自制第一个Linux小程序——进度条 进度条1、两个储备知识以及编写一个demo1.1、回车与换行是一回事吗1.2、缓冲区的简单认识1.3、demo2、制作进度条2.1、第一个版本的进度条2.2、第二个版本的进度条2.2.1、完善进度条内容2.2.2、网速的不稳定性2.2.3、多文件下载2.3、两种补充设计1、两个储备知识以及编写一个demo1.1、回车与换行是一回事吗回车与换行不是一回事回车光标回到当前行的开头换行光标新起一行我们之前在C/C代码中使用的\n std::endl其实是回车 换行。回车的转义字符是\r回车加换行的转义字符是\r\n。1.2、缓冲区的简单认识给出一段C语言代码#includestdio.h#includeunistd.hintmain(){printf(Hello world.\n);// \n与\r\n的效果一样sleep(1);return0;}没有循环语句和分支语句的情况下C语言在每一个函数域中执行代码都是从上往下执行的。也就是说上面代码中printf函数先于sleep函数执行。我们在Linux上运行一下确实是先有字符串再停顿了一会儿。对于这段代码我们把\n去除#includestdio.h#includeunistd.hintmain(){printf(Hello world.);sleep(1);return0;}上面代码中printf函数也应该是先于sleep函数执行。但如果我们把上面程序放在Linux环境下跑的时候我们会发现我们等了一秒空白Hello world.才出现。这似乎与C语言从上往下执行的规则不符。printf函数一定是先于sleep函数执行的只是Hello world.字符串没有显示到屏幕上。那么字符串去哪了呢答案是进入了缓冲区被缓存起来了之后程序结束字符串才被刷新到了屏幕上。所以\n与\r\n除了有回车换行的功能还可以立即将缓冲区里的内容刷新到屏幕上。其实还有一个函数具有立即刷新的功能fflush。#includestdio.h#includeunistd.hintmain(){printf(Hello world.);fflush(stdout);//sleep(1);return0;}这么一修改程序也能达到先显示字符串后等待的效果。1.3、demo我们尝试编写一个倒计时小程序#includestdio.h#includeunistd.hintmain(){for(intcnt9;cnt0;cnt--){printf(%d\r,cnt);sleep(1);}printf(\n);return0;}为了能够达到数字在屏幕上跳动切换的效果我们期望打印完一个数字时光标回到最开始继续打印下一个数字以覆盖前一个数字同时加上sleep函数。可是我们在Linux上运行程序等了老半天还没出现一个数字最后程序结束前才出现一个0。这是怎么回事原来数字全部先写入了缓冲区而\r没有刷新缓冲区的功能所以数字没有刷新出来我们也就看不到。所以我们需要手动刷新加上一个fflush函数#includestdio.h#includeunistd.hintmain(){for(intcnt9;cnt0;cnt--){printf(%d\r,cnt);fflush(stdout);sleep(1);}printf(\n);return0;}数字不显示的问题是解决了我们却遇到了另外一个问题当我们把cnt修改成10运行程序时我们会发现跳动的数字后面诡异地跟着一个0然后程序倒计时结束后回显一个诡异的00。这又是怎么回事要知道程序向显示器中写入的10是1和0两个字符0字符未被覆盖所以直到程序结束之前0会一直被保留。还记得吗printf格式化输出将数据转化成字符scanf格式化输入将字符转化成数据键盘是一种字符设备。修改的方法是将%d改为%-2d其中2表示占两个字符的位置这样就可以刷新掉0。**-**表示左对齐。#includestdio.h#includeunistd.hintmain(){for(intcnt9;cnt0;cnt--){printf(%-2d\r,cnt);//fflush(stdout);sleep(1);}printf(\n);return0;}2、制作进度条有了上面的知识我们就可以制作第一个版本的进度条。2.1、第一个版本的进度条// main.c#includeprocess.hintmain(){Process();return0;}// process.h#pragmaonce#includestdio.h#includeunistd.h#includestring.hvoidProcess();// poocess.c#includeprocess.hvoidProcess(){// 写一个进度条包含// 表示进度条百分比旋转图标intcnt0;charstr[102];// 表示进度的字符串将会依次放入memset(str,\0,sizeof(str));constchar*flag|/-\\;// 旋转图标intlenstrlen(flag);while(cnt100){printf([%-100s][%3d%%][%c]\r,str,cnt,flag[cnt%len]);// 打印fflush(stdout);// 刷新str[cnt];usleep(100000);cnt;}printf(\n);}当前制作的进度条有三个要素的增长表示进度的增长百分比记录进度旋转图标表明进度正在进行即使速度很慢但是当前的进度条能否真正满足实际场景的需求吗答案是不能的。我们要根据实际任务的进度来实时更新进度条。所以我们将制作第二个版本的进度条。2.2、第二个版本的进度条我们可以虚构出一个场景有一个下载任务软件包的大小是1024下载速度固定是1。那么下载每完成1进度条都要更新一次。我们就可以先调用下载函数进行下载每一次current进度推进时我们就调用进度条函数更新进度条。// main.c#includeprocess.hdoublegtotal1024.0;// 总任务doublegspeed1.0;// 速度voidDownLoad(doublegtotal){intcurrent0;// 已经完成了多少if(currentgtotal)return;// 保护措施while(currentgtotal){Process(gtotal,current);// 每推进一个单位更新一次进度条currentgspeed;usleep(90000);}if(currentgtotal)printf(\n);// 换行}intmain(){DownLoad(gtotal);return0;}// process.h#pragmaonce#includestdio.h#includeunistd.h#includestring.hvoidProcess(doubletotal,doublecnt);// process.c#includeprocess.h#defineFlagvoidProcess(doubletotal,doublecnt){// 1. 计算比率doubleratecnt*100/total;// 2. 填充进度条charstr[102];memset(str,\0,sizeof(str));inti0;for(;i(int)rate;i){str[i]Flag;}// 3. 制作动态图标constchar*flag[]{,.,..,...,..,.,};intlensizeof(flag)/sizeof(flag[0]);staticintbegin0;// 4. 打印进度条printf([%-100s][%5.1lf%%][%-3s]\r,str,rate,flag[begin%len]);begin;fflush(stdout);}现在一个与下载任务绑定的基本进度条就已经初步创建成了。但是还有一些地方可以完善2.2.1、完善进度条内容进度条不仅可以有进度的直观显示、记录百分比、动态图标还可以有目标文件总大小、已经下载了的大小、网速及网速的单位。// process.c#includeprocess.h#defineFlagvoidProcess(doubletotal,doublecnt,doublespeed,constchar*speedinfo)// 函数参数加上速度与速度单位{// 1. 计算比率doubleratecnt*100/total;// 2. 填充进度条charstr[102];memset(str,\0,sizeof(str));inti0;for(;i(int)rate;i){str[i]Flag;}// 3. 制作动态图标constchar*flag[]{,.,..,...,..,.,};intlensizeof(flag)/sizeof(flag[0]);staticintbegin0;// 4. 制作进度条printf([%-100s][%5.1lf%%][%-3s] | %.1lf/%.1lf, %.1lf%s\r,\ str,rate,flag[begin%len],total,cnt,speed,speedinfo);// 总大小/已经下载的大小, 速度begin;fflush(stdout);}2.2.2、网速的不稳定性文件下载时网速其实是不稳定的时好时坏。我们可以创建一个网速的数组然后利用随机数随机选取一个网速以达到模拟真实情况下网速不稳定的效果。我们还需注意一点的是由于网速是随机的下载的量可能超过文件总量所以我们要做一步判断如果已下载的量小于文件总量正常下载如果已下载的量大于等于文件总量那么文件已经下载完成了所有的进度都应调整成100%// main.c#includeprocess.hdoublegtotal1024.0;//double gspeed 1.0;voidDownLoad(doublegtotal){intcurrent0;if(currentgtotal)return;doublespeed[]{0.01,0.05,0.1,0.2,0.5,1.1,1.8,2.12,5.8,10.5,20.7,35.4};// 随机时间的数组while(1)// 设置成死循环{doublecnt_speedspeed[rand()%(sizeof(speed)/sizeof(speed[0]))];if(currentgtotal)// 没下完正常下载{Process(gtotal,current,cnt_speed,MB/s);currentcnt_speed;usleep(90000);}else// 已经下完了100%结束循环{Process(gtotal,gtotal,cnt_speed,MB/s);break;}}if(currentgtotal)printf(\n);}intmain(){srand((unsignedint)time(NULL));DownLoad(gtotal);return0;}2.2.3、多文件下载在某些场景下我们需要下载多个文件那么我们就可以调用多个下载函数。// main.c// ...intmain(){srand((unsignedint)time(NULL));DownLoad(gtotal);DownLoad(66.7);DownLoad(2048.0);DownLoad(999.0);DownLoad(120.4);return0;}2.3、两种补充设计函数指针我们在下载函数DownLoad()中显示调用了进度条函数Process()这显然是增加了代码的耦合度的因为如果我们修改了Process()DownLoad()也需要跟着修改。C中我们可以用仿函数和lambda表达式但是C语言中我们只能用函数指针。// process.htypedefvoid(*flush)(...)// 重命名函数指针类型// main.cvoidDownLoad(...,flush cb){cb(...);// 回调函数}printf()可以进行彩色输出printf()可以进行彩色输出包括但不限于字体彩色、背景彩色。printf()的彩色输出就留给我们自行探索吧