马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
进度条看起来是个小功能,真要写出来却牵出两个最基础也最容易想当然的概念:回车(\r)和换行(\n)不是一回事、缓冲区什么时候刷新。把这两件事搞明白,写一个终端进度条只需要十几行代码。本文从概念讲到代码,并给出两个版本。
一、回车和换行,其实是两个动作
教科书和日常表述里常把两者混着说,但键盘上的老式打字机给了最清楚的对照:写满一行要移到下一行,实际是两个动作——先换行(到下一行),再回车(回到行首)。我们语文作文里那个"下一行行首"的位置,就是这两步合起来的结果。
放到 C 语言里:
\n 是换行(换行到下一行);
\r 是回车(回到当前行行首)。
两者单独用时都不是"换到下一行行首"。本文要做的进度条,用的正是 \r 这个光标回退特性——把光标退到行首,下次就能原地覆盖上一行的输出,进度条之所以"滚着走"就是这么来的。
二、缓冲区:为什么会先休眠再打印
看这段代码在 Linux 里跑:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("Hello Linux!");
sleep(1);
return 0;
}
结果是:先等一秒,然后才打印出 Hello Linux! 很多人第一反应是"printf 慢",其实恰恰相反——printf 是快地把内容送进了缓冲区(一块内存),然后等待刷新;因为没有刷新标志,程序就先去执行 sleep(1) 了,等程序结束才强制刷新缓冲区,于是看起来"先休眠再打印"。
在 "Hello Linux!" 后面加上 \n,现象就变成先打印再休眠——因为 \n 是行刷新标志,遇到它立刻把缓冲区内容送到显示器。这个差别不明显,但正好引出了缓冲区的机制。
三、手动强制刷新
不想靠 \n 也能验证缓冲区,就用 fflush 强制刷新。它的参数是文件指针——去哪找文件指针?系统默认打开了三个流:stdin(输入)、stdout(输出)、stderr(错误),输出流就是 stdout。为什么是默认打开的?因为早期设计者看到大家都有这个需求,就定为默认行为沿用至今。
把代码改成:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("Hello Linux!");
fflush(stdout);
sleep(1);
return 0;
}
现象就变成先打印、再 sleep——这就是强制刷新。
顺带回答"为什么非要用 \n 当刷新标志":从缓冲区刷到显示器,原则是少次数、多数据量。早期设计者综合权衡后认为行刷新最合适,既兼顾刷新次数也兼顾数据量,于是选了 \n(行刷新,line buffered)。
四、第一版进度条
有了上面这些基础,进度条就水到渠成:
#pragma once
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define LENGTH 101
#define style '='
const char* lable = "|/-\\";
void processbar()
{
char arr[LENGTH];
memset(arr, '\0', sizeof(arr));
int len = strlen(lable);
int cnt = 0;
while (cnt < 100)
{
arr[cnt++] = style;
usleep(20000);
}
printf("\n"); // 不换行会导致命令提示符覆盖进度条
}
两个关键点:
- 不强制刷新也能有进度条效果,但会等
usleep 休眠完才一次性打印,观感卡顿——这正是第二节缓冲区的坑在进度条上的体现;
- 结尾一定要
printf("\n"),否则命令提示符会接着进度条那一行打印,把进度条顶掉,看起来像乱了。
顺带一个 C 语言细节:在格式化输出里要表达一个 %,得写成两个 %(和写两个反斜杠转义同理),不然会被当成格式符解析掉。
五、第二版:带百分比的进度条
第一版光秃秃一条等号,看不出进度多少。加个百分比修饰:
typedef void (*callback_t)(double, double);
void processbar(double total, double current)
{
char arr[LENGTH];
memset(arr, '\0', sizeof(arr));
double rate = (current * 100.0) / total;
int loop_rate = (int)rate;
int len = strlen(lable);
int cnt = 0;
while (cnt < loop_rate)
{
arr[cnt++] = style;
usleep(20000);
}
printf("[%s][%d%%][%c]\r", arr, loop_rate, lable[cnt % len]);
fflush(stdout);
}
要点:rate 用浮点算百分比,loop_rate 取整后当循环上界;后面的转圈符号 lable[cnt % len] 靠取模循环切换 | / - \ 四个字符,形成转动效果;行尾 \r 回退加 fflush(stdout) 强制刷新,进度才能原地往前长。
六、三个容易踩的坑
- 忘了 fflush:进度条要么不动,要么一次跳到底,都是缓冲区没刷新的表现;
- 结尾少了换行:命令提示符直接压在进度条后面,明明跑完了看着像失败;
- 百分号只写了一个:被当成格式符,输出错乱或者编译告警。
七、小结
整个项目其实只用了两个知识点:\r 让光标回退到行首,fflush(stdout) 让缓冲区立刻输出。 加上 \n 是行刷新、\r 只是回退这个区分,就能理解为什么进度条必须"回退 + 强制刷新"配合。剩下的就是 memset 清零、数组当画布、循环画等号这些基础操作了。
qidao123.com企服评测-应用市场 编辑提示 |