
目录1. 回车与换行1.1 示例演示1.2 fflush(stdout)1.3 倒计时2. 实例2.1 进度条核心模块2.2 独立加载旋转模块2.3 模拟下载与回调2.4 主函数1. 回车与换行虽然我们在日常打字时经常把“回车”和“换行”混为一谈通常按一下Enter键就同时完成了这两个动作但在计算机底层它们其实是两个完全不同的概念。\r CRCarriage Return回车光标回到本行开头\n LFLine Feed换行光标下移一行Linux / UnixLF\n单独作为行结束符WindowsCRLF\r\n回车 换行两个字符老 MacMac OS 9 及更早CR\r现代 macOS 是 LF\n老式打字机的例子这是理解这两个概念最经典的例子在老式机械打字机上当你打完一行字你需要进行两个独立的物理操作才能开始写下一行回车 (Carriage Return)用手把打字机的字车打字头推回最左边。换行 (Line Feed)拨动一个拨杆让滚筒把纸张向上卷一行留出新的空白行。如果只做第一步你会打在原来的字上如果只做第二步你会从下一行的中间开始打字。回车概念 (Carriage Return,\r)字面意思在英文中 Carriage 指的是老式打字机上的“字车”承载纸张并左右移动的部件。动作将打字头或光标移回到当前行的最左侧开头位置。注意它不会切换到下一行。如果只执行回车新的字符会覆盖在当前行原有的字符上。编程符号通常用\r表示ASCII码为 13。换行概念 (Line Feed,\n)字面意思Feed 指的是进纸。动作将纸张向上卷动一行或者将光标垂直向下移动到下一行。注意它不会改变水平位置。如果只执行换行光标会来到下一行但依然停留在当前所在的列通常是行尾。编程符号通常用\n表示ASCII码为 10。1.1 示例演示1. 带\r\n的代码# 光标在这里闪烁等待2秒执行./test.exe后立刻打印了hello world。等待2秒期间屏幕上已经能看到结果了。原因Linux 终端默认是行缓冲Line Buffered。printf遇到了\n换行符触发了“刷新机制”把缓冲区里的数据立刻推到了显示器上并且执行了回车换行。只有\n或 没有换行符 的代码此时终端一片空白。程序确实执行了printf但内容被扣在了 Linux 标准库在内存中分配的用户态缓冲区里main函数执行return 0时C 运行时会清理资源强制把缓冲区里的hello world冲刷flush到终端。细节现象因为程序结束时只刷新了缓冲区并没有自动带换行所以hello world会和下一行的命令提示符[whb...]$挤在同一行。1.2fflush(stdout)stdout全称 Standard Output标准输出。在 Linux/C语言中它默认指代你的终端屏幕。它是一个FILE *类型的指针。fflush全称 File Flush文件冲刷/刷新。连起来“强制把stdout标准输出对应的缓冲区里的数据立刻推送到屏幕硬件上不要等”在 Linux 终端下printf默认是行缓冲Line Buffered的。意思是它像个懒汉你不给它一点甜头换行符\n或者不把它的桶装满缓冲区满或者不拿枪指着它程序结束它就不愿意动。printf(Loading); // 数据被扔进了内存缓冲区屏幕没反应 sleep(5); // 这5秒内屏幕上什么都没有如果你把代码改成printf(Loading); fflush(stdout); // 强行把内存里的 Loading 推给屏幕 sleep(5); // 这5秒内屏幕上一直显示 Loading这就是fflush的作用打破了“必须遇到\n才刷新”的限制。1.3 倒计时2. 实例2.1 进度条核心模块功能接收当前进度和总进度计算出百分比利用回车符\r在控制台同一行不断覆盖刷新绘制出带旋转动画的进度条。void FlushProcess(double total, double curr) // 更新进度按照下载进度进行更新进度条 { if(curr total) curr total; double rate curr / total * 100; // 1024.0 , 512.0 - 0.5 - 50.0 int cnt (int)rate; // 50.8 , 49.9 - 50, 49 char processbuff[SIZE]; memset(processbuff, \0, sizeof(processbuff)); int i 0; for(; i cnt; i) processbuff[i] STYLE; static const char *lable |/-\\; static int index 0; // 刷新 printf([%-100s][%.1lf%%][%c]\r, processbuff, rate, lable[index]); index % strlen(lable); fflush(stdout); if(curr gt; total) { printf(\n); } }数据校验与计算curr total时强制拉回防止越界(int)rate用于确定进度条中的个数。构建缓冲区char processbuff[SIZE]和memset清空是标配。for循环往数组里填入STYLE。静态变量魔法static int index 0;是核心。因为函数会频繁调用普通的局部变量每次都会重置加上static后它能记住上一次的值从而实现lable[index]字符的不断轮换实现旋转动画。覆盖刷新printf末尾的\r让光标回到行首。%-100s保证进度条占满100个字符宽度防止旧内容残留。收尾fflush(stdout)强制将缓冲区的内容输出到屏幕。if(curr total)时打印\n让后续的输出换行。2.2 独立加载旋转模块功能这是一个独立测试函数不需要外部传参自己控制从 0 到 100 的循环并利用旋转字符集模拟纯加载动画。// version1: 能够使用吗 // 说明原理 void Process() { const char *lable |/-\\; int len strlen(lable); char processbuff[SIZE]; memset(processbuff, \0, sizeof(processbuff)); int cnt 0; while(cnt 100) { printf([%-100s][%d%%][%c]\r, processbuff, cnt, lable[cnt%len]); fflush(stdout); processbuff[cnt] STYLE; usleep(30000); } printf(\n); }旋转技巧这里的旋转用了一个很巧妙的方法lable[cnt%len]。因为cnt是递增的对len也就是4取模会得到0, 1, 2, 3, 0...的循环从而依次取出|,/,-,\。内部驱动这个模块是一个死循环直到cnt 100。它使用usleep(30000)休眠 30 毫秒来模拟程序的延时。对比模块一模块一是由外部下载进度驱动模块二是由内部usleep驱动。模块一是被动刷新模块二是主动循环刷新。2.3 模拟下载与回调功能定义回调函数类型封装随机速度生成器模拟真实的下载循环逻辑。通过函数指针实现业务与界面的解耦。// 函数指针类型 typedef void (*callback_t)(double, double); // 1.0 4.3 double SpeedFloat(double start, double range) // [1.0 3.0] - [1.0, 4.0] { int int_range (int)range; return start rand()%int_range (range - int_range); } // cb: 回调函数 void DownLoad(int total, callback_t cb) { srand(time(NULL)); double curr 0.0; while(1) { if(curr total) { curr total; // 模拟下载完成 cb(total, curr); // 更新进度按照下载进度进行更新进度条 break; } cb(total, curr); // 更新进度按照下载进度进行更新进度条 curr SpeedFloat(speed, 20.3); // 模拟下载行为 usleep(30000); } }函数指针callback_t类似于 Java 中的接口或 C# 中的委托。它规定了回调函数的格式void (*)(double, double)。这意味着任何接受两个double参数且无返回值的函数都能作为回调传入。随机速度生成SpeedFloatrand()用于生成随机整数。通过取模和浮点数计算生成了一个带有小数部分的随机步长用来模拟网络波动。下载循环DownLoadsrand(time(NULL))初始化随机数种子务必在rand()前调用且通常全局只调用一次。每轮循环先调用cb(total, curr)。此时传入的cb实际上是FlushProcess函数。这是关键解耦点下载模块完全不知道界面长什么样只负责“通知”刷新。然后curr SpeedFloat(...)增加进度usleep模拟网络延迟。当curr total时补一次满进度回调然后跳出循环。2.4 主函数int main() { printf(download: 20.0MB\n); DownLoad(20.0, FlushProcess); printf(download: 2000.0MB\n); DownLoad(2000.0, FlushProcess); printf(download: 100.0MB\n); DownLoad(100.0, FlushProcess); printf(download: 20000.0MB\n); DownLoad(20000.0, FlushProcess); return 0; }