#include <stdio.h>
#include <time.h>
int main(void)
{
int msec = 0;
const int trigger = 500; // ms
const int printWidth = 4;
int counter = 0;
clock_t before = clock();
while (1)
{
fputs("Loading", stdout);
clock_t difference = clock() - before;
msec = difference * 1000 / CLOCKS_PER_SEC;
if (msec >= trigger)
{
counter++;
msec = 0;
before = clock();
}
for (int i = 0; i < counter; ++i)
{
fputc('.', stdout);
}
for (int i = 0; i < printWidth - counter; ++i)
{
fputc(' ', stdout);
}
fputc('\r', stdout);
fflush(stdout);
if (counter == printWidth)
{
counter = 0;
}
}
}
以上。 我知道可以做得更好。有任何改进建议吗?
#1 楼
太疯狂了我运行了您的程序,但是非常疯狂。它一直在清除“正在加载”提示并重新打印,从而导致闪烁效果。另外,光标也以闪烁的方式移动(类似于动画图像中的绿色框)。
要改善这一点,我要做两件事:
没有任何变化,请不要不断画画。而是睡到下一个触发时间,然后重画。这样做还有一个好处,就是不用100%的CPU。大概,您将有另一个正在运行的线程正在执行一些加载工作,并且您不希望该线程占用CPU时间。
无需清除并重绘整个提示,直到最后一个点。到目前为止,您一次只能绘制一个点。 />
#include <stdio.h>
#include <unistd.h>
int main(void)
{
const int trigger = 500; // ms
const int numDots = 4;
const char prompt[] = "Loading";
while (1) {
// Return and clear with spaces, then return and print prompt.
printf("\r%*s\r%s", sizeof(prompt) - 1 + numDots, "", prompt);
fflush(stdout);
// Print numDots number of dots, one every trigger milliseconds.
for (int i = 0; i < numDots; i++) {
usleep(trigger * 1000);
fputc('.', stdout);
fflush(stdout);
}
}
}
重写2 />
#include <stdio.h>
#include <time.h>
static void redrawPrompt(void);
static void doWork(void);
int main(void)
{
const int trigger = (CLOCKS_PER_SEC * 500) / 1000; // 500 ms in clocks.
clock_t prevClock = clock() - trigger;
while (1) {
clock_t curClock = clock();
if (curClock - prevClock >= trigger) {
prevClock = curClock;
redrawPrompt();
}
doWork();
}
}
static void redrawPrompt(void)
{
static int numDots;
const int maxDots = 4;
const char prompt[] = "Loading";
// Return and clear with spaces, then return and print prompt.
printf("\r%*s\r%s", sizeof(prompt) - 1 + maxDots, "", prompt);
for (int i = 0; i < numDots; i++)
fputc('.', stdout);
fflush(stdout);
if (++numDots > maxDots)
numDots = 0;
}
static void doWork(void)
{
// This function does loading work but returns at least every 500 ms.
}
评论
\ $ \ begingroup \ $
这将暂停/挂起当前线程,这就是为什么我首先编写了一个计时器来处理该线程的原因。您能想到一种更好的方法来不睡觉吗?另外,unistd.h在Windows系统上不可用。
\ $ \ endgroup \ $
–syb0rg
16年8月23日在18:01
\ $ \ begingroup \ $
为什么用sizeof(prompt)-1代替strlen(prompt)?
\ $ \ endgroup \ $
–machine_1
16年8月23日在18:27
\ $ \ begingroup \ $
@ machine_1,因为在这种情况下,它是数组类型。在编译时使用sizeof将产生其大小,而strlen是运行时。数组不是指针的另一个原因(至少在编译期间)。
\ $ \ endgroup \ $
–难以置信
16年8月23日在18:30
\ $ \ begingroup \ $
@ syb0rg为什么不想挂起线程?是否应该调用工作函数?如果是这样,那么您可以调用工作函数并读取一个计时器。但是除非时间已过下一个触发点,否则您不会重印提示。
\ $ \ endgroup \ $
– JS1
16年8月23日在20:16
\ $ \ begingroup \ $
@ syb0rg参见我的重写#2。也许这就是您在想的更多?
\ $ \ endgroup \ $
– JS1
16年8月23日在20:33
#2 楼
您可以通过禁用光标来摆脱闪烁的绿色框。fputs("\e[?25l", stdout); /* hide the cursor */
如果需要,可以重新启用它。
fputs("\e[?25h", stdout); /* show the cursor */
评论
\ $ \ begingroup \ $
仅当您的终端支持ANSI转义码时,例如Windows cmd.exe不支持
\ $ \ endgroup \ $
–猫
16年8月24日在0:21
\ $ \ begingroup \ $
@cat我确实假定输出是代码的支持环境,是的。如果将输出重定向到文件,则带有isatty的检查也可能会有所帮助,否则文件文本会混乱。
\ $ \ endgroup \ $
–syb0rg
16年8月24日在2:41
\ $ \ begingroup \ $
@cat几个月来,Windows 10 cmd.exe支持ANSI转义码,但要在没有Visual Studio的情况下进行编译将很难。 (甚至Pelles C都有一些停机时间!)
\ $ \ endgroup \ $
– wizzwizz4
16年8月24日在9:44
\ $ \ begingroup \ $
@ wizzwizz4你是认真的吗?这是我整周听到的最荒谬的话。无论如何,我仍然认为这是“不支持”,因为它是最近发行的,并没有交付给用户,因此您必须自己呕吐MSVC ++。
\ $ \ endgroup \ $
–猫
16年8月24日在13:48
\ $ \ begingroup \ $
@cat安装操作系统时,它已经运送到我的计算机上。参见en.wikipedia.org/wiki/ANSI_escape_code#Windows_and_DOS
\ $ \ endgroup \ $
– wizzwizz4
16年8月24日在14:28
#3 楼
我可能会将此绑定到实际上正在加载内容的任何代码中,并每100 kb或任何数字产生一个新的点(或使用/-\|
微调器),而不是尝试花费时间。如果停顿或运行缓慢,这也会增加一些额外的反馈。在更改阶段时打印任何内容。#include <stdio.h>
#include <time.h>
const char *dot_str[] = {".", ".", ".", "\b\b\b \b\b\b"};
#define countof(x) (sizeof(x)/sizeof((x)[0]))
static int next_state = 0;
void update_progress(void) {
fputs(dot_str[next_state], stdout);
next_state = (next_state + 1) % countof(dot_str);
fflush(stdout);
}
static time_t last_time = 0;
void update_progress_if_time(void) {
time_t now = time(NULL);
if(now > last_time) {
update_progress();
last_time = now;
}
}
void start_progress(const char *loading) {
fputs(loading, stdout);
next_state = 0;
last_time = 0;
fflush(stdout);
}
对于此示例,如果可以安排将其在足够大的固定值之后调用,我将使用update_progress作为回调工作单位,否则update_progress_if_time大约每秒更新一次。也许将变量移入结构并将其传递给回调可能有意义,但是无论如何您只有一个标准输出。
int main(void) {
start_progress("Loading");
for(;;) {
update_progress_if_time();
}
}
评论
\ $ \ begingroup \ $
我不想在我的问题中过多地限制评论,但是我打算如何使用此代码是为了在我的一个项目中替换混乱的“ Listening:”。对用户来说,这将是一个很好的指示,表明系统仍在工作而不会造成输出混乱。
\ $ \ endgroup \ $
–syb0rg
16年8月24日在4:46
\ $ \ begingroup \ $
@ syb0rg我添加了一些示例代码。对于您所链接的特定情况,您可能还希望在收到第一个“讲话”帧时“开始”动画,并在第一个静音时结束(擦除它?打印换行符?打印回车符和其他消息?)。帧被接收。而不是使用基于时间的版本,我只是选择适当数量的帧并每N帧调用一次update_progress。
\ $ \ endgroup \ $
–Random832
16年8月24日在4:55
\ $ \ begingroup \ $
(退格之间的)三个空格分别是什么?
\ $ \ endgroup \ $
–阿曼尼·基卢曼加(Amani Kilumanga)
16年8月25日在7:42
\ $ \ begingroup \ $
@AmaniKilumanga要删除点-退格键只是移动光标。
\ $ \ endgroup \ $
–Random832
16年8月25日在16:27
#4 楼
代替双for循环:一个用于点,一个用于空格,您可以编写组合的一个:可以使用取模运算符。for (int dotPosition = 0; dotPosition < printWidth; ++dotPosition)
{
fputc(dotPosition < counter ? '.' : ' ', stdout);
}
正如其他人所说,无时无刻不在冲洗(浪费水),这是我程序的版本。
counter = (counter+1) % printWidth;
我不确定为什么,但是也没有显示出疯狂的光标瞬移。它停留在行尾。
评论
\ $ \ begingroup \ $
光标不会移动,因为在这里,打印速度很慢,无法跟上终端的速度,但是您仍然需要全天候使用cpu内核进行全天候()调用。
\ $ \ endgroup \ $
–ilkkachu
16年8月24日在12:30
\ $ \ begingroup \ $
@ilkkachu感谢您提供信息。我认为繁忙的循环是故意的。我主要将C用于嵌入式内容。在我看来,定时器中断将是执行间隔的更好方法,但我认为在香草C中没有一种很好的可移植方法来执行此操作。如果我愿意我错了。
\ $ \ endgroup \ $
–明天我将添加评论
16年8月24日在12:40
评论
移动中的绿色框怎么了?功能?@ Mat'sMug打印回车的伪像很快返回。
从第二个角度看,这当然只是因为四个点-状态被立即覆盖。这可以通过没有这么快的重写速度来解决,而这个问题已经在一个没关系的答案中处理了。 :)