《Linux系统编程》Linux基础开发工具 (三):从零实现动态进度条(附回车、换行与缓冲区详解)
小叶-duck个人主页❄️个人专栏《Data-Structure-Learning》《C入门到进阶自我学习过程记录》《Linux操作系统从入门到实践》《Qt从入门到实践》《算法题讲解指南》--优选算法《算法题讲解指南》--递归、搜索与回溯算法《算法题讲解指南》--动态规划算法✨未择之路不须回头已择之路纵是荆棘遍野亦作花海遨游目录一、两个储备知识回车换行 / 缓冲区1.1 回车\r与换行\n的本质区别1.2 深入理解行缓冲区运行机制1.3 进度条的核心构成元素详解二、实战开发打造动态彩色进度条2.1 进度条测试版本演示原理2.2 基础版进度条模拟实现无拓展功能实现2.3 自动化构建流程Makefile编写2.4 头文件设计process.h接口函数声明2.5 核心实现文件process.c2.6 主函数模块main.c应用场景测试三、操作实践与效果展示结束语一、两个储备知识回车换行 / 缓冲区在动手写代码前必须先理清 2 个关键概念否则容易出现 “进度条不刷新”“换行错乱” 等问题。1.1 回车\r与换行\n的本质区别换行\n光标移动到下一行的行首但不会回到当前行开头我们日常使用它的时候其实是回车换行的作用(/r/n)回车\r光标回到当前行的行首但不会移动到下一行进度条的核心是 “在同一行反复覆盖刷新”因此必须用\r让光标回到行首再重新打印新的进度信息。1.2 深入理解行缓冲区运行机制C语言的 printf 函数默认是“行缓冲”——只有遇到\n、缓冲区满或手动刷新fflush(stdout)时才会把缓冲区的内容输出到终端。示例#include stdio.h #include unistd.h int main() { printf(hello world!); sleep(1); return 0; }如果只写printf 而不加\n或fflush内容会一直存在缓冲区终端看不到任何输出这就是很多人写进度条 “没反应” 的原因。注意虽然没显示出来但是我们的C语言默认是顺序结构的一定是先执行 printf 再执行 sleep的。示例修正#include stdio.h #include unistd.h int main() { printf(hello world!); fflush(stdout); sleep(1); return 0; }练练手光标快速回退完成倒计时功能#include stdio.h #include unistd.h int main() { int i 10; while(i 0) { printf(%-2d\r, i); fflush(stdout); i--; sleep(1); } printf(\n); }1.3 进度条的核心构成元素详解一般一个完整的动态进度条通常包含以下部分进度条主体用等字符填充直观显示完成比例百分比显示完成进度0%~100%动态光标用 | / - \ 循环切换提示程序正在运行。附加信息如当前进度 / 总进度、传输速度提升实用性。二、实战开发打造动态彩色进度条基于基础框架我们先实现一个基础功能的简单进度条后续慢慢优化实现出 “彩色区分 速度显示 多场景适配” 的进度条核心分为头文件、实现文件、主函数三部分。2.1 进度条测试版本演示原理这个测试版本的其他文件我就不写了就展示一个proces.cvoid process_v1() { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char* lable |/-\\; int len strlen(lable); int cnt 0; while(cnt 100) { // printf([%s]\r, buffer); printf([%-100s][%d%%][%c]\r, buffer, cnt, lable[cnt % len]); fflush(stdout); buffer[cnt] STYLE; usleep(50000); } printf(\n); }2.2 基础版进度条模拟实现无拓展功能实现//process.h #pragma once #include stdio.h void FlushProcess(double current, double total); //process.c #include process.h #include string.h #include unistd.h #include stdlib.h #define NUM 101 #define STYLE # void FlushProcess(double current, double total) { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char* lable |/-\\; int len strlen(lable); int cnt (int)(current * 100 / total); static int count 0; int i 0; for(i 0; i cnt; i) { buffer[i] STYLE; } printf([%-100s][%.1f%%][%c]\r, buffer, current * 100 / total, lable[count % len]); fflush(stdout); count; } //main.c #include process.h #include stdio.h #include unistd.h double total 1024.0; double speed 1.0; //使用回调函数 typedef void (*callback_t)(double current, double total); //函数指针类型 取别名-callback_t void Download(callback_t cb) { double current 0.0; while(current total) { cb(current, total); //下载代码 usleep(3000); //充当下载数据 current speed; } printf(\n); printf(download %.2lfMB Done\n, total); } int main() { Download(FlushProcess); return 0; }2.3 自动化构建流程Makefile编写为了方便编译和清理编写 Makefile 实现自动化构建只需一条命令即可生成可执行文件我们在之前讲过 Makefile 的编写策略这里就不多说了直接展示BINprocess.exe SRC$(shell ls *.c) OBJ$(SRC:.c.o) $(BIN):$(OBJ) gcc $^ -o $ %.o:%.c gcc -c $ .PHONY: clean: rm -f $(OBJ) $(BIN)2.4 头文件设计process.h接口函数声明定义进度条回调函数类型和核心接口方便后续扩展和复用#pragma once #include stdio.h //void process_v1(); //void FlushProcess(double current, double total); // 定义进度条回调函数类型适配不同场景的进度刷新逻辑 typedef void (*flush_t)(double total,double current,double speed,const char* userinfo); // 彩色动态进度条核心接口 // total总进度如文件总大小 // current当前进度如已下载大小 // speed当前速度如MB/s // userinfo附加信息如单位 void Process_version(double total,double current,double speed,const char*userinfo);2.5 核心实现文件process.c集成颜色控制、进度计算、动态刷新是进度条的核心#include process.h #include string.h // 进度条配置参数 #define NUM 103 // 进度条缓冲区大小适配100%额外字符 #define STYLE # // 进度填充字符 #define COLOR_GREEN \033[32m // 绿色 #define COLOR_GRAY \033[90m // 灰色 #define COLOR_CYAN \033[36m // 青色 #define COLOR_RESET \033[0m // 重置颜色 #define COLOR_RED \033[31m // 红色 #define COLOR_YELLOW \033[33m // 黄色 #define COLOR_BLUE \033[34m // 蓝色 #define COLOR_MAGENTA \033[35m // 品红 #define COLOR_WHITE \033[97m // 白色 #define COLOR_BLACK \033[30m // 黑色 // 彩色动态进度条实现 void Process_version(double total, double current, double speed, const char*userinfo) { // 边界处理当前进度超过总进度时直接返回 if(current total) { return; } // 1. 计算比率 char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); double rate current * 100.0 / total; // 2. 填充进度字符 int cnt (int)(current * 100 / total); int i 0; for(i 0; i cnt; i) { buffer[i] STYLE; } // 动态光标循环切换提示程序运行中 const char* lable |/-\\; static int count 0; int len strlen(lable); // 3. 彩色打印进度条分颜色区分不同部分视觉更清晰 //printf([%-100s][%.1f%%][%c]\r, buffer, current * 100 / total, lable[count % len]); printf(COLOR_RED[%-100s], buffer); // 进度主体红色 printf(COLOR_BLUE[%5.1lf%%],rate); // 百分比蓝色 printf(COLOR_CYAN[%c],lable[count % len]); // 动态光标青色 printf(COLOR_YELLOW| %.1lf/%.1lf,speed: %.1lf%s\r,current,total,speed,userinfo); // 附加信息黄色 printf(COLOR_RESET); // 重置颜色避免污染后续输出 // 手动刷新缓冲区确保内容实时显示 fflush(stdout); count; }2.6 主函数模块main.c应用场景测试模拟多场景下载任务测试进度条的适配性和稳定性#include process.h #include stdio.h #include unistd.h #include stdlib.h #include time.h // 全局配置可根据实际场景修改 double total 1024.0; double speed 1.0; void Download(double total, flush_t cb) { double current 0.0; // 模拟不同网络速度模拟实际场景中速度波动 double level[] { 0.05, 0.50, 1.00, 10.0, 1.00, 16.0, 20.0, 12.0, 24.0, 1.00, 26.0, 38.0, 1.00, 41.0, 50.0, 1.00, 65.0 }; int num sizeof(level)/sizeof(level[0]); while(1) { // 模拟下载耗时0.5秒刷新一次 usleep(500000); // 随机选择当前速度模拟网络波动 speed level[rand() % num]; current speed; // 边界处理进度达到100%时终止 if(current total) { current total; // 确保进度不超过100% cb(total,current,speed,MB/s); break; } else{ // 进度条未满刷新进度条 cb(total,current,speed,MB/s); } } printf(\n); } int main() { // 初始化随机数种子模拟速度波动 srand(time(NULL)); // 测试4个不同大小的下载任务验证进度条适配性 printf(download: \n); Download(total, Process_version); printf(download: \n); Download(200.0, Process_version); printf(download: \n); Download(500.0, Process_version); printf(download: \n); Download(900.0, Process_version); return 0; }三、操作实践与效果展示实际操作过程[adminiZbp12ear9ufvimc78fddkZ processbar]$ ll total 20 -rw-rw-r-- 1 admin admin 1 May 2 10:10 code.c -rw-rw-r-- 1 admin admin 2763 May 20 00:17 main.c -rw-rw-r-- 1 admin admin 139 May 1 15:03 makefile -rw-rw-r-- 1 admin admin 3123 May 20 00:14 process.c -rw-rw-r-- 1 admin admin 570 May 20 00:01 process.h [adminiZbp12ear9ufvimc78fddkZ processbar]$ make gcc -c code.c gcc -c main.c gcc -c process.c gcc code.o main.o process.o -o process.exe [adminiZbp12ear9ufvimc78fddkZ processbar]$ ./process.exe download: [####################################################################################################][100.0%][-]| 1024.0/1024.0,speed: 16.0MB/s download: [####################################################################################################][100.0%][|]| 200.0/200.0,speed: 24.0MB/s download: [####################################################################################################][100.0%][/]| 500.0/500.0,speed: 20.0MB/s download: [####################################################################################################][100.0%][\]| 900.0/900.0,speed: 50.0MB/s执行 ./process_bar 后终端会输出 4 个下载任务的进度条每个部分颜色区分清晰红色进度主体# 填充部分蓝色百分比如50.0%青色动态光标 | / - \ 循环切换黄色附加信息当前进度 / 总进度、传输速度进度完成后自动换行后续任务不重叠整体流畅无错乱。注意其中动态光标只与调用的这个函数有关不管进度条动不动他都是得转动的。其它的优化大家也可以自己想想尝试一下。效果演示进度条结束语一个高质量的进度条不仅是功能的补充更是用户体验的提升。本文从回车换行、缓冲区两大底层知识点切入清晰拆解了 Linux 终端输出的核心原理在此基础上完整实现了彩色动态进度条项目。通过本案例既能吃透行缓冲、字符刷新等 IO 底层细节也能掌握小型项目的模块化开发思路。Linux 终端开发的魅力就在于此 —— 看似简单的功能背后藏着扎实的底层逻辑吃透这些细节才能写出更稳定、更优雅的代码。