
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux操作系统从入门到实践》《Qt从入门到实践》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
[一、两个储备知识:回车换行 / 缓冲区](#一、两个储备知识:回车换行 / 缓冲区)
[1.1 回车(\r)与换行(\n)的本质区别](#1.1 回车(\r)与换行(\n)的本质区别)
[1.2 深入理解行缓冲区运行机制](#1.2 深入理解行缓冲区运行机制)
[1.3 进度条的核心构成元素详解](#1.3 进度条的核心构成元素详解)
[2.1 进度条测试版本(演示原理)](#2.1 进度条测试版本(演示原理))
[2.2 基础版进度条模拟实现(无拓展功能实现)](#2.2 基础版进度条模拟实现(无拓展功能实现))
[2.3 自动化构建流程:Makefile编写](#2.3 自动化构建流程:Makefile编写)
[2.4 头文件设计(process.h):接口函数声明](#2.4 头文件设计(process.h):接口函数声明)
[2.5 核心实现文件(process.c)](#2.5 核心实现文件(process.c))
[2.6 主函数模块(main.c):应用场景测试](#2.6 主函数模块(main.c):应用场景测试)
一、两个储备知识:回车换行 / 缓冲区
在动手写代码前,必须先理清 2 个关键概念,否则容易出现 "进度条不刷新""换行错乱" 等问题。
1.1 回车(\r)与换行(\n)的本质区别
- 换行(
\n) :光标移动到下一行的行首 ,但不会回到当前行开头 ;我们日常使用它的时候其实是 回车+换行 的作用(=/r/n ) - 回车(
\r) :光标回到当前行的行首 ,但不会移动到下一行; - 进度条的核心 是 "在同一行反复覆盖刷新",因此必须用 \r让光标回到行首,再重新打印新的进度信息。
1.2 深入理解行缓冲区运行机制
C语言的 printf 函数默认是**"行缓冲"** ------只有遇到**\n** 、缓冲区满 或**手动刷新(fflush(stdout))**时,才会把缓冲区的内容输出到终端。
示例:
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello world!");
sleep(1);
return 0;
}

- 如果只写printf 而不加\n或fflush,内容会一直存在缓冲区,终端看不到任何输出,这就是很多人写进度条 "没反应" 的原因。
注意:虽然没显示出来,但是我们的C语言默认是顺序结构的,一定是先执行 printf 再执行 sleep的。
示例修正:
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello world!");
fflush(stdout);
sleep(1);
return 0;
}

练练手:光标快速回退,完成倒计时功能
cpp
#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:
cpp
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 基础版进度条模拟实现(无拓展功能实现)
cpp
//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 的编写策略,这里就不多说了直接展示
bash
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):接口函数声明
定义进度条回调函数类型和核心接口,方便后续扩展和复用:
cpp
#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)
集成颜色控制、进度计算、动态刷新,是进度条的核心:
cpp
#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):应用场景测试
模拟多场景下载任务,测试进度条的适配性和稳定性:
cpp
#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;
}
三、操作实践与效果展示
实际操作过程:
cpp
[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 终端开发的魅力就在于此 ------ 看似简单的功能,背后藏着扎实的底层逻辑,吃透这些细节,才能写出更稳定、更优雅的代码。