0


Linux:指尖上的进度,流转如诗的进度条

在这里插入图片描述

文章目录

  • 前言
    • 在这里插入图片描述 在这里插入图片描述
  • 一、😶‍🌫️😶‍🌫️前置技能掌握😶‍🌫️😶‍🌫️
      1. 回车与换行- 2. 回车与换行在作文纸与老式打字机的例子- 3. 行缓冲区- - 行缓冲区: 解释- 1. 第一个程序:- 2. 第二个程序:- 3. 第三个程序:
  • 二、😴😴第一个小程序——倒计时程序(练手)😴😴
  • 三、❤️❤️进度条❤️❤️
      1. 进度条V1版本,功能展示- - 代码展示- 关键点解析- 2. 进度条V2通用版本——根据下载进度动态更新进度条- - 代码展示- 关键解析

前言

今天我们一起来看一看Linux下第一个程序:进度条🥰🥰🥰
我们先来看一下效果图😚😚😚

在这里插入图片描述

在这里插入图片描述

一、😶‍🌫️😶‍🌫️前置技能掌握😶‍🌫️😶‍🌫️

1. 回车与换行

回车(Carriage Return, 简称 CR)和换行(Line Feed, 简称 LF)是计算机文本处理中的两个控制字符,它们源于早期的打印机和打字机技术,分别代表两个不同的操作:

  1. 回车 (CR): 表示将打印头移回到行的开头,相当于不改变行的位置,只是光标回到当前行的起点。
  2. 换行 (LF): 表示将打印头移动到下一行,但不改变水平位置。

总结

  • 回车是水平移动光标,换行是垂直移动光标。
  • 两者在现代计算机中通常配合使用,具体实现方式依赖于操作系统的约定。

2. 回车与换行在作文纸与老式打字机的例子

对于这样一张作文纸~~~

在这里插入图片描述

其实这样的原理来自我们的老式打字机~~~
在这里插入图片描述

我们的键盘其实已经隐形的提示了,Enter键是一个向下再向左的样式~~~

在这里插入图片描述


3. 行缓冲区

行缓冲区: 解释

在Linux下,标准输出(

stdout

)的缓冲机制就像一个小水桶,专门用来装字符数据。这个水桶有点小任性:它只有在某些特定情况下才会把数据“倒出去”(也就是显示到终端)。具体来说,当:

  1. 水桶被装满(缓冲区满)。
  2. 遇到“换行符”(\n),就像按下了“冲水按钮”。
  3. 程序结束或者主动调用 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;}

运行现象

  1. 程序开始后,屏幕显示倒计时,从 10 开始,每秒递减,逐步显示 10 9 8 ... 0
  2. 数字 总是在同一行显示,没有产生多行输出。
  3. 最终倒计时结束时,输出 0,并在 printf("\n") 处换行。

关键代码行为解释

  1. %-2d 格式说明:- %- 表示左对齐。- 2 表示宽度为 2 个字符。如果数字小于 2 个字符宽,会用空格填充。- 例如:10 显示为 10,而 9 显示为 9<space>
  2. \r 的作用:- \r 是回车符(Carriage Return),将光标移回到行首,不换行。- 每次新数字输出后,光标重新回到行首,覆盖之前的数字。
  3. fflush(stdout) 的作用:- 因为标准输出是行缓冲模式(遇到换行符或缓冲区满才会刷新),这里使用 fflush(stdout) 强制刷新缓冲区,确保数字立即显示,而不是留在缓冲区中等待。
  4. sleep(1) 的作用:- 暂停程序 1 秒,形成明显的倒计时效果。
  5. 最后的换行:- 倒计时结束后,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");// 输出换行,防止进度条被终端光标覆盖。}

关键点解析

  1. **动态旋转符号 (lable)**:- 动态效果通过 rate % len 实现,rate 每次递增,而 len 为 4(|/-\ 的长度),rate % len 保证每次选取不同的旋转符号,形成循环动态效果。
  2. **缓冲区 buffer**:- 逐步填充进度条,每次填入一个符号(如 #),对应当前进度。- buffer[rate] = STYLE,将进度条当前位置填充为 STYLE(如 #)。
  3. 输出控制:- printf("[%-100s][%d%%][%c]\r", buffer, rate, lable[rate % len]): - [%-100s]:保证进度条左对齐,预留 100 格空间。- [%d%%]:显示当前进度的百分比。- [%c]:动态旋转符号。- \r:回车符,将光标回到行首,覆盖之前的内容,避免多行输出。
  4. **刷新输出 (fflush(stdout))**:- printf 输出是行缓冲模式,通常需要换行符 \n 或缓冲区满才会刷新到终端。这里使用 fflush(stdout) 强制刷新输出,确保进度条每次更新后能立即显示。
  5. 时间控制:- usleep(100000) 暂停程序 100 毫秒,模拟任务耗时,制造进度加载的效果。

运行效果

  1. 初始输出:[....................................................................................................][0%][|]
  2. 随着循环,进度条逐步加载:[#####...............................................................................................][5%][/]
  3. 最终显示:[####################################################################################################][100%][|]
  4. 换行后,结束程序。

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;}

运行效果

  1. 进度条动态更新[下载中...][##############........................................................][50.0%][|]
  2. 任务完成提示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第一个有意思的程序就写完啦!!!
创作不易,如果对各位有帮助的,求各位一个一键三连,谢谢大佬们🥰🥰🥰

在这里插入图片描述

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/Jdxxwu/article/details/143811236
版权归原作者 小柯J桑_ 所有, 如有侵权,请联系我们删除。

“Linux:指尖上的进度,流转如诗的进度条”的评论:

还没有评论