Linux第一个系统程序-进度条(14)
文章目录
- 事先说明(8-0.00.00)
- 第一个版本
- 解决进度条的增长
- 调整比率(下载进度的) + 旋转光标
- 代码
- 第二个版本
- 1. 代码展示
- 2. 问题
- 3. 添加旋转光标
- 4. 完整代码
- 4.1 main.c
- 4. 2 process.c
- 5. 补充(解耦)
进度条动图效果展示(动图展示不了光标旋转)。用到的Makefile模板获取
事先说明(8-0.00.00)
-
思路展示
:进度条的增长用#
数量的增加来体现,当#
数量增长,末尾数字也在不断增长,用一个\
的不断旋转,来告诉用户是否在下载
-
这里需要写进度条的两个版本,第一个版本:正常展示进度条的基本功能。第二个版本:能用于工程项目当中
-
准备三个文件:
main.c,process.c,process.h
第一个版本
解决进度条的增长
1. 先展示大致框架
-
process.h
:函数的声明,process.c
:函数的实现,main.c
:进行函数的调用。process.h中要修改成 #include<string.h>
-
再将之前写的
Makefile
拷贝到你这三个文件所在的目录,记得去更改可执行程序的名称(BIN
)为process
[xiao@hcss-ecs-28ce dir_process]$ make
编译... main.c 成 main.o
编译... process.c 成 process.o
链接 main.o process.o 成 process[xiao@hcss-ecs-28ce dir_process]$ ls
main.c main.o Makefile process process.c process.h process.o
[xiao@hcss-ecs-28ce dir_process]$ ./process
hello world[xiao@hcss-ecs-28ce dir_process]$ make clean
[xiao@hcss-ecs-28ce dir_process]$ ls
main.c Makefile process.c process.h
2. 第一次尝试(写代码就是:看效果 + 反复修改代码)
#include"process.h" #define SIZE 101 //100个警号+\0
#define STYLE '#'
// v1: 展示进度条的基本功能 void process()
{ int rate = 0; char buffer[SIZE]; // 将buffer数组里的字符全部设置成\0 // 以免字符串的\0被覆盖,打印乱码 memset(buffer, 0, sizeof(buffer)); while(rate <= 100) { printf("[%s]\n", buffer); buffer[rate] = STYLE; rate++; } // printf("hello world\n");
}
- 先解决第一个问题:这里打印成多行,只想让它在一行内增长警号的数量。这里就要用到
倒计时
小程序的思路:换行符(\r
)+fflush
刷新缓冲区
- 要解决上面这个问题也很简单:
while
循环结束后,打印换行符即可(printf("\n");
) - 是打印在一行了,但是一下就出现了一百个警号,好像没体现出动态增长。这里就要请出程序休眠函数:
sleep
与usleep
。sleep
函数参数的单位是秒,usleep
函数参数的单位是微秒
- 可是最右边的中括号咋跟着一起走呢?可以用到:
[%100s]
,预留出100个字符。这样中括号就在最右边,但你会发现它是倒着增长警号数量的(从右边逐渐增长) - 这是因为格式化输出定长控制的时候,不足对应的位置默认是右对齐的,所以100前面得加个负号
#include"process.h"#define SIZE 101 //100个警号+\0
#define STYLE '#'
// v1: 展示进度条的基本功能void process()
{int rate = 0;char buffer[SIZE];// 将buffer数组里的字符全部设置成\0// 以免字符串的\0被覆盖,打印乱码memset(buffer, 0, sizeof(buffer));while(rate <= 100){printf("[%-100s]\r", buffer);fflush(stdout);buffer[rate] = STYLE;rate++;// sleep(1);这里用sleep函数也可以usleep(50000);// 1s = 1000ms = 1000x1000us}printf("\n");// printf("hello world\n");
}
调整比率(下载进度的) + 旋转光标
- 这里的比率就是
rate
(#
的数量),有几个#
就意味着下载了多少百分比
- 而最后面的
%%
是为了在输出中显示一个%
符号本身,所以用了两个%
。这样在输出时,这个格式字符串就会正确地显示一个%
符号,而不是被当作格式说明符来解析 - 如何让
\
字符旋转起来,这个思路设计的也很巧妙。一个字符串:"|/-\\"
,只要不断地依次打印其中地字符,就能模拟出动态旋转,\\
→\
- 动图展示效果(动图光标旋转刷新不出来)
代码
#include"process.h"#define SIZE 101 //100个警号+\0
#define STYLE '#'
// v1: 展示进度条的基本功能void process()
{int rate = 0;char buffer[SIZE];// 将buffer数组里的字符全部设置成\0// 以免字符串的\0被覆盖,打印乱码memset(buffer, 0, sizeof(buffer));const char* label = "|/-\\";int len = strlen(label);while(rate <= 100){printf("[%-100s][%d%%][%c]\r", buffer, rate, label[rate%len]);fflush(stdout);buffer[rate] = STYLE;rate++;// sleep(1);这里用sleep函数也可以usleep(50000);// 1s = 1000ms = 1000x1000us}printf("\n");// printf("hello world\n");
}
咱们软件安装的时候,安装的进度条显示是根据你实际下载的数据量和期望下载的数据量之比去显示进度条的长度(边下载,边更新进度条),并不是向上面这种直接将进度条直接打完,才去开始下载。现在写的代码都是单进程的代码
第二个版本
1. 代码展示
total
:下载的总量speed[]
:随机下载的速度FlushProcess
:根据目前下载的量,去更新一次进度current
:目前下载的量
process.c
,记得在process.h
文件中定义该函数的声明
#include"process.h"#define SIZE 101 //100个警号+\0
#define STYLE '#'// v2: 根据进度,动态刷新一次进度条void FlushProcess(double total, double current)
{char buffer[SIZE];memset(buffer, 0, sizeof(buffer));double rate = current*100.0/total;int num = (int)rate;//打印警号的数量// 根据下载的进度,刷新一次进度条for (int i = 0; i < num; i++)buffer[i] = STYLE;printf("[%-100s][%.1lf%%]\r", buffer, rate); //.1:保留一位小数fflush(stdout);if (num >= 100) printf("\n");
}
main.c
#include"process.h"
#include<time.h>
#include<stdlib.h>double total = 1024.0; //下载的总量;
double speed[] = {1.0, 0.5, 0.3, 0.2, 0.01, 0.001}; // 下载速度-网速void download()
{srand(time(NULL));double current = 0.0;while (current <= total){FlushProcess(total, current);//下面的代码是模拟资源的下载usleep(5000);current += speed[rand() % sizeof(speed)];}
}
int main()
{download();// 进行process的函数调用// process();return 0;
}
2. 问题
第一个解决方案:将
int i = 0;
放置在for
循环的外面
第二个解决方案:编辑
Makefile
,编译加工-std=c99
的选项。如果出现了一个warning
的警告关于usleep
函数,不用管是C99
的问题
这里的
current += speed
并不一定能让current
等于total
,可能会出现下面的两个问题(这里的rate
=100.0,可能是四舍五入,导致if (num >= 100) printf("/n");
语句不执行)
void download()
{srand(time(NULL));double current = 0.0;while (current <= total){FlushProcess(total, current);if (current >= total) break;//下面的代码是模拟资源的下载usleep(5000);current += speed[rand() % sizeof(speed)];// current 假如等于99,加上speed等于101,就跳出了循环// 那实际上current = 100,这次的进度条还没刷新if (current >= total) current = total;}
}
3. 添加旋转光标
旋转光标的存在原因:可能因为你网速的原因,那段时间的下载量不足以让警号和比率增长,那为了让用户看到实际上确实在下载,就需要用到旋转光标。只要调用了
FlushProcess
函数,光标就会去旋转
- 这里可以在
main.c
中将current += speed
注释掉就能看到效果。ctrl + c
可以强制停止
4. 完整代码
4.1 main.c
#include"process.h"
#include<time.h>
#include<stdlib.h>double total = 1024.0; //下载的总量;
double speed[] = {1.0, 0.5, 0.3, 0.2, 0.01, 0.001}; // 下载速度-网速void download()
{srand(time(NULL));double current = 0.0;while (current <= total){FlushProcess(total, current);if (current >= total) break;//下面的代码是模拟资源的下载usleep(5000);current += speed[rand() % sizeof(speed)];if (current >= total) current = total;}
}
int main()
{download();// 进行process的函数调用// process();return 0;
}
4. 2 process.c
#include"process.h"#define SIZE 101 //100个警号+\0
#define STYLE '#'// v2: 根据进度,动态刷新一次进度条void FlushProcess(double total, double current)
{// 旋转光标const char* label = "|/-\\";int len = strlen(label);static int index = 0;char buffer[SIZE];memset(buffer, 0, sizeof(buffer));double rate = current*100.0/total;int num = (int)rate;//打印警号的数量// 根据下载的进度,刷新一次进度条int i = 0;for (; i < num; i++)buffer[i] = STYLE;printf("[%-100s][%.1lf%%][%c]\r", buffer, rate, label[index++]); //.1:保留一位小数fflush(stdout);index %= len;if (num >= 100) printf("\n");
}
5. 补充(解耦)
- 想要这个进度条和实际的业务彻底解耦
- 举个例子, 上面是用到
下载资源
这个业务上,那以后如果要用到上传或加载
呢?那每一份上传,加载
的进度条代码本身都需要把FlushProcess
函数给带上吗? - 这样的代码是不是耦合度比较高,下面的代码就用到了
回调的方式
进行进度条的刷新 - 假如我今天更改了
FlushProcess
函数名,用回调函数
就不会对download
的逻辑造成任何影响,只需要更改下download
所传参数的函数名即可
#include"process.h"
#include<time.h>
#include<stdlib.h>//函数指针类型
typedef void (*call_t)(const char*,double,double);double total = 1024.0;
//double speed = 1.0;
double speed[] = {1.0, 0.5, 0.3, 0.02, 0.1, 0.01};//回调函数
void download(int total, call_t cb)
{srand(time(NULL));double current = 0.0;while(current <= total){cb("下载中", total, current); // 进行回调if(current>=total) break;// 下载代码int random = rand()%6;usleep(5000);current += speed[random];if(current>=total) current = total;}
}void uploadload(int total, call_t cb)
{srand(time(NULL));double current = 0.0;while(current <= total){cb("上传中", total, current); // 进行回调if(current>=total) break;// 下载代码int random = rand()%6;usleep(5000);current += speed[random];if(current>=total) current = total;}
}int main()
{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(500.0, FlushProcess);return 0;
}