文章目录
- 前言
- 在这里插入图片描述
- 一、😶🌫️😶🌫️前置技能掌握😶🌫️😶🌫️
- 回车与换行- 2. 回车与换行在作文纸与老式打字机的例子- 3. 行缓冲区- - 行缓冲区: 解释- 1. 第一个程序:- 2. 第二个程序:- 3. 第三个程序:
- 二、😴😴第一个小程序——倒计时程序(练手)😴😴
- 三、❤️❤️进度条❤️❤️
- 进度条V1版本,功能展示- - 代码展示- 关键点解析- 2. 进度条V2通用版本——根据下载进度动态更新进度条- - 代码展示- 关键解析
前言
今天我们一起来看一看Linux下第一个程序:进度条🥰🥰🥰
我们先来看一下效果图😚😚😚
一、😶🌫️😶🌫️前置技能掌握😶🌫️😶🌫️
1. 回车与换行
回车(Carriage Return, 简称 CR)和换行(Line Feed, 简称 LF)是计算机文本处理中的两个控制字符,它们源于早期的打印机和打字机技术,分别代表两个不同的操作:
- 回车 (CR): 表示将打印头移回到行的开头,相当于不改变行的位置,只是光标回到当前行的起点。
- 换行 (LF): 表示将打印头移动到下一行,但不改变水平位置。
总结
- 回车是水平移动光标,换行是垂直移动光标。
- 两者在现代计算机中通常配合使用,具体实现方式依赖于操作系统的约定。
2. 回车与换行在作文纸与老式打字机的例子
对于这样一张作文纸~~~
其实这样的原理来自我们的老式打字机~~~
我们的键盘其实已经隐形的提示了,Enter键是一个向下再向左的样式~~~
3. 行缓冲区
行缓冲区: 解释
在Linux下,标准输出(
stdout
)的缓冲机制就像一个小水桶,专门用来装字符数据。这个水桶有点小任性:它只有在某些特定情况下才会把数据“倒出去”(也就是显示到终端)。具体来说,当:
- 水桶被装满(缓冲区满)。
- 遇到“换行符”(
\n
),就像按下了“冲水按钮”。 - 程序结束或者主动调用
fflush(stdout)
让它把水倒干净。
如果程序没有触发这些条件,水桶里的水(数据)就会乖乖地留在那里,暂时不会被“看见”。
1. 第一个程序:
#include<stdio.h>intmain(){printf("hello bite!\n");sleep(3);return0;}
现象:程序运行后,立即看到
hello bite!
输出,随后程序暂停 3 秒。
原因:这里的输出字符串以
\n
换行符结尾,触发了行缓冲区的“冲水按钮”。在
printf
后,缓冲区的内容被及时刷新到屏幕,所以字符串被立即显示。
2. 第二个程序:
#include<stdio.h>intmain(){printf("hello bite!");sleep(3);return0;}
现象:程序运行后,什么也看不到,等 3 秒后,
hello bite!
才会出现在屏幕上。
原因:这里的
printf
输出没有换行符
\n
,行缓冲区没有被触发,所以数据被留在缓冲区中,没有及时输出。直到程序结束时,系统自动刷新缓冲区,才将
hello bite!
显示出来。
3. 第三个程序:
#include<stdio.h>intmain(){printf("hello bite!");fflush(stdout);sleep(3);return0;}
现象:程序运行后,
hello bite!
立即显示,然后程序暂停 3 秒。
原因:这里调用了
fflush(stdout)
,相当于主动按下了“冲水按钮”,强制刷新了缓冲区内容,所以字符串被立即显示,无需等待缓冲区满或者程序结束。
总结:结合缓冲区讲现象
- 带有
\n
的输出:触发行缓冲机制,数据立即刷新。 - 没有
\n
且无fflush
:数据留在缓冲区,直到程序结束。 - 使用
fflush(stdout)
:立即刷新缓冲区,强制输出。
现象背后的原理:缓冲机制的设计目的是提高效率,减少频繁的 I/O 操作,但它会导致某些输出延迟。通过换行符、缓冲区满、程序结束或者
fflush
调用,可以触发缓冲区刷新,避免延迟显示。
二、😴😴第一个小程序——倒计时程序(练手)😴😴
#include<stdio.h>#include<unistd.h>// for sleep()intmain(){int i =10;while(i >=0){printf("%-2d\r", i);// 打印 i,左对齐,占 2 宽,光标回到行首fflush(stdout);// 强制刷新缓冲区
i--;// i 自减sleep(1);// 暂停 1 秒}printf("\n");// 最后换行,避免光标留在行首return0;}
运行现象
- 程序开始后,屏幕显示倒计时,从
10
开始,每秒递减,逐步显示10 9 8 ... 0
。 - 数字 总是在同一行显示,没有产生多行输出。
- 最终倒计时结束时,输出
0
,并在printf("\n")
处换行。
关键代码行为解释
%-2d
格式说明:-%-
表示左对齐。-2
表示宽度为 2 个字符。如果数字小于 2 个字符宽,会用空格填充。- 例如:10
显示为10
,而9
显示为9<space>
。\r
的作用:-\r
是回车符(Carriage Return),将光标移回到行首,不换行。- 每次新数字输出后,光标重新回到行首,覆盖之前的数字。fflush(stdout)
的作用:- 因为标准输出是行缓冲模式(遇到换行符或缓冲区满才会刷新),这里使用fflush(stdout)
强制刷新缓冲区,确保数字立即显示,而不是留在缓冲区中等待。sleep(1)
的作用:- 暂停程序 1 秒,形成明显的倒计时效果。- 最后的换行:- 倒计时结束后,
printf("\n")
输出换行符,确保光标移动到下一行,避免数字0
和终端提示符(例如$
)在同一行。
三、❤️❤️进度条❤️❤️
1. 进度条V1版本,功能展示
该函数用于展示一个简单的动态进度条,形式如下:
代码展示
[######......][50%][旋转符号]
它模拟任务进度的加载效果,显示进度百分比以及动态旋转符号(|,/,-, \)
//progress.h#pragmaonce #include<stdio.h>// 版本1voidprocess();
//progress.cvoidprocess(){// 进度条样子 [######...][n%][旋转|] int rate =0;// 统计进度百分比,初始值为 0。char buffer[SIZE];// 缓冲区,用于存储进度条的字符内容。memset(buffer,0,sizeof(buffer));// 初始化 buffer 内容为 `\0`,清空缓冲区,方便逐步更新进度条。constchar* lable ="|/-\\";// 定义旋转符号,共 4 种状态,用于动态效果。int len =sizeof(lable);// 计算旋转符号数组的长度,方便取模循环使用。// 开始循环输出进度条while(rate <=100)// 进度条从 0% 加载到 100%。{printf("[%-100s][%d%%][%c]\r", buffer, rate, lable[rate % len]);// [%-100s]:显示进度条,左对齐,占 100 格。// [%d%%]:显示当前百分比,`%%` 输出一个 `%`。// [%c]:动态旋转符号,利用 `%` 取模实现循环状态切换。// `\r`:回车符,让光标回到行首以覆盖之前的输出。fflush(stdout);// 强制刷新标准输出,立即显示内容。
buffer[rate]= STYLE;// 使用一个符号(`STYLE`)填充进度条,例如:`#`。
rate++;// 进度增加 1%。usleep(100000);// 模拟任务耗时,暂停 100 毫秒。}printf("\n");// 输出换行,防止进度条被终端光标覆盖。}
代码片段:
voidprocess(){// 进度条样子 [######...][n%][旋转|] int rate =0;// 统计进度百分比,初始值为 0。char buffer[SIZE];// 缓冲区,用于存储进度条的字符内容。memset(buffer,0,sizeof(buffer));// 初始化 buffer 内容为 `\0`,清空缓冲区,方便逐步更新进度条。constchar* lable ="|/-\\";// 定义旋转符号,共 4 种状态,用于动态效果。int len =sizeof(lable);// 计算旋转符号数组的长度,方便取模循环使用。// 开始循环输出进度条while(rate <=100)// 进度条从 0% 加载到 100%。{printf("[%-100s][%d%%][%c]\r", buffer, rate, lable[rate % len]);// [%-100s]:显示进度条,左对齐,占 100 格。// [%d%%]:显示当前百分比,`%%` 输出一个 `%`。// [%c]:动态旋转符号,利用 `%` 取模实现循环状态切换。// `\r`:回车符,让光标回到行首以覆盖之前的输出。fflush(stdout);// 强制刷新标准输出,立即显示内容。
buffer[rate]= STYLE;// 使用一个符号(`STYLE`)填充进度条,例如:`#`。
rate++;// 进度增加 1%。usleep(100000);// 模拟任务耗时,暂停 100 毫秒。}printf("\n");// 输出换行,防止进度条被终端光标覆盖。}
关键点解析
- **动态旋转符号 (
lable
)**:- 动态效果通过rate % len
实现,rate
每次递增,而len
为 4(|/-\
的长度),rate % len
保证每次选取不同的旋转符号,形成循环动态效果。 - **缓冲区
buffer
**:- 逐步填充进度条,每次填入一个符号(如#
),对应当前进度。-buffer[rate] = STYLE
,将进度条当前位置填充为STYLE
(如#
)。 - 输出控制:-
printf("[%-100s][%d%%][%c]\r", buffer, rate, lable[rate % len])
: -[%-100s]
:保证进度条左对齐,预留 100 格空间。-[%d%%]
:显示当前进度的百分比。-[%c]
:动态旋转符号。-\r
:回车符,将光标回到行首,覆盖之前的内容,避免多行输出。 - **刷新输出 (
fflush(stdout)
)**:-printf
输出是行缓冲模式,通常需要换行符\n
或缓冲区满才会刷新到终端。这里使用fflush(stdout)
强制刷新输出,确保进度条每次更新后能立即显示。 - 时间控制:-
usleep(100000)
暂停程序 100 毫秒,模拟任务耗时,制造进度加载的效果。
运行效果
- 初始输出:
[....................................................................................................][0%][|]
- 随着循环,进度条逐步加载:
[#####...............................................................................................][5%][/]
- 最终显示:
[####################################################################################################][100%][|]
- 换行后,结束程序。
2. 进度条V2通用版本——根据下载进度动态更新进度条
代码展示
**文件 1:
progress.h
**
#pragmaonce#include<stdio.h>#include<string.h>#defineSIZE101// 缓冲区大小(进度条长度 + 1)#defineSTYLE'#'// 进度条填充字符// 进度条 V1:简单进度条功能voidprocess();// 进度条 V2:动态更新进度条voidFlushProcess(constchar* tips,double total,double current);
**文件 2:
progress.c
**
#include"progress.h"#include<string.h>#include<unistd.h>#include<stdlib.h>#include<stdio.h>#defineSIZE100#defineSTYLE'#'voidFlushProcess(constchar* tips,double total,double current){constchar* lable ="|/-\\";int len =sizeof(lable);staticint index =0;// 定义静态变量,外部每一次调用FlushProcess,lable都会旋转char buffer[SIZE];// 初始化buffermemset(buffer,0,sizeof(buffer));double rate = current *100.0/ total;// 当前下载进度,是一个百分数,因此要乘100int num =(int)rate;// 进度条'#'的数量就是ret取整int i =0;for(; i < num; i++){
buffer[i]= STYLE;// 将进度条大小加载入buffer}// 进行输出printf("[%s][%-100s][%.1lf%%][%c]\r", tips, buffer, rate, lable[index++]);fflush(stdout);
index = index % len;if(num >=100)printf("\n");}
**文件 3:
main.c
**
#include<stdio.h>#include<time.h>#include<unistd.h>#include<stdlib.h>#include"progress.h"// 定义函数指针,以便于自定义回调函数typedefvoid(*call_t)(constchar*,double,double);int total =1024;// 定义总下载量double speed[]={1,5,0.3,0.5,0.03,0.01};// 设计下载速度,用这几个double模拟网速的变化// 下载,回调函数voiddownload(int total, call_t cb)// total 下载总量,cb刷新策略{srand(time(NULL));// 设计随机数种子double current =0.0;// 设置当前下载量,初始化为0while(current <= total){cb("下载中...", total, current);// 进行函数回调,进度条刷新策略if(current >= total)break;// 下载代码usleep(1500);// 模拟下载过程int len =sizeof(speed);int random =rand()% len;// 设置随机数current += speed[random]; // 下载对应网速的大小
current += speed[random];// 下载对应网速的大小if(current > total) current = total;// 如果下载到最后直接超过了100%的话,就重新设置一下current以便于加载出100%的状态}}// 上传,回调函数voiduploadload(int total, call_t cb)// total 下载总量,cb刷新策略{srand(time(NULL));// 设计随机数种子double current =0.0;// 设置当前下载量,初始化为0while(current <= total){cb("上传中...", total, current);// 进行函数回调,进度条刷新策略if(current >= total)break;// 上传代码usleep(1500);// 模拟上传过程int len =sizeof(speed);int random =rand()% len;// 设置随机数
current += speed[random];// 下载对应网速的大小if(current > total) current = total;// 如果下载到最后直接超过了100%的话,就重新设置一下current以便于加载出100%的状态}}intmain(){// process();download(1024.0, FlushProcess);printf("download 1024.0MB done\n");download(512.0, FlushProcess);printf("download 512.0MB done\n");download(256.0,FlushProcess);printf("download 256.0MB done\n");download(128.0,FlushProcess);printf("download 128.0MB done\n");download(64.0,FlushProcess);printf("download 64.0MB done\n");uploadload(256.0,FlushProcess);printf("uploadload 256.0MB done\n");uploadload(128.0,FlushProcess);printf("uploadload 128.0MB done\n");return0;}
运行效果
- 进度条动态更新:
[下载中...][##############........................................................][50.0%][|]
- 任务完成提示:
download 1024.0MB doneuploadload 256.0MB done
关键解析
1. 数组模拟网速
在代码中,
speed[]
数组用来模拟不同的网速。每个数组元素表示一种下载速率,比如
1 MB/s
、
5 MB/s
、
0.03 MB/s
等。
double speed[]={1,5,0.3,0.5,0.03,0.01};// 模拟下载速率
通过这种方式,您可以在每次下载时随机选择一个速率值,从而模拟网速波动,使下载过程更具真实感。比如,下载一段时间后网速可能突然加快或减慢,这个随机的变化通过
rand()
函数来实现:
int random =rand()% len;// 随机选择一个下载速度
current += speed[random];// 根据随机选中的网速更新当前下载量
这样,下载的进度就会根据随机选择的网速变化,避免了单调的、恒定的下载速度。
2. 函数指针实现回调
使用函数指针可以让程序更加灵活,特别是在进度条更新的策略上。
download
函数接受一个函数指针
call_t
,这意味着您可以在调用
download
时传入不同的回调函数,以改变进度条显示的方式。
typedefvoid(*call_t)(constchar*,double,double);// 定义函数指针类型
当下载进行时,您会调用回调函数:
cb("下载中...", total, current);// 回调函数,用于更新进度条
通过回调函数,您可以在不同的场景下使用不同的显示策略,比如显示不同的文字、更新不同的进度条样式等。
3. 静态变量的妙用
static
关键字用来定义静态变量,使得变量在函数多次调用时保持其值。在这里,
index
被声明为静态变量:
staticint index =0;// 用来控制旋转符号的显示
每次调用
FlushProcess
时,
index
的值都会记住上次的状态,从而让旋转符号持续变化。例如,
index
的值会在
0
到
3
之间循环,控制显示的旋转符号(
|
,
/
,
-
,
\
):
lable[index++]// 循环使用旋转符号
index = index % len;// 循环回到0
index
保持其状态直到程序结束,这使得旋转符号能够平滑、连续地显示,而不需要重新计算。
4. 输出格式与刷新显示
为了让进度条动态更新,输出格式采用了左对齐和
\r
回车符。
%-100s
确保了进度条总是左对齐并占据 100 个字符的位置,即使进度条还没有完全填满 100 个字符,它也会在同一行内保持输出。
printf("[%s][%-100s][%.1lf%%][%c]\r", tips, buffer, rate, lable[index++]);
- **
\r
**:让输出光标返回行首,这样每次打印新的进度条时,它会覆盖掉之前的内容,而不是换行。这样就能保证进度条始终在同一行上更新。 - **
fflush(stdout)
**:每次更新后调用fflush(stdout)
,强制刷新输出缓冲区,确保进度条在每次更新时即时显示。
到这里,我们Linux第一个有意思的程序就写完啦!!!
创作不易,如果对各位有帮助的,求各位一个一键三连,谢谢大佬们🥰🥰🥰
版权归原作者 小柯J桑_ 所有, 如有侵权,请联系我们删除。