Linux程序->进度条

进度条最终效果:

目录

进度条最终效果:

一:两个须知

1:缓冲区

①:C语言自带缓冲区

②:缓冲区的刷新策略

2:回车和换行的区别

二:倒计时程序

三:入门板进度条的实现

四:升级版进度条的实现

1:预留中括号

2:箭头

3:动态百分比

4:旋转光标

五:模拟下载场景

1:对进度条的封装

2:下载程序的编写

3:串联两个函数:

a:调用函数

b:函数回调

六:低速下载场景代码的问题

七:高速下载进度条代码

八:总代码:

①:不上色版本

②:上色版本


一:两个须知

实现进度条之前,我们要先理解两个点

1:缓冲区

例子1:

#include<stdio.h>
#include<unistd.h>int main()
{printf("you can see me!\n");sleep(3);return 0;
}

我们预期是先打印出"you can see me!"然后换行,3秒后再显式提示符: 

运行结果:

解释:与我们预期一致

例子2:

#include<stdio.h>
#include<unistd.h>int main()
{printf("you can see me!");sleep(3);return 0;
}

我们预期是先打印出"you can see me!",3秒后再同一行显式提示符

运行结果如下:

解释:与我们预期不符,它是3秒后,把"you can see me!"和提示符在一行打印出来

首先我们能确定的是,打印语句无论是哪一种,一定都是第一句就被执行的了,所以肯定是某个东西影响到了打印语句的效果是先显式到屏幕上,还是后显示到屏幕上罢了,而我们的区别仅仅只有换行'\n',所以缓冲区及其刷新策略如下:

①:C语言自带缓冲区

在C语言中,每个文件都有自己的缓冲区,代码本质也是在一个文件里面编写,所以每个代码,比如上面的test.c就有自己的缓冲区,缓冲区就像一个装快递的车子,装满了才发车,这样效率更高,本质就是用空间换时间!

②:缓冲区的刷新策略

C语言缓冲区的三种刷新策略:
①:无刷新 没有缓冲区
②:行刷新->遇见\n刷新(显示器)
③:全刷新->缓冲区满了才刷新(普通文件)

两种特殊情况:
①:强制刷新(fflush()函数)
②:进程退出的时候(进度条不加\n显示不出来 程序退出显示出来的原因)

刷新要结合普通的刷新策略和特殊情况来判断,现在我们再来理解一下例子1和2的现象原因

例子1:因为我们是对显示器写入,所以其遇见\n才会刷新,这就是为什么在sleep函数之前,就已经把"you can see me!"打印了出来,因为缓冲区满足被刷新的条件

例子2:虽然我们是对显示器写入,但是我们没有\n,所以我们要么使用特殊情况中的fflush函数,或者是进程退出的时候,这就是为什么进程退出的时候,"you can see me!"和提示符一起打印了

建议看看博主的这篇文章:Linux->基础IO-CSDN博客,你会对缓冲区和刷新策略理解更透彻

2:回车和换行的区别

我们平时键盘上的回车,其实是有两个功能,换行+移动到新行首,这就是为什么按一下就能到下一行的开始位置:

所以我们代码中的'\n',就是键盘上的回车(换行+移动到新行首),这就是为什么我们连续的printf包含'\n'会呈现清晰的打印结果

我们的进度条很明显是在同一行不同的打印,所以'\n'是不行的!要用'\r'!

但是呢,\r 需要一些例子来说明其的特性,才能为我们之后的进度条实现打好基础,所以,现在实现一个倒计时程序的代码中体会\r的特性

二:倒计时程序

例子1:

期望是实现一个建议的10秒倒数到1秒的效果

#include<stdio.h>
#include<unistd.h>int main()
{int count = 9;while(count){printf("%d\r",count);count--;sleep(1);}return 0;
}

运行效果:

解释:什么都没打印,最后打印了提示符?!因为我们知道\r 只是把光标移动到同一行的起始为止,所以打印的结果9会被8封盖,8会被7覆盖.....最后提示符会覆盖掉最后一次的1,然后程序结束时,缓冲区刷新,刷新除了提示符!

本质就是我们向显示屏打印,没有换行,只能缓冲区满了才刷新,而这么点内容根本不会满,所以最后程序退出,强制刷新了缓冲区!所以\r一定要配合强制刷新函数fflush使用,才能起到效果!

修改后的代码和效果如下:

#include<stdio.h>
#include<unistd.h>int main()
{int count = 9;while(count){printf("%d\r",count);fflush(stdout);count--;sleep(1);}//printf("\n");return 0;
}

运行效果:

 

解释:虽然达到了效果,但是最后一次不应该被提示符覆盖,所以我们要在代码循环的外面加上打印\n(将上面代码的注释放开即可),如下效果

例子2:

唯一的区别就是count从10开始倒数

#include<stdio.h>
#include<unistd.h>int main()
{int count = 10;while(count){printf("%d\r",count);fflush(stdout);count--;sleep(1);}printf("\n");return 0;
}

运行结果:

解释:为什么是10到90到80....

因为9去覆盖10,只能覆盖到第一个字符,以此类推,就产生了这种效果!所以我们使用\r要预留空间,也就是设置为%-2d即可!

修改后的代码和效果如下:

#include<stdio.h>
#include<unistd.h>int main()
{int count = 10;while(count){printf("%-2d\r",count);fflush(stdout);count--;sleep(1);}printf("\n");return 0;
}

运行效果: 

最后我们实现出了一个倒计时程序,也总结出了使用\r的三条性质:

总结:
1:\r之后一定要紧接fflush
2:最后一次打印完后 要\n 避免提示符覆盖打印内容
3:\r 对%d的预留很重要

务必牢记这三点 其会贯彻整个进度条代码

三:入门板进度条的实现

所以我们就可以不断的'\r'的方式去打印一个递增的字符串,不就能够实现进度条了吗?该字符串的字符类型我们可以自己设置,你想要一串*号,一串=号都可以

所以我们需要一个101大小的字符数组,一开始全都是'\0',我们打印一次,就往该数组内部增加一个字符=,即可!

Q:为什么数组是101大小,不是100?

A:因为预留最后那一个位置给'\0',才能安全的进行最后一次的打印!

代码如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101//定义数组的大小
#define S '='//定义进度条的字符类型int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));//把数组全设置为'\0'int i = 0;while(i<=100){printf("%s\r",bar);fflush(stdout);//强制刷新buffer[i++] = S;usleep(100000);//0.1秒更新数组且打印一次,可以比sleep函数更快} printf("\n");//避免提示符覆盖进度条return 0;
}

效果如下:

解释:=号刚好有100个,符合预期

Q1:为什么是“连续增加的等号”?而不是把上一次z字符数组清空然后再打印新的字符数组?

A1:因为\r 不会清除之前的内容,只是把移动光标到行首,然后进行下一次的字符数组打印,而字符数组一次比一次多,所以就会呈现出连续递增的感觉?

为什么称这个代码是入门级呢,因为其有缺陷:

Q2:数组的最终状态是怎么样的?

A2:整个数组全是(buffer[0] 到 [100])全是 '='(共 101 个),我们给最后一个位置预留存放\0,根本没有存放,因为当i为99的时候,此时进来打印后,buffer[99]='=',然后i变成了100,再次进入循环打印数组,此时的数组[0]~[99]都是'=',共100个,所以上面GIF图是100个,此时打印后,buffer[100] =‘=',也就是第101个元素也被变成了'=',此时i++为101,跳出循环!

验证:在最外面再打印一次数组:

符合我们的分析,数组的确是有101个'=' 

Q3:既然 bar 数组的所有元素(bar[0]~bar[100])都被赋值为 '=',没有 '\0',为什么最外面 printf("%s", bar) 仍然能正确停止,并且每次都能打印 101 个 '='?按照 C 字符串的规则,printf 应该一直往后读,直到随机遇到 '\0' 才停止才对啊?

A3:这在VS下运行的确是随机的,但是在Linux下就不是了,因为涉及到了Linux的内存布局,可能是bar 的相邻内存可能是 '\0'等原因,不必在意

所以入门级别的代码,为了安全,应该一开始设置MAX为102才对:

如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 102//定义数组的大小
#define S '='//定义进度条的字符类型int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));//把数组全设置为'\0'int i = 0;while(i<=100){printf("%s\r",bar);fflush(stdout);//强制刷新buffer[i++] = S;usleep(100000);//0.1秒更新数组且打印一次,可以比sleep函数更快} printf("\n");//避免提示符覆盖进度条return 0;
}

如果你非要保持101 ,修改一下while的条件和内部执行顺序也行:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define type '='int main()
{char bar[MAX];memset(bar,'\0',sizeof(bar));int i = 0;while(i<=99){bar[i++] = type;printf("%s\r",bar);fflush(stdout);usleep(100000);} printf("\n");printf("%s\n",bar);return 0;
}

注:博主在下面的实现中 会全程使用这个类型的代码 !

最后的这两种写法,都可以让安全得打印100个'='

四:升级版进度条的实现

有入门进度条的效果,我们能看出缺了一些细节: 

①:预留中括号

②:箭头的设置

③:动态百分比

④:旋转光标

1:预留中括号

也就是说,进度条应该是被一个中括号括起来的,而且中括号,一开始就再最开始和最末尾:

代码如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define type '-'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;while(i<=99){buffer[i++] = type;printf("[%-100s]\r",buffer);//预留中括号 %-100s的形式fflush(stdout);usleep(100000);} printf("\n");return 0;
}

运行效果:

2:箭头

也就是说,不要只是一条线,在线的最前面加一个箭头,更有指向性一点,所以我们就要在每次让i下标对应的元素变成'-'的同时,还要让i++对应的元素变成'>',代码如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body; buffer[i] = head;printf("[%-100s]\r",buffer);fflush(stdout);usleep(100000);} printf("\n");return 0;
}

效果如下:

解释:乍一看好像没什么问题,但是其实如果你数一下,会发现'-'和'>'的数目为101,这意味着我们只有101个空间,却全被使用了,没有给'\0'预留,无法保证被安全得打印;且进度条加载满了之后,箭头应该消失才对,综合这两点来看,我们不用改变数组的大小,而是让i=99的时候呢,我们让下标99对应的第100个元素'>'变成-即可(buffer[i++] = body; ),而不在进行i++后,让下标100对应的第101个元素再变成>"(buffer[i] = head;),所以我们在赋值'>'处加一个判断即可如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s]\r",buffer);fflush(stdout);usleep(100000);} printf("\n");return 0;
}

运行效果:

解释:和最上面的原来效果相比,不仅的确变短了1个字符,总数为100,其次最后加载满的时候,箭头也消失了

 

3:动态百分比

用一个动态百分比来反映进度条的进展,这是进度条必不可少的一部分,但是非常简单,只需要把i打印出来不就可以了吗?代码如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s][%d%%]\r",buffer,i);fflush(stdout);usleep(100000);} printf("\n");return 0;
}

运行效果:

Q1:i不是最大只有99吗?为什么最后打印出来能到100%?

A1:因为i进入循环,对i下标赋值body后会后置++的

Q2:[%d%%]是什么意思? 

A2:在格式化字符串中,% 是特殊符号(如 %d%f),如果想直接输出 %,必须用 %% 转义。

4:旋转光标

日常中的进度条的旁边都有一个圆圈一直再转,或者一个竖线在顺时针旋转,这样即显得不那么单调,当我们进度条由于网速等原因卡住的时候,正在旋转的旋转光标也会告诉我们进度条只是由于网速被卡住了,而不是程序卡了

所以我选择使用 |/-\ 四个符号循环,模拟顺时针旋转,实现旋转光标,代码如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
const char *rotate="|/-\\";//旋转光标数组
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int n = strlen(rotate);//旋转光标数组的个数n  用来后面打印的时候取模int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s][%d%%][%c]\r",buffer,i,rotate[i%n]);fflush(stdout);usleep(100000);} printf("\n");return 0;
}

运行效果:

解释:打印的时候,取旋转光标数组的下标,正好可以用一直改变的i来模上旋转光标数组的元素个数n,这样就能一直取到[0,3] ;注意,"|-/\\"中的\\依旧是转义!

其他光标推荐:

用"..oo00OO00oo.."

效果如下:

所以,升级版进度条的最终代码如下:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
const char *rotate="..oo00OO00oo..";
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int n = strlen(rotate);//旋转光标数组的个数n  用来后面打印的时候取模int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s][%d%%][%c]\r",buffer,i,rotate[i%n]);fflush(stdout);usleep(100000);} printf("\n");return 0;
}

五:模拟下载场景

但是,进度条怎么可能是一个独立的程序,其往往是用来反映其他东西的,比如下载的进度?所以,下面手动得模拟一个反映下载进度的场景:

我们用1024*1024*1024  也就是1GB来表示我们需要下载的总量,然后我们每次(间隔时间自己设定)下载一个随机数,其范围在0kb ~ 1mb之间,然后当总量被减到零的时候,我们就下载完成!

1:对进度条的封装

所以我们要对我们之前写的代码进行管理一下,我们把之前的程序封装成一个函数,这样我们再写一个下载函数,然后就可以在main中完成函数的调用了

封装如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define MAX 101
const char *rotate = "..oo00OO00oo..";
#define body '-'
#define head '>'void processbar()
{char buffer[MAX];memset(buffer, '\0', sizeof(buffer));int n = strlen(rotate); // 旋转光标数组的个数n  用来后面打印的时候取模int i = 0;buffer[0] = head;while (i <= 99){buffer[i++] = body;if (i < 99)buffer[i] = head;printf("[%-100s][%d%%][%c]\r", buffer, i, rotate[i % n]);fflush(stdout);usleep(100000);}printf("\n");
}int main()
{rocessbar();return 0;
}

2:下载程序的编写

首先我们定义一个下载的总量为1个G:

#define FILESIZE 1024*1024*1024

以及下载函数的框架:

void download()
{}

①:种随机数种子

下载的速率不是固定的,所以用随机数来代替正好可以模拟:

void download()
{srand(time(NULL)*11);//乘11只是为了让结果更随机int one = rand()%(1024*1024);//让每次下载的速度在0kb~1mb之间
}

 

②:让每次下载循环起来,得到余量

//模拟一种场景
void download()
{srand(time(NULL)*11);int total = FILESIZE;//需要下载的总大小while(total){usleep(5000); //下载动作int one = rand()%(1024*1024);//一次下载的大小total -= one;//剩余的大小if(total < 0) total = 0;//不一定刚好减到0 所以当其<0 直接让其为0}
}

解释:每次下载的大小one,用total减等one,total就是动态的余量,但是余量有可能最后一次减到负数,所以当其是负数的时候,我们将其赋值0,方便退出循环

③:得到下载的进度

下载的进度,不就是已经下载的大小比上总大小吗;而为什么要求下载的进度呢?因为我们要将这个数传给进度条程序,让其能够更具速度打印进度条,所以必然的我们在后面还要对我们的进度条函数进行改造的,下载进度rate如下:


void download()
{srand(time(NULL)*11);int total = FILESIZE;while(total){usleep(5000); //下载动作int one = rand()%(1024*1024);total -= one;if(total < 0) total = 0;// 当前的进度是多少?int download = FILESIZE - total;double rate = (download*1.0/(FILESIZE))*100; // 0 23.4 35.6, 56.6printf("download: %f\n", rate);}
}

解释:

 double rate = (download*1.0/(FILESIZE))*100; 

 我们的速度肯定是要double类型才真实一点,所以两个整数相除,怎么获得浮点数呢?download*1.0/FILESIZE即可,但是为什么要乘100呢?因为不乘就是0~1之间的double类型,而我我们平时生活中下载都是56.7%这种0~100之间的double类型,所以我们给其乘100

下面我们运行一下这个下载函数:

重点:要明白的是,我们的下载跨度最多0.1%,因为最大下载1mb,而总大小1个G,所以你单次下载最多也才占到总大小的%0.1,为什么我要这么写,这意味着,我们的processbar函数就可以无脑的套用之前的部分代码,比如无脑的将buffer数组进行递增写入字符,因为如果前一次是1.5 后一次是1.6,其实数组根本不用变,因为两次作为下标取整之后都是1,只有当其加到二点几的时候,才会取整得到2,才能改变数组,才会影响进度条的效果!所以即使下载速录在扩大10被,也就波动到1%,依旧可以复用前面的代码!

这样写的好处是,复用前面的代码,坏处是,生活中的下载可能跨度远远大于了1%,所以在后面我们还要对总代码升级一次,让其可以应对跨度大的下载场景!

至此我们的下载函数就写完了,下一步就是把下载函数和进度条函数关联起来,有两种常见方法:

3:串联两个函数:

a:调用函数

所以我们的processbar函数要改造一下,参数要接受每次循环产生的rate:

改造如下:

#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;void processbar(double rate)
{int n = strlen(rotate); // 旋转光标数组的个数n  用来后面打印的时候取模// 该句不能放在全局 因为其要在编译时运行if (rate <= 1.0) buffer[0] = head; // 第一次下载在1%以内 第一个符号就是箭头printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}

解释:我们的 processbar函数 中肯定不会自己循环了,而是当下载函数传过来一个参数,则其在打印对饮的进度条,而我们不用担心两次传过来的数据,波动较大,因为我们在上文说过,你传过来的两个double类型差值不会超过1,这就意味着,大部分代码不用改变,依旧是递增式的往buffer数组里面插入数据,但有几个点需要说明一下:

①:

rotate[(count++) % n]

我们的旋转光标的下标,不能再用i 了,因为没i了,所以我们需要自建一个全局变量count,来作为其下标,其要++,才能让光标动起来,当然你也可以用static修饰count写进函数内,一样的

②:

if (rate < 99)buffer[(int)rate + 1] = head;

因为作为数组buffer的下标需要整形,所以我们进行强转为整形后再作为下标,而至于+1,是因为我们要把下一个字符变成箭头'>';而当其取整后为99的时候,代表其在上行代码已经将下标为99也就是对应的第100个元素变成'-'了,所以我们的箭头不要了,即美观又安全

③:

 if (rate >= 100.0)printf("\n");

最后下载完成后,我们要打印一次换行,避免被提示符覆盖

所以总代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define MAX 101
#define body '-'
#define head '>'
#define FILESIZE 1024 * 1024 * 1024char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
int count = 0;void processbar(double rate)
{int n = strlen(rotate); // 旋转光标数组的个数n  用来后面打印的时候取模// 该句不能放在全局 因为其要在编译时运行if (rate <= 1.0)buffer[0] = head; // 第一次下载在1%以内 第一个符号就是箭头printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99.0)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}// 模拟一种场景
void download()
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000); // 下载动作int one = rand() % (1024 * 1024);total -= one;if (total < 0)total = 0;// 获取当前下载比率(已经下载大小/总大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;processbar(rate);}
}int main()
{download();return 0;
}

运行效果:

b:函数回调

我们定义一个processbar函数指针的类型,然后让download函数参数使用这个类型,就可以在download函数内部使用了,其实感觉没什么必要哈哈哈,但还是讲一下吧,代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define MAX 101
#define body '-'
#define head '>'
#define FILESIZE 1024 * 1024 * 1024char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
int count = 0;void processbar(double rate)
{int n = strlen(rotate); // 旋转光标数组的个数n  用来后面打印的时候取模// 该句不能放在全局 因为其要在编译时运行if (rate <= 1.0)buffer[0] = head; // 第一次下载在1%以内 第一个符号就是箭头printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99.0)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}typedef void((*pro_bar)(double));
// 模拟一种场景
void download(pro_bar pb)
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000); // 下载动作int one = rand() % (1024 * 1024);total -= one;if (total < 0)total = 0;// 获取当前下载比率(已经下载大小/总大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;pb(rate);}
}int main()
{download(processbar);return 0;
}

六:低速下载场景代码的问题

六作为拓展讲解,想要了解可以看一下

上面的这个代码代码用进度条反映了一个伪下载的场景,但是其实不完美的!因为其对下载较快的场景会出错!

例子:将download中的单次one的下载速度上限提高至原来的15倍,此时单次最高占到了1.5%,也就是意味着如果你上次是0.9,下次正好单次下载最快,达到了2.4,也就是第一个的取整下标是0,第二次的取整下标是2,这会导致下标1对应的元素没被赋值为'-',所以我们觉得其应该是保留了原本的'\0',这会导致,哪里第一次被跳过了没被赋值'-',其就应该停留在那个地方,那真的是这样吗?

首先我们先单独使用download函数来验证高速率下传递给processbar函数的(int)rate是否会跳跃:

所以我们把单词下载one的速率提高20倍,然后打印所有的(int)rate查看是否会跳跃:

void download(pro_bar pb)
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000); // 下载动作int one = rand() % (1024 * 1024*15);total -= one;if (total < 0)total = 0;// 获取当前下载比率(已经下载大小/总大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;printf("%d ",(int)rate);//pb(rate);}
}

打印结果:

解释:随便就能找到两个下标被跳过了,所以按照我们的预想,4下标仍然为'\0',所以往后的每次打印遇到'\0'都会停止,也就是你的光标虽然在旋转,百分比虽然在增加,但是你每次\r打印的应该都一样!也就是我认为 buffer[4] 应保留初始值 \0(即空白),导致进度条在 4% 位置中断。

但是效果如下:

原因如下:

这与 VS Code 终端的渲染机制 和 字符串输出的特性,不作深究,所以我们的想法是对的,但是受到了终端的影响,所以看起来的结果很奇怪

所以我们可以把buffer数组全部设置为空格字符' ',然后最后一次设置为'\0'(为了安全打印),这样我们就可以看到断裂的进度条了:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define MAX 101
#define body '-'
#define head '>'
#define FILESIZE 1024 * 1024 * 1024char buffer[MAX];
const char *rotate = "|-/\\";
int count = 0;void processbar(double rate)
{int n = strlen(rotate);if (rate <= 1.0)buffer[0] = head;printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99.0)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}typedef void((*pro_bar)(double));
// 模拟一种场景
void download(pro_bar pb)
{// srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(100000); // 下载动作int one = rand() % (1024 * 1024 * 50);//50倍更好观察结果total -= one;if (total < 0)total = 0;// 获取当前下载比率(已经下载大小/总大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;pb(rate);}
}int main()
{memset(buffer, ' ', sizeof(buffer));//buffer数组全部设置为空格字符' 'buffer[101] = '\0';//最后一个设置为'\0' 安全打印download(processbar);return 0;
}

 

Q:进度条断裂我理解了,但是为什么会有这么多的箭头,而不是"---- ----  ----    ---->"这样?

A:

由图可知就能知道为什么箭头这么多,本质就是跳跃会导致覆盖箭头失败!

七:高速下载进度条代码

其实非常简单,我们在processbar函数中接收到一个下标,我们就把数组一开始到这个下标的内容全部置为'-',然后再把下标对应的内容置为'>'即可,其实一开始就可以讲这个,为什么不讲呢,本质是通过低级的代码引出更多的问题,不然我们不懂为什么不断裂,不知道可以用空格字符来展现断裂,甚至有的人都不知道低速代码的弊端,所以,讲下还是有必要的!

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;void processbar(double rate) {int n = strlen(rotate);memset(buffer, body, (int)rate); // 直接填充'-'到当前下标buffer[(int)rate] = head;        // 再把头部换成箭头printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);if (rate >= 100) printf("\n");
}#define FILESIZE (1024 * 1024 * 1024)void download() {srand(time(NULL) ^ 1023);int total = FILESIZE;while (total) {usleep(10000);int one = rand() % (50 * 1024 * 1024); // 扩大随机范围(最大5MB/次,却依旧不会断裂)total -= one;if (total < 0) total = 0;double rate = ((FILESIZE - total) * 100.0) / FILESIZE;processbar(rate);}
}int main() {download();return 0;
}

50倍:

15倍:

 

注:高速代码可完全替代低速代码,而不是仅仅只可用于高速下载!

八:总代码:

我们对高速下载代码,上个色吧~

①:不上色版本

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;void processbar(double rate)
{int n = strlen(rotate);memset(buffer, body, (int)rate); // 直接填充'-'到当前下标buffer[(int)rate] = head;        // 再把头部换成箭头printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);if (rate >= 100)printf("\n");
}#define FILESIZE (1024 * 1024 * 1024)void download()
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000);int one = rand() % (50 * 1024 * 1024); // 扩大随机范围(最大5MB/次,却依旧不会断裂)total -= one;if (total < 0)total = 0;double rate = ((FILESIZE - total) * 100.0) / FILESIZE;processbar(rate);}
}int main()
{download();return 0;
}

②:上色版本

让所有元素(进度条 百分比 光标)都能根据进度改变颜色:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;// ANSI 颜色代码
#define COLOR_RED    "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_GREEN  "\033[32m"
#define COLOR_CYAN   "\033[36m"
#define COLOR_RESET  "\033[0m"void processbar(double rate) {int n = strlen(rotate);memset(buffer, body, (int)rate);buffer[(int)rate] = head;// 根据进度选择颜色(红→黄→绿)const char *color;if (rate < 30)       color = COLOR_RED;else if (rate < 70)  color = COLOR_YELLOW;else                 color = COLOR_GREEN;// 旋转光标单独用青色const char *spin_color = COLOR_CYAN;// 打印带颜色的所有元素printf("[%s%-100s%s][%s%.1f%%%s][%s%c%s]\r", color, buffer, COLOR_RESET,    // 进度条部分color, rate, COLOR_RESET,      // 百分比部分spin_color, rotate[(count++) % n], COLOR_RESET); // 旋转光标fflush(stdout);if (rate >= 100) printf("\n");
}#define FILESIZE (1024 * 1024 * 1024)void download() {srand(time(NULL) ^ 1023);int total = FILESIZE;while (total) {usleep(10000);int one = rand() % (10 * 1024 * 1024); // 单次最多下载10MBtotal -= one;if (total < 0) total = 0;double rate = ((FILESIZE - total) * 100.0) / FILESIZE;processbar(rate);}
}int main() {download();return 0;
}

效果:

上色原理

通过插入ANSI转义码(如\033[31m表示红色)临时修改终端文本颜色。代码中动态选择颜色后,用\033[0m重置样式,使进度条、百分比和光标按进度变色(如红→黄→绿),所有颜色变化均由终端实时解析实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93081.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93081.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫实战:研究tldextract库相关技术构建新闻网站域名分析爬虫系统

1. 引言 网络爬虫作为一种自动获取互联网信息的技术,在数据挖掘、信息检索、舆情分析等领域有着广泛的应用。Python 因其丰富的库和简洁的语法,成为了开发爬虫的首选语言。tldextract 是 Python 中一个强大的域名解析库,能够准确地从 URL 中提取顶级域名、二级域名等关键信…

【算法-华为机试-火星基地改造】

基地改造题目描述目标输入输出代码实现题目描述 在2XXX年&#xff0c;人们发现了一块火星地区&#xff0c;这里看起来很适合建设新家园。但问题是&#xff0c;我们不能一次性将这片地区的空气变得适合人类居住&#xff0c;得分步骤来。 把这片火星地区想象成一个巨大的棋盘。棋…

C++入门自学Day1-- C语言的宏函数和C++内联函数

一、函数调用开销函数调用会涉及&#xff1a;参数压栈&#xff08;或寄存器传参&#xff09;跳转到函数体返回值处理栈帧销毁这个过程对小函数来说可能非常浪费&#xff0c;因此&#xff0c;宏函数和内联函数的目的就是避免“函数调用的开销”&#xff0c;通过代码展开&#xf…

Pytorch混合精度训练最佳实践

混合精度训练&#xff08;Mixed Precision Training&#xff09;是一种通过结合单精度&#xff08;FP32&#xff09;和半精度&#xff08;FP16/FP8&#xff09;计算来加速训练、减少显存占用的技术。它在保持模型精度的同时&#xff0c;通常能带来 2-3 倍的训练速度提升&#x…

Qt C++动态库SDK在Visual Studio 2022使用(C++/C#版本)

01 将C SDK 集成到 IDE 中以下是在 Microsoft Visual Studio 平台下 SDK 的集成。2.1 Visual Studio 平台下 C/C环境配置及集成到 IDE 中xxx.lib 和 xxx.dll 适合在 Windows 操作系统平台使用&#xff0c;这里以 VS2022 环境为例。2.1.1 C/C 工程环境配置与集成1、C# SDK 接口…

大语言模型 LLM 通过 Excel 知识库 增强日志分析,根因分析能力的技术方案(2):LangChain + LlamaIndex 实现

文章大纲 1 技术原理总览 2 详细实现步骤(含代码) 2.1 环境准备 2.2 Excel → LlamaIndex 节点 2.3 构建向量索引(FAISS 本地) 2.4 Google Cloud 向量检索(可选替换 FAISS) 2.5 LangChain 问答链 A. RAG 模式(向量检索 + LLM 生成) B. SQL 模式(无 RAG,直接查表) 2.…

提升ARM Cortex-M系统性能的关键技术:TCM技术解析与实战指南

文章目录引言一、TCM基础架构与工作原理1.1 TCM的物理特性1.2 与缓存机制的对比1.3 ARM Cortex-M系列对TCM的支持二、TCM的典型应用场景2.1 实时中断处理2.2 低功耗模式下的待机代码2.3 高性能算法执行2.4 系统初始化阶段的关键代码三、实战指南&#xff1a;在STM32H7上配置和优…

大数据之路:阿里巴巴大数据实践——大数据领域建模综述

为什么需要数据建模 核心痛点 数据冗余&#xff1a;不同业务重复存储相同数据&#xff08;如用户基础信息&#xff09;&#xff0c;导致存储成本激增。计算资源浪费&#xff1a;未经聚合的明细数据直接参与计算&#xff08;如全表扫描&#xff09;&#xff0c;消耗大量CPU/内存…

实战演练1:实战演练之命名实体识别

实战演练1:实战演练之命名实体识别 命名实体识别简介 代码 命名实体识别简介 什么是命名实体识别任务 命名实体识别(Named Entity Recognition,简称NER)是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。通常包括两部分: (1)实体边界识别。(2)确定…

数据结构基础内容(第七篇:堆、哈夫曼树)

# 堆 Heap 优先队列(Priority Queue) 结构性:用 *数组* 表示的完全二叉树; 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值) * “最大堆(MaxHeap)”,也称“大顶堆”:最大值 * “最小堆(MinHeap)”,也称“小顶堆” :最小值 主要操作有: • MaxHeap Create( i…

CS231n-2017 Lecture7训练神经网络(二)笔记

本节主要是神经网络的动态部分&#xff0c;也就是神经网络学习参数和搜索最优超参数的过程梯度检查&#xff1a;进行梯度检查&#xff0c;就是简单地把解析梯度与数值计算梯度进行比较&#xff0c;防止反向传播的逻辑出错&#xff0c;仅在调试过程中使用。有如下技巧 &#xff…

IntelliJ IDEA 中左上方未显示项目根目录问题

问题&#xff1a; 在IDEA中编写代码时&#xff0c;发现左上方只显示项目的子模块&#xff0c;未显示根项目名称。 如图所示&#xff0c;未显示子模块的根项目&#xff1a;问题分析 顶层根目录未被识别为项目根目录&#xff0c;需要手动添加识别。 问题解决 进入File – Project…

OpenCV 图像变换全解析:从镜像翻转到仿射变换的实践指南

前言处理图像时&#xff0c;翻转、旋转、平移等操作很常用。OpenCV 提供了简单的方法实现这些变换&#xff0c;本文带你快速学会用它做图像翻转和仿射变换。1 图像翻转(图像镜像旋转)在OpenCV中&#xff0c;图片的镜像旋转是以图像的中心为原点进行镜像翻转的。cv2.flip(img,fl…

【运维】Linux运维命令记录

重置root密码使用命令重新设置一下root账户的密码 passwd root根据提示设置一下密码&#xff0c;然后使用sudo -i 时输入密码就可以切换到root账户了ssh登陆以后&#xff0c;要用sudo -i命令给用户提权&#xff0c;提到超级管理员&#xff0c;然后输入密码才有用

PandasAI连接LLM进行智能数据分析

1. 引言 Pandas是一个数据分析开源组件库&#xff0c;提供了高性能、易用的数据结构和数据分析工具。它的核心的功能是其DataFrame对象&#xff0c;这是一个带有行和列标签的二维表格数据结构&#xff0c;支持缺失数据处理、时间序列功能、灵活的数据输入输出方法、数据对齐和…

Spring之【Bean的生命周期】

目录 1、生成BeanDefinition BeanDefinitionRegistry接口 DefaultListableBeanFactory实现类 2、合并BeanDefnition AbstractBeanFactory类 3、BeanFactoryPostProcessor的方法回调 AbstractApplicationContext类 PostProcessorRegistrationDelegate类 4、BeanPostPro…

搜狐新闻直播间适配HarmonyOs实现点赞动画

01背景介绍随着新闻客户端鸿蒙单框架系统适配工作的推进&#xff0c;从原来的基础功能到现在已经适配全功能的85%以上。与此同时&#xff0c;我们也在持续深入挖掘鸿蒙系统的特性&#xff0c;以提升整体应用的质量与用户体验。在这一过程中&#xff0c;动画作为增强交互与视觉体…

83、设置有人DTU设备USR-M100采集传感器数据,然后上传阿里云服务

基本思想:设置M100 采集传感器数据 一、首先将DTU设备USR-M100连接路由器上,然后使用python代码搜索同一局域网设备, import platform import sys import os import time import threadinglive_ip = 0def get_os():os = platform.system()if os == "Windows":re…

P1019 [NOIP 2000 提高组] 单词接龙

题目描述单词接龙是一个与我们经常玩的成语接龙相类似的游戏&#xff0c;现在我们已知一组单词&#xff0c;且给定一个开头的字母&#xff0c;要求出以这个字母开头的最长的“龙”&#xff08;每个单词都最多在“龙”中出现两次&#xff09;&#xff0c;在两个单词相连时&#…

详解力扣高频SQL50题之1633. 各赛事的用户注册率【简单】

传送门&#xff1a;1633. 各赛事的用户注册率 题目 用户表&#xff1a; Users -------------------- | Column Name | Type | -------------------- | user_id | int | | user_name | varchar | -------------------- user_id 是该表的主键(具有唯一值的列)。 该表中的每行包…