☰
Linux 开发工具(五) 实现动态进度条
2026/10/23 4:57:22 网站建设 项目流程

目录

一. 回车和换行的区别

二. 初探缓冲区

2.1 倒计时小程序

2.1.1 手动清除缓冲区 fflush

优化一下倒计时小程序:

2.1.2 理解显示

最后优化一下小程序:

三. 模拟实现进度条

3.1 版本一

Makefile:

process.cpp:

process.h:

main.cpp:

3.2 版本二

process.cpp:

main.cpp:


一. 回车和换行的区别

  • 换行(\n):光标移动到下一行的行首,但不会回到当前行开头;我们日常使用它的时候其实是回车+换行的作用(=/r/n)
printf("hello"); printf("\n"); printf("world"); //第 1 行:hello // ↓ \n 只向下移动一行 //第 2 行: world
  • 回车(\r):光标回到当前行的行首,但不会移动到下一行;
#include <stdio.h> int main() { printf("hello\rABC"); return 0; } //ABClo

二. 初探缓冲区

下面这个代码运行的时候我们会发现很有意思的情况:

#include<iostream> 2 #include<unistd.h> 3 using namespace std; 4 5 int main() 6 { 7 cout<<"hallo linux"; 8 sleep(3); 9 return 0; 10 } 11

我们会发现他会先休眠3秒再打出hallo linux,但是执行流程依旧是自上而下,先cout<<"hallo linux";再sleep(3),为什么呢?

2.1 倒计时小程序

#include <iostream> #include <unistd.h> #include <cstdio> using namespace std; int main() { // 1. 循环从 9 开始,每次减 1,直到 0 for (int i = 9; i >= 0; i--) { // 2. 将当前数字和 \r 写入标准输出 // \r 表示回车,让终端光标回到当前行行首 // 注意:\r 本身不会刷新输出缓冲区 printf("%d\r", i); // 3. 由于没有使用 \n,也没有调用 fflush(stdout) // 输出内容可能暂时留在标准输出缓冲区中 } // 4. 循环结束后,缓冲区中可能保存着: // 9\r8\r7\r6\r5\r4\r3\r2\r1\r0\r // 注意:缓冲区保存的是全部字符,不是只有 0 // 5. 程序休眠 3 秒 // 此时循环已经执行完毕,不会每输出一个数字就休眠 // 如果缓冲区尚未刷新,这 3 秒内可能看不到输出 sleep(3); // 6. main 函数正常结束 // C 标准规定,正常终止时会刷新打开的输出流 // 所以缓冲区中的内容会被送往终端 // 7. 终端依次处理收到的字符: // 显示 9 → \r 回到行首 // 显示 8 → \r 回到行首 // ... // 显示 0 → \r 再回到行首 // 因此,最终通常只剩下数字 0 // 8. 但此时光标停在当前行行首 // 程序退出后,Shell 输出新的命令提示符 // 提示符从行首开始输出,可能覆盖数字 0 // 所以看起来就像没有输出一样 return 0; }

2.1.1 手动清除缓冲区 fflush

fflush()是 C 语言中的一个函数,用于刷新输出缓冲区,把缓冲区中尚未输出的数据及时交给目标设备(例如终端)。

#include <stdio.h> printf("hello"); fflush(stdout); // 立即刷新标准输出缓冲区
优化一下倒计时小程序:
#include <iostream> #include <stdio.h> // 提供 printf、fflush、stdout #include <unistd.h> // 提供 sleep() using namespace std; int main() { int i = 9; // 从 9 开始倒数,每次减 1,直到 0 for (i = 9; i >= 0; i--) { printf("%d\r", i); // 输出数字,\r 让光标回到行首 fflush(stdout); // 立即刷新输出缓冲区,让数字显示出来 sleep(1); // 暂停 1 秒,再输出下一个数字 } return 0; }

2.1.2 理解显示

显示器只认字符:

最后优化一下小程序:

优化前:

#include <stdio.h> #include <unistd.h> int main() { for (int i = 10; i >= 0; i--) { printf("%d\r", i); // %d:按照数字的实际位数输出 // \r:让光标回到当前行行首 fflush(stdout); // 立即刷新输出缓冲区 sleep(1); // 暂停 1 秒,再输出下一个数字 } printf("\n"); return 0; }

问题:第一次输出10,第二次输出9时,只覆盖了原来的1,原来的0还在,因此会显示90,接着显示80、70……

优化后:

#include <stdio.h> #include <unistd.h> int main() { for (int i = 10; i >= 0; i--) { printf("%2d\r", i); // %2d:输出宽度至少为 2 // 一位数前面自动补一个空格 // \r:让光标回到当前行行首 fflush(stdout); // 立即刷新输出缓冲区 sleep(1); // 暂停 1 秒,再输出下一个数字 } printf("\n"); return 0; }

效果:第一次输出10,第二次输出的是空格 + 9,空格覆盖原来的1,9覆盖原来的0,因此不会残留旧字符。

三. 模拟实现进度条

3.1 版本一

Makefile:

BIN=processbar # 最终生成的可执行文件名 SRC=$(wildcard *.cpp) # 查找 make 当前工作目录下的所有 .cpp 文件 OBJ=$(SRC:.cpp=.o) # 将 SRC 中的 .cpp 后缀替换为 .o # 例如:a.cpp b.cpp -> a.o b.o $(BIN):$(OBJ) g++ $(OBJ) -o $(BIN) # 目标:processbar # 依赖:所有 .o 文件 # 当需要更新目标时,将所有 .o 文件链接成 processbar %.o:%.cpp g++ -c $< -o $@ # 模式规则:用对应的 .cpp 文件生成 .o 文件 # %:匹配相同的文件名部分 # $<:第一个依赖文件,例如 a.cpp # $@:当前目标文件,例如 a.o # -c:只编译,不进行链接 .PHONY:clean clean: rm -f $(BIN) $(OBJ) # clean 是伪目标,每次执行 make clean 都会运行命令 # 删除可执行文件和所有 .o 文件

process.cpp:

#include "process.h" #include <stdio.h> #include <string.h> #include <unistd.h> #define NUM 101 // 缓冲区大小为 101,合法下标为 0~100 void process_v1() { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); // 将 buffer 全部初始化为 '\0' // 此时 buffer 是一个空字符串 char flag[] = "-\\|/"; // 定义旋转字符:'-'、'\'、'|'、'/' // 注意:"\\" 表示一个反斜杠字符 int length = strlen(flag); // strlen 不计算末尾的 '\0' // 因此 length = 4 int count = 0; for (count = 0; count <= 100; count++) { printf("[%-100s] %d%% %c\r", buffer, count, flag[count % (length - 1)]); // %-100s:字符串左对齐,输出宽度至少为 100 // %d%%:输出 count,后面的 %% 表示输出一个百分号 // %c:输出一个旋转字符 // count % (length - 1):对 3 取余,因此只会访问 // flag[0]、flag[1]、flag[2],不会显示 flag[3] 的 '/' // \r:让光标回到当前行行首,覆盖上一轮显示 fflush(stdout); // 立即刷新标准输出缓冲区,让进度条及时显示 buffer[count] = '#'; // 将当前下标设置为 '#' // 下一轮输出时,进度条就会多显示一个 '#' // 注意:count == 100 时,会覆盖 buffer[100] 原来的 '\0' count++; // 这里额外自增一次 // 由于 for 循环本身也会执行 count++, // 所以每轮实际增加 2,进度会跳着增长 usleep(50000); // 暂停 50000 微秒,也就是 0.05 秒 } printf("\n"); // 输出换行,避免后续命令提示符与进度条显示在同一行 }

process.h:

1 #pragma once 2 #include<stdio.h> 3 4 void process_v1();

main.cpp:

1 #include"process.h" 2 #include<stdio.h> 3 4 int main() 5 { 6 process_v1(); 7 return 0; 8 }

3.2 版本二

版本二相较于版本一,模拟了一下文件的下载过程:

process.cpp:

#include "process.h" // 引入进度条函数的声明 #include <stdio.h> // 提供 printf()、fflush()、stdout #include <string.h> // 提供 memset()、strlen() #include <unistd.h> // 提供 usleep() #define NUM 101 // 缓冲区大小为 101,合法下标为 0~100 void process_v2(double total, double current) { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); // 将 buffer 全部初始化为 '\0' // 这样 buffer 一开始就是一个空字符串 char flag[] = "-\\|/"; // 定义旋转字符:'-'、'\'、'|'、'/' // 注意:在字符串中,\\ 表示一个反斜杠字符 int length = strlen(flag); // 获取旋转字符的数量 // strlen 不计算末尾的 '\0',所以 length = 4 int count = (int)(current * 100 / total); // 根据当前下载量计算进度百分比 // 例如:current = 513,total = 1026 // count = 513 * 100 / 1026 = 50 int i = 0; for (i = 0; i < count; i++) { buffer[i] = '#'; // 根据进度填充进度条 // 例如 count = 50,就将 buffer[0]~buffer[49] 设置为 '#' // buffer[count] 及后面的内容仍然是 '\0' } static int num = 0; // 定义静态局部变量,记录旋转字符的位置 // static 使 num 在函数调用结束后仍然保留原来的值 num %= length; // 将 num 限制在 0~3 之间 // 防止访问 flag 数组时下标越界 double rate = current * 100 / total; // 计算实际进度百分比 // 使用 double 保留计算结果的小数部分 printf("[%-100s] %.1lf%% %c\r", buffer, rate, flag[num]); // %-100s:字符串左对齐,输出宽度至少为 100 // %.1lf:输出浮点数,保留 1 位小数 // %%:输出一个真正的百分号 // %c:输出当前旋转字符 // \r:让光标回到当前行行首,方便覆盖上一轮进度条 num++; // 切换到下一个旋转字符 // 下次调用时,通过 num %= length 循环使用四个字符 fflush(stdout); // 立即刷新标准输出缓冲区 // 让当前进度条及时显示出来 }

main.cpp:

#include "process.h" // 引入进度条函数的声明 #include <stdio.h> // 提供 printf() #include <unistd.h> // 提供 usleep() double total = 1026.0; // 下载文件的总大小,单位 MB double speed = 1.0; // 每次循环模拟增加的下载量,单位 MB void download() { double current = 0.0; // 当前已下载大小,初始为 0 // 当前下载量不超过文件总大小时,持续更新进度 while (current <= total) { process_v2(total, current); // 调用 process_v2(),显示当前下载进度 // total:文件总大小 // current:当前已下载大小 usleep(3000); // 暂停 3000 微秒,即 3 毫秒 // 模拟下载过程中的时间间隔 current += speed; // 更新已下载大小,每次增加 1 MB } // 下载循环结束,打印完成信息 // %.2lf:浮点数保留两位小数 printf("\ndownload %.2lfMB Done\n", current); } int main() { // process_v1(); // 之前的进度条版本,暂时不调用 download(); // 调用下载函数,启动模拟下载 return 0; // 程序正常结束 }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询