

🔥@雾忱星: 个人主页
👀专栏:《C++学习之旅》、《Linux学习指南》
💪学习阶段:C/C++、Linux
⏳"人理解迭代,神理解递归。"
文章目录
- 引言
- 一、核心知识储备:回车换行与缓冲区
-
- [1.1 解疑:回车和换行别再分不清](#1.1 解疑:回车和换行别再分不清)
- [1.2 缓冲区:回车换行的潜在作用 ------ 刷新缓冲区](#1.2 缓冲区:回车换行的潜在作用 —— 刷新缓冲区)
- 二、进度条实战:从基础到彩色进阶
-
- [2.1 自动化构建:适配多文件的 makefile 编写](#2.1 自动化构建:适配多文件的 makefile 编写)
- [2.2 基础版本:进度条核心原理(多文件协作实现)](#2.2 基础版本:进度条核心原理(多文件协作实现))
- [2.3 进阶版本:打造彩色进度条](#2.3 进阶版本:打造彩色进度条)
-
- [2.3.1 头文件设计:函数接口声明](#2.3.1 头文件设计:函数接口声明)
- [2.3.2 测试源文件:模拟下载场景实现](#2.3.2 测试源文件:模拟下载场景实现)
- [2.3.3 核心源文件:彩色进度条逻辑实现](#2.3.3 核心源文件:彩色进度条逻辑实现)
- 三、最终成果展示与操作演示
- 总结
引言
Linux系统编程的学习,离不开理论与实操的结合 。本次进度条小实战,并非孤立的功能开发,而是对前期所学知识的综合复盘与落地运用 。
我们将依托缓冲区机制、回车换行原理、多文件编程、Makefile自动化构建等前置知识点,一步步搭建可运行的动态进度条,把零散的语法、底层逻辑串联成完整的开发链路,夯实Linux环境下编程的实操能力。
一、核心知识储备:回车换行与缓冲区
在进行本篇的的主要任务之前,需要补充几个关键知识点,为第二节内容打下基础。
1.1 解疑:回车和换行别再分不清
在前面 C语言、C++ 的学习,你对于**"回车换行"**这个概念肯定不陌生。比如 C语言 的\n,C++ 的std::endl,这两个换行控制符都起到了回车换行的作用。但是,你可能不知道,"回车","换行"其实是两个行为。
回车\r: 光标只在当前位置的垂直方向移动,水平位置不变;
换行\n: 光标只回到本行的最开头,垂直位置不变;
在 C语言 和 C++中,将"回车"、"换行"这两种行为综合到了一起。
1.2 缓冲区:回车换行的潜在作用 ------ 刷新缓冲区
📖 【"药引子"】:体会回车换行的潜在的作用------刷新缓冲区
- 版本一: 有
\n\r
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello world\n\r");
sleep(1); //休眠函数
return 0;
}
显示结果 :先输出
hello world,再休眠。
- 版本二: 没有
\n\r
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello world");
sleep(1); //休眠函数
return 0;
}
显示结果 :先休眠,再输出
hello world。
🔦 为什么不加回车换行就输出延迟?------存在缓冲区
如果在此时前不了解缓冲区,就会误以为上面的情况是先执行的休眠在输出。实则不然,执行顺序已经定好了就不会改变。
C语言的缓冲区就好比购物车 ,购物时是先将想买的物品放到购物车,最后在一次性完成。缓冲区的原理就是如此,它就是内存中的一块缓存区域,目的是减少I/O操作,提高效率。
对于printf函数,只有在遇到缓冲区满或者\n\r换行符才会立即将内容刷新到显示器上。这也就是版本二看着像是先执行的休眠后执行的输出的原因。
【刷新缓存区方法】:
- 默认程序结束时刷新缓存区;
- 在
printf函数加上回车换行符\n\r,主要是换行\n; - 使用
fflush(stdout)函数强制刷新;
【综合练手】:倒计时
c
#include <stdio.h>
#include <unistd.h>
int main()
{
int time = 10; //倒计时从10开始
for(;time>=0;time--) //循环time-1
{
printf("倒计时:%-2d\r", time);//注意域宽覆盖0,回车到开头不需要换行
sleep(1); //休眠,防止程序直接结束
fflush(stdout); //刷新缓存区,立即显示
}
return 0;
}
🔍为什么设置域宽为2?
不设置会导致从9开始回车只会覆盖10的"1"而"0"则会一直呆着,就会变成90,80......
二、进度条实战:从基础到彩色进阶
准备进行多文件编写,为了方便编译,重新写一遍 makefioe文件实现自动化构建。
2.1 自动化构建:适配多文件的 makefile 编写
在前面已经学习过如何编写一个完整的makefile文件,这里只是对前版本进行修改适配现在的多文件。
bash
Bin=process_bar #进度条文件
Src=$(wildcard *.c) #获取源文件
Obj=$(Src:.c=.o) #替换后缀
cc=gcc #编译工具
$(Bin):$(Obj)
@echo "$^ Link To $@"
@$(cc) -o $@ $^
%.o:%.c
@echo "Compling $< To $@"
@$(cc) -c $<
#清理项目
.PHONY:clean
clean:
@echo "File Clean... Done"
@rm -f $(Bin) $(Obj)
.PHONY:Print
Print:
@echo $(Bin)
@echo $(Obj)
@echo $(Src)
@echo $(cc)
2.2 基础版本:进度条核心原理(多文件协作实现)
多文件协作:main.c/process.c/process.h三个文件,process.c/.h文件实现进度条项目。
c
#include "process.h"
#include <unistd.h>
#include <string.h>
#define SIZE 102
#define LABLE '=' //定义宏,显示进度
void Process()
{
int cnt = 0; //进度
char out_bar[SIZE]; //存储图形
const char *lable = "|/-\\";
int len = strlen(lable); //计算提示图案的长度
memset(out_bar, '\0', sizeof(out_bar)); //初始化数组out_bar
while(cnt<=100)
{
printf("[%-100s][%3d%%][%c]\r", out_bar, cnt, lable[cnt%len]);
fflush(stdout); //刷新缓冲区
out_bar[cnt] = LABLE; //设置数组,进度可视化
cnt++;
usleep(50000);
}
printf("\r\n");
}
【头文件】:
#include "process.h":自定义头文件,包含实现的函数接口的声明;
#include <unistd.h>:使用函数usleep,进行程序休眠,更好体现进度条的推进;
#include <string.h> :使用函数strlen计算图标的长度,memset预先设置数组内容;
【逻辑实现】:
存储图形数组预先全部设置为\0,无需再注意字符串结尾问题。进度为100,在循环中完成进度条的推进。循环中,每次循环向数组填充一个图形,再刷新缓冲区,回车进行下一次输出,营造整体推进的感觉。
【细节处理】:
- 定义宏
SIZE,设置为102大小。进度为100,加上\0,整体为101,设置为102为安全冗余设计; - 最后进度条完成推进,仍要回车换行,保证图形完整;
🔍 效果演示:

2.3 进阶版本:打造彩色进度条
2.3.1 头文件设计:函数接口声明
c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
//void Process();
//自定义函数别名,方便调用
typedef void(*flush_t)(double total, double current, double speed, const char *userinfo);
//下载接口
void Download(double total, flush_t cb);
void Process(double total, double current, double speed, const char *userinfo);
【头文件】:
#include <time.h> :使用time函数获取系统时间,升华成随机种子;
#include <stdlib.h>:设置随机数,模拟网速的波动;
【细节处理】:
- 自定义回调函数别名
typedef void(*flush_t),方便其他版本的进度条函数应用,移植性较强; - 声明下载函数接口,主要应用场景在该函数实现,并测试进度条函数;
2.3.2 测试源文件:模拟下载场景实现
c
#include "process.h"
//主要目的是下载文件
void Download(double total, flush_t cb)
{
double current = 0.0; //当前下载量
double level[] = {0.01, 0.05, 1.0, 0.5, 1.5, 2.5, 5.5, 10.0, 3.6, 20.0, 30.0, 40.0, 50.0 , 68.9, 89.1, 40.9, 30.1};//网速梯度
int num = sizeof(level) / sizeof(level[0]); //计算网速总数
//只要下载未完成,进度条就推进
while(1)
{
usleep(50000); //进度条刷新时间
double speed = level[rand()%num]; //取余保证在网速梯度中选择
current += speed; //当前下载量根据网速决定递增
if(current >= total)
{
current = total;
cb(total, current, speed, "MB/s"); //调用进度条函数
break; //跳出,防止重复刷新
}
else
{
cb(total, current, speed, "MB/s"); //调用进度条函数
}
}
}
int main()
{
// ProcessVersion1();
srand(time(NULL)); //时间种子
printf("任务下载1:\r\n");
Download(gtotal, Process);
printf("任务下载完成!\r\n");
printf("任务下载2:\r\n");
Download(102.0, Process);
printf("任务下载完成!\r\n");
printf("任务下载3:\r\n");
Download(72.4, Process);
printf("任务下载完成!\r\n");
printf("任务下载4:\r\n");
Download(789.2, Process);
printf("任务下载完成!\r\n");
return 0;
}
【应用实践】: 下载场景
- 函数中需要定义网速梯度 ,模拟网速波动。
- 若当前下载量小于总量,调用进度条函数,需要将参数(下载总量、当前下载量、网速、提示信息)传过去。反之直接输出最后完整的进度条,
2.3.3 核心源文件:彩色进度条逻辑实现
c
#include "process.h"
#define SIZE 102 //进度条长度
#define LABEL '=' //进度条填充字符
void Process(double total, double current, double speed, const char *userinfo)
{
//超出总量,直接结束下载,代表完成
if(current > total)
{
return;
}
//旋转光标:只和进度条函数有没有被调用有关
static const char *label = "|/-\\";
int size = strlen(label);
static int index = 0; //保证lable始终在有效范围内
//1.计算下载比率
double rate = current * 100.0 / total; //占比,不满1.0%不推进
char out_bar[SIZE]; //进度条数组
memset(out_bar, '\0', sizeof(out_bar)); //初始化数组
//2.填充进度条字符
int i = 0;
for(; i < (int)rate; i++)
{
out_bar[i] = LABEL;
}
// ========== 新增ANSI颜色控制序列 ==========
// 绿色进度条主体,黄色百分比,蓝色文件大小,青色网速,重置颜色
const char *COLOR_BAR = "\033[32m"; // 进度条填充字符 -> 绿色
const char *COLOR_RATE = "\033[33m"; // 进度百分比 -> 黄色
const char *COLOR_SIZE = "\033[34m"; // 已下载/总大小 -> 蓝色
const char *COLOR_SPEED = "\033[36m"; // 网速信息 -> 青色
const char *COLOR_RESET = "\033[0m"; // 重置终端颜色,防止污染后续输出
const char *COLOR_CURSOR = "\033[35m"; // 旋转光标 -> 紫色
//3.输出进度条
//printf("[%-100s][%5.1lf][%.2lf/%.2lf][speed:%.2lf %s][%c]\r", out_bar, rate, current, total, speed, userinfo, lable[index]);
//3.输出进度条(仅修改printf的颜色拼接,格式、参数完全不变)
printf("[%s%-100s%s][%s%5.1lf%%%s][%s%.2lf/%.2lf%s][speed:%s%.2lf %s%s][%s%c%s]\r",
COLOR_BAR, out_bar, COLOR_RESET, // 彩色进度条
COLOR_RATE, rate, COLOR_RESET, // 彩色百分比
COLOR_SIZE, current, total, COLOR_RESET, // 彩色文件大小
COLOR_SPEED, speed, userinfo, COLOR_RESET, // 彩色网速
COLOR_CURSOR, label[index], COLOR_RESET); // 彩色旋转光标
index++;
index %= size;
fflush(stdout);
//4. 进度条完成,换行
if(current >= total)
{
printf("\r\n");
index = 0; //重置旋转光标状态
}
}
【进阶改动】: 对基础版进行改动
- 计算下载比率,显示当前下载进度,图形填充改为根据比率进行(每次都重更新填充);
- 增加彩色显示,便于区分;
【细节处理】:
- 格式输出
%%转义输出一个%(也可以\%); - 完成一个任务下载,立即将旋转光标状态重置,使得下载连贯;
- 我使用的Linux终端,是C89 标准,不支持在循环条件中定义变量,需要注意;
【彩色输出】: 大家可以通过AI工具获取彩色输出格式。
三、最终成果展示与操作演示
【操作示范】:
bash
[tac@VM-0-6-centos lesson12]$ ll
total 16
-rw-rw-r-- 1 tac tac 1573 Feb 6 16:38 main.c
-rw-rw-r-- 1 tac tac 396 Feb 5 21:13 makefile
-rw-rw-r-- 1 tac tac 3030 Feb 6 00:06 process.c
-rw-rw-r-- 1 tac tac 383 Feb 5 22:51 process.h
[tac@VM-0-6-centos lesson12]$ make
Compling main.c To main.o
Compling process.c To process.o
main.o process.o Link To process_bar
[tac@VM-0-6-centos lesson12]$ ./process_bar
任务下载1:
[====================================================================================================][100.0%][1024.00/1024.00][speed:30.00 MB/s][/]
任务下载完成!
任务下载2:
[====================================================================================================][100.0%][102.00/102.00][speed:89.10 MB/s][/]
任务下载完成!
任务下载3:
[====================================================================================================][100.0%][72.40/72.40][speed:68.90 MB/s][\]
任务下载完成!
任务下载4:
[====================================================================================================][100.0%][789.20/789.20][speed:40.00 MB/s][/]
任务下载完成!
[tac@VM-0-6-centos lesson12]$ make clean
File Clean... Done
在终端演示中:
- 呈现绿色:进度条部分 "==" ;
- 呈现黄色:下载比率部分 " ...... % " ;
- 呈现蓝色:下载量部分 " ...... / ...... ";
- 呈现青色:网速部分 " ...... MB / s " ;
- 呈现紫色:旋转光标部分 " | / - \ ";
【动态演示】

总结
html
🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:
这场轻量化实战,完美承接了前期Linux系统编程的核心知识点,通过进度条开发完成了知识闭环。从底层缓冲区操控,到多文件协作、编译脚本编写,再到进阶彩色效果实现,既巩固了旧知,也培养了工程化开发思维。后续可基于这套框架继续拓展功能,为更复杂的系统编程项目打下扎实基础。
