《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.c)
2.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.c:
void 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 的编写策略,这里就不多说了直接展示
BIN=process.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; }三、操作实践与效果展示
实际操作过程:
[admin@iZbp12ear9ufvimc78fddkZ 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 [admin@iZbp12ear9ufvimc78fddkZ processbar]$ make gcc -c code.c gcc -c main.c gcc -c process.c gcc code.o main.o process.o -o process.exe [admin@iZbp12ear9ufvimc78fddkZ 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 终端开发的魅力就在于此 —— 看似简单的功能,背后藏着扎实的底层逻辑,吃透这些细节,才能写出更稳定、更优雅的代码。
