我最近想在C中制作一个“正在加载...”的显示,点依次一次打印一个,然后重新设置:互联网上没有很多东西可以做到这一点,所以我想我会为此做一个简单的程序。

#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;
        }
    }
}
以上。

我知道可以做得更好。有任何改进建议吗?

评论

移动中的绿色框怎么了?功能?

@ Mat'sMug打印回车的伪像很快返回。

从第二个角度看,这当然只是因为四个点-状态被立即覆盖。这可以通过没有这么快的重写速度来解决,而这个问题已经在一个没关系的答案中处理了。 :)

#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