【Linux 系统篇(十)】基础开发工具(五) —— 第一个系统程序 - 进度条
大家好欢迎来到 huangjin007_ 的博客⭐个人主页huangjin007_文章收录专栏Linux 内功修炼手册(系统篇)总会有一些坚持能从冰封的土地里培育出十万朵怒放的蔷薇Linux 系统篇(十) —— 第一个系统程序 - 进度条文章目录Linux 系统篇(十) —— 第一个系统程序 - 进度条一、回车与换行1.1 老式打字机1.2 计算机中的表示\r 和 \n二、行缓冲区2.1 三个现象对比2.2 缓冲区到底是啥三、练手程序一个简单倒计时四、进度条项目从朴素版本到工程化实现4.1 朴素进度条 V1原地刷新版4.2 更通用的进度条引入下载任务模拟4.3 模拟下载和上传任务4.4 工程化管理编写 Makefile五、完整代码汇总process.hprocess.cmain.cMakefile结语一、回车与换行在正式开始写进度条之前有一个绕不开的基础概念需要先讲清楚——回车Carriage Return和换行Line Feed。1.1 老式打字机故事要从很久以前的机械打字机说起。在那个年代打字机上有一个可以左右移动的“字车”上面装着纸张。当你敲击按键时字车会把纸张上的墨带打到纸上留下字符。回车打完一行字之后你需要把字车推回到最左边这就是“回车”。它对应一个动作把打印头移动到当前行的行首。换行如果你想开始写下一行就需要把纸张向上卷起一行这个动作叫做“换行”。它对应把纸张向上移动一行。在机械打字机上这两个动作是分开操作的。先回车再换行才能从一行的末尾顺利移动到下一行的开头。1.2 计算机中的表示\r和\n后来这些概念被带到了计算机和电传打字机中并成为了字符编码的一部分。在 C 语言中我们用转义字符来表示它们\r—— 代表回车功能是把光标移动到当前行的行首。\n—— 代表换行功能是把光标移动到下一行列位置不变。在 Linux 环境下编程时如果你单独使用\r光标就会回到当前行的开头但不会向下换行。这恰恰是实现进度条动态刷新的关键——我们可以反复在同一行的开头打印新的内容从而覆盖掉旧内容给用户造成一种“原地更新”的视觉感受。二、行缓冲区看下面三段代码猜猜它们的运行现象有什么不同。2.1 三个现象对比代码一带\n的 printf#includestdio.h#includeunistd.hintmain(){printf(hello Linux!\n);sleep(3);return0;}运行结果程序一开始就打印出hello Linux!然后等待 3 秒程序结束。代码二没有\n的 printf#includestdio.h#includeunistd.hintmain(){printf(hello Linux!);sleep(3);return0;}运行结果程序运行后屏幕上什么也没有一片空白等待 3 秒后在程序即将退出时才突然弹出hello Linux!。为什么会这样难道printf没有执行吗当然不是printf已经执行了但输出的内容并没有立刻显示到屏幕上而是被放到了一个叫“缓冲区”的地方。代码三不带\n但手动刷新缓冲区#includestdio.h#includeunistd.hintmain(){printf(hello Linux!);fflush(stdout);// 强制刷新标准输出缓冲区sleep(3);return0;}运行结果程序一开始就打印出hello Linux!然后才等待 3 秒。2.2 缓冲区到底是啥简单来说缓冲区Buffer是 C 语言标准库在内存中开辟的一块临时空间用来暂存待输出的数据。为什么要有缓冲区因为直接操作硬件比如显示器、磁盘是非常慢的。如果每次调用printf都立刻去写硬件程序的性能会非常差。标准库的做法是先把你要输出的数据收集起来等攒够一批或者遇到某些特定条件时再一次性写出去。对于标准输出stdout它的缓冲模式通常是行缓冲。也就是说遇到以下几种情况时缓冲区才会被“刷新”真正写到屏幕缓冲区满了。遇到换行符\n行缓冲的核心特征。程序正常结束return或exit系统会刷新所有缓冲区。手动调用fflush(stdout)强制刷新。等待输入时比如scanf有时也会触发刷新但这取决于实现不绝对。回到代码二的例子printf(hello Linux!);执行后字符串被写入了输出缓冲区但因为没有\n缓冲区没满程序也没结束所以它就一直待在内存里。紧接着sleep(3)让程序暂停了 3 秒这期间屏幕当然是一片空白。直到main函数执行return 0准备退出时系统才会自动清理并刷新缓冲区于是字符串才被打印出来。代码一因为带了\nprintf遇到换行符立即触发刷新所以字符串直接显示。代码三则是通过fflush(stdout)人工干预强制把缓冲区的数据推送到终端。三、练手程序一个简单倒计时有了\r和fflush的知识我们可以先写一个小例子巩固一下——一个在终端原地倒计时的程序。#includestdio.h#includeunistd.hintmain(){inti10;while(i0){printf(%-2d\r,i);// 用 \r 让光标回到行首fflush(stdout);// 立刻刷新否则看不到变化i--;sleep(1);// 每秒更新一次}printf(\n);// 倒计时结束后换行让命令行提示符正常显示return0;}解析%-2d格式化打印一个左对齐、宽度为 2 的整数。-表示左对齐2表示至少占两位。这样当数字从10变成9时9后面会多一个空格正好把原来10中个位的0覆盖掉不会留下残影。\r打印完数字后光标回到行首而不是换到下一行。这样下一次printf就会从行首开始打印覆盖掉上一次的数字。fflush(stdout)由于printf的格式串中没有\n行缓冲不会自动刷新所以必须手动强制刷新否则屏幕上始终不会有任何变化。sleep(1)让程序暂停 1 秒模拟倒计时的间隔。循环结束后单独打印一个\n倒计时完成时把光标移到下一行这样 shell 提示符会另起一行而不是直接黏在0后面。运行效果你会看到屏幕上数字从10逐步递减到0全程只在同一行原地变化不会刷出一屏新行。四、进度条项目从朴素版本到工程化实现掌握了倒计时的原理进度条的实现思路就非常清晰了我们也是用\r反复覆盖同一行只是这次显示的内容不是数字而是一个由字符拼接而成的进度条比如[### ][50%]并且还需要加一点小细节。下面我们从最简单的版本开始逐步演进到模块化、带回调函数、支持模拟下载和上传的完整工程。4.1 朴素进度条 V1原地刷新版我们先写一个最基本的进度条函数它从 0% 跑到 100%每次刷新一行。#includestdio.h#includeunistd.h#includestring.h#defineNUM101// 进度条总格数 1 (用于字符串结尾)#defineSTYLE// 进度条填充字符voidprocess(){charbar[NUM];memset(bar,0,sizeof(bar));// 全部初始化为 \0constchar*label|/-\\;// 旋转动画的字符序列intlenstrlen(label);intcount0;while(count100){// 用 %-100s 保证进度条字符串宽度固定为 100不足的部分用空格填充printf([%-100s][%d%%][%c]\r,bar,count,label[count%len]);fflush(stdout);bar[count]STYLE;// 每次往 bar 里多填一个 usleep(30000);// 休眠 30 毫秒控制进度速度count;}printf(\ndownload successfully!\n);}intmain(){process();return0;}代码详解bar[NUM]是一个大小为 101 的字符数组前 100 个位置用来放进度条字符最后一个位置始终为\0由memset保证。进度从 0% 开始时bar里全是空字符printf用%-100s打印时会输出 100 个空格因为空字符不会显示但宽度占用了。随着循环递增bar[count]被赋值为进度条逐渐被填满。%-100s-表示左对齐100表示字段最小宽度为 100。如果字符串长度不足 100剩余位置会用空格填充。label是一个包含 4 个字符的字符串|、/、-、\注意反斜杠在字符串中需要转义所以写成\\。每次循环取label[count % len]就能顺序循环播放这四个字符产生一个旋转小棒的效果用来提醒用户下载正在进行中。usleep(30000)让程序暂停 30000 微秒也就是 30 毫秒。因为sleep只能以秒为单位这里用usleep实现更精细的时间控制需要#include unistd.h。运行这个程序你会看到一个从[ ]逐渐变为[]的进度条后面跟着百分比和一个不停旋转的字符。4.2 更通用的进度条引入下载任务模拟上面的process()函数虽然能跑但它是“自娱自乐”型——进度条自己控制自己的进度没有和外部任何任务挂钩。实际应用中进度条应该反映某个真实任务的完成情况比如下载文件、处理数据等。所以我们把进度条的更新逻辑分离出来做成一个可以接收“当前进度”参数的函数。process.h头文件声明#pragmaonce#includestdio.hvoidprocess();voidFlushprocess(doubletotal,doublecurrent);这里我们声明了两个函数原有的process用于测试新函数Flushprocess用于接收总任务量和当前进度来动态刷新进度条。process.c实现#includeprocess.h#includestring.h#includeunistd.h#defineNUM101#defineSTYLEvoidFlushprocess(doubletotal,doublecurrent){charbar[NUM];memset(bar,0,sizeof(bar));constchar*label|/-\\;intlenstrlen(label);staticintcount0;// 这里把 count 声明为静态变量用来控制旋转动画// 计算已经填充的格子数current / total * 100 取整intnum(int)(current*100/total);// 填充 barfor(inti0;inum;i){bar[i]STYLE;}// 百分比doubleratecurrent/total;// 旋转动画的字符索引count%len;// 确保不越界printf([%-100s][%.1f%%][%c]\r,bar,rate*100,label[count]);count;fflush(stdout);}几个关键改动函数参数total和current分别表示任务总量和当前完成量单位可以根据实际场景定义这里我们内部统一用double保持通用性。static int count在Flushprocess被多次调用时count变量不会每次都被重新初始化为 0而是保留上一次的值。这样旋转动画才能连贯地播放下去。如果不加static每次调用count都会从 0 开始动画就跳来跳去。进度计算num (int)(current * 100 / total);算出当前进度占 100 格中的几格rate current / total算出百分比小数。输出格式[%.1f%%]%.1f保留一位小数%%在printf中用来输出一个百分号本身。4.3 模拟下载和上传任务现在Flushprocess准备好了我们需要写一个“任务函数”来模拟实际工作并不断调用Flushprocess来更新进度条。main.c使用回调函数解耦任务与进度显示#includeprocess.h#includeunistd.h#includestdio.h// 定义一个回调函数类型接收总量和当前量无返回值typedefvoid(*callback_t)(doubletotal,doublecurrent);doubletotal1024.0;// 模拟总数据量 1024MBdoublespeed1.0;// 每次循环增加 1MBvoidDownload(callback_tcb){doublecurrent0.0;while(currenttotal){cb(total,current);// 调用回调更新进度条usleep(3000);// 模拟网络延迟currentspeed;// 数据量增加}printf(\ndownload %.2lfMB Done\n,current);}voidUpload(callback_tcb){doublecurrent0.0;while(currenttotal){cb(total,current);usleep(3000);currentspeed;}printf(\nupload %.2lfMB Done\n,current);}intmain(){Download(Flushprocess);// 把 Flushprocess 作为回调传入Upload(Flushprocess);return0;}设计思路解析回调函数callback_t是一个函数指针类型指向一个形如void func(double, double)的函数。Download和Upload函数接受这样一个函数指针cb在循环体内调用cb(total, current)来通知“外界”当前进度是多少。至于这个“外界”怎么处理进度比如打印进度条、记录日志、还是显示在图形界面完全由调用者传入的具体函数决定。模拟任务total 1024.0speed 1.0每次循环current speed所以从 0MB 跑到 1024MB 需要 1024 次循环。usleep(3000)让每次循环暂停 3 毫秒整个模拟过程约 3 秒肉眼能清晰看到进度条的变化。main函数直接调用Download(Flushprocess)就把进度条显示函数“注入”到了下载任务中。上传任务同理。如果想换一种进度显示方式比如只打印百分比只需换一个回调函数即可完全不用修改Download和Upload的代码。4.4 工程化管理编写 Makefile当项目包含多个源文件时每次手动用gcc编译会很麻烦。我们写一个简单的 Makefile 来自动化构建。SRC $(wildcard *.c) # 获取当前目录所有 .c 文件 OBJ $(SRC:%.c%.o) # 将 .c 后缀替换成 .o BIN processbar # 最终生成的可执行文件名 $(BIN): $(OBJ) gcc -o $ $^ # 链接所有 .o 文件生成可执行程序 %.o: %.c gcc -c $ # 编译每个 .c 为 .o .PHONY: clean clean: rm -f $(BIN) $(OBJ) # 清理编译生成的文件Makefile 关键点wildcard函数自动匹配通配符获取文件列表。$(SRC:%.c%.o)模式替换把main.c、process.c变成main.o、process.o。$代表目标文件$^代表所有依赖文件$代表第一个依赖文件。这些是 Makefile 的自动变量可以简化书写。.PHONY: clean声明clean是一个伪目标即使当前目录存在一个名为clean的文件也依然执行 clean 命令。使用方法在终端进入项目目录执行make即可编译生成processbar。执行./processbar运行程序。想清理的话make clean。五、完整代码汇总process.h#pragmaonce#includestdio.hvoidprocess();voidFlushprocess(doubletotal,doublecurrent);process.c#includeprocess.h#includestring.h#includeunistd.h#defineNUM101#defineSTYLEvoidFlushprocess(doubletotal,doublecurrent){charbar[NUM];memset(bar,0,sizeof(bar));constchar*label|/-\\;intlenstrlen(label);staticintcount0;intnum(int)(current*100/total);for(inti0;inum;i){bar[i]STYLE;}doubleratecurrent/total;count%len;printf([%-100s][%.1f%%][%c]\r,bar,rate*100,label[count]);count;fflush(stdout);}voidprocess(){charbar[NUM];memset(bar,0,sizeof(bar));constchar*label|/-\\;intlenstrlen(label);intcount0;while(count100){printf([%-100s][%d%%][%c]\r,bar,count,label[count%len]);fflush(stdout);bar[count]STYLE;usleep(30000);count;}printf(\ndownload successfully!\n);}main.c#includeprocess.h#includeunistd.h#includestdio.htypedefvoid(*callback_t)(doubletotal,doublecurrent);doubletotal1024.0;doublespeed1.0;voidDownload(callback_tcb){doublecurrent0.0;while(currenttotal){cb(total,current);usleep(3000);currentspeed;}printf(\ndownload %.2lfMB Done\n,current);}voidUpload(callback_tcb){doublecurrent0.0;while(currenttotal){cb(total,current);usleep(3000);currentspeed;}printf(\nupload %.2lfMB Done\n,current);}intmain(){Download(Flushprocess);Upload(Flushprocess);return0;}MakefileSRC $(wildcard *.c) OBJ $(SRC:%.c%.o) BIN processbar $(BIN): $(OBJ) gcc -o $ $^ %.o: %.c gcc -c $ .PHONY: clean clean: rm -f $(BIN) $(OBJ)结语今天的内容到这里就结束了希望你能有所收获~干货整理到手抖觉得有用的话赏个三连回回血__(:ᗤ」ㄥ)_ _

相关新闻

GitNexus

GitNexus

GitNexus 能帮你搭一个本地、可持续更新的“微服务代码知识库”,并生成对 Copilot 很有价值的跨服务影响参考图谱。 Repo GitHub - abhigyanpatwari/GitNexus: GitNexus: The Zero-Server Code Intelligence Engine - GitNexus is a client-side knowledge graph …

2026/7/30 14:54:24 阅读更多 →
[特殊字符] CNSH × 龍魂系统 · 公开透明治理架构术语总表 v2.0 《Chinese-Native Sovereign Semantic Architecture》

[特殊字符] CNSH × 龍魂系统 · 公开透明治理架构术语总表 v2.0 《Chinese-Native Sovereign Semantic Architecture》

🐉 CNSH 龍魂系统 公开透明治理架构术语总表 v2.0 Chinese-Native Sovereign Semantic Architecture 🌌 系统声明 本体系不是黑箱AI、隐藏Prompt系统、不可解释Agent网络、覆盖式记忆框架、不透明人格系统。 而是**「公开透明语义治理系统」**。 核…

2026/7/30 14:54:24 阅读更多 →
终极Evernote笔记备份方案:5分钟掌握完整本地备份技术

终极Evernote笔记备份方案:5分钟掌握完整本地备份技术

终极Evernote笔记备份方案:5分钟掌握完整本地备份技术 【免费下载链接】evernote-backup Backup & export all Evernote notes and notebooks 项目地址: https://gitcode.com/gh_mirrors/ev/evernote-backup 你的Evernote笔记是珍贵的数字资产&#xff0…

2026/7/30 14:54:23 阅读更多 →

最新新闻

单片机毕设项目:基于单片机的药盒状态检测与语音播报系统 基于嵌入式硬件的多品类药品定时管理装置(012901)

单片机毕设项目:基于单片机的药盒状态检测与语音播报系统 基于嵌入式硬件的多品类药品定时管理装置(012901)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/7/30 15:03:33 阅读更多 →
单片机毕设项目:基于单片机的室内环境预警与家电自动调控装置 基于 STM32 的本地阈值配置与环境设备联动系统(012801)

单片机毕设项目:基于单片机的室内环境预警与家电自动调控装置 基于 STM32 的本地阈值配置与环境设备联动系统(012801)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/7/30 15:03:33 阅读更多 →
起猛了?凯哥用QQ2007写代码?

起猛了?凯哥用QQ2007写代码?

起猛了?凯哥用QQ2007写代码?原创:凯哥Java本文标签:Codex皮肤、QQ2007主题、AI编程工具美化、代码编辑器皮肤大家好,我是凯哥Java。先说个事儿。前两天我写代码写到凌晨两点,起身去客厅倒水。回来一坐下&am…

2026/7/30 15:03:33 阅读更多 →
【单片机毕业设计】基于 STM32 的 OLED 显示智能药盒控制系统开发 基于红外传感的智能用药提醒硬件系统设计(012901)

【单片机毕业设计】基于 STM32 的 OLED 显示智能药盒控制系统开发 基于红外传感的智能用药提醒硬件系统设计(012901)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/7/30 15:03:33 阅读更多 →
夸克微FS4059C规格书:NTC引脚20μA恒流源驱动的高/低温判断阈值(0.38V/1.43V)

夸克微FS4059C规格书:NTC引脚20μA恒流源驱动的高/低温判断阈值(0.38V/1.43V)

2026/7/30 15:03:33 阅读更多 →
Azure“交卷时刻”:微软如何用千亿资本开支跑通AI回报的正循环

Azure“交卷时刻”:微软如何用千亿资本开支跑通AI回报的正循环

2026年7月29日,微软交出了一份让市场彻底打消“AI烧钱恐慌”的财报。第四财季营收900.1亿美元,同比增长18%;Azure云业务收入同比增长43%,创下2022年初以来的最快增速。更关键的是,微软将2027财年资本支出预期从1900亿美…

2026/7/30 15:02:33 阅读更多 →

日新闻

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:13 阅读更多 →
如何3步掌握Video Download Helper:网页视频下载的完整实战指南

如何3步掌握Video Download Helper:网页视频下载的完整实战指南

如何3步掌握Video Download Helper:网页视频下载的完整实战指南 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 你是否曾经在浏览…

2026/7/30 0:00:13 阅读更多 →
“双减”后首个AI备课压力测试报告:覆盖32所中小学的176节AI辅助课,暴露4大隐性增负节点

“双减”后首个AI备课压力测试报告:覆盖32所中小学的176节AI辅助课,暴露4大隐性增负节点

更多请点击: https://intelliparadigm.com 第一章:AI 教师备课辅助 AI 教师备课辅助系统正逐步成为教育数字化转型的核心支撑工具,它并非替代教师,而是通过语义理解、知识图谱与多模态生成能力,将教师从重复性劳动中解…

2026/7/30 0:00:13 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/29 22:18:20 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻