这篇博客要综合利用以前的知识,来实现一个进度条程序~
目录
换行&回车
缓冲区
实现简单的倒计时
实现进度条
version1
version2
在开始写这个小程序之前,我们先学习一些预备知识:
换行&回车
缓冲区
在我们运行这个程序时,并没有直接打印出“hello bit,hello world...”,而是当程序运行结束后才显示出来,但是这并不代表这句打印没有执行,而是没有显示出来而已。
那么,在我sleep期间,字符串在哪里?
答案就是被保存在叫做缓冲区的地方,就是一块内存空间,当程序结束时,一般会自动刷新缓冲区到字符设备(显示器),另外,如果程序遇到‘/n’的时候,也会刷新缓冲区。如果想要强制刷新,可以使用fflush命令。
那么,为什么要有缓冲区呢,为什么按行刷新?为了提高效率,方便人类阅读,人类读信息都是按行读的,是一种尊重用户的表现。
实现简单的倒计时
在实现倒计时中,我们想要达到的效果是依次显示数字,并且下一个覆盖前一个数字,创建test.c文件:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int main() 5 { 6 int cnt=10; 7 while(cnt >=0) 8 { 9 printf("倒计时:%2d/r",cnt); 10 fflush(stdout); 11 cnt--; 12 sleep(1); 13 } 14 printf("/n"); 15 16 return 0 ; 17 }
我们实现的效果是10,9,...1,0的倒计时效果,其中,
printf("倒计时:%2d/r",cnt);
这句代码中/r是回车,在一次打印完成后,光标回到最前面,继续打印下一个值,覆盖掉上一次的值,用%2d的格式打印的原因是,每次打印两个字符,以防某个字符一直在屏幕上显示。使用fflush(stdout)强制刷新到显示器上。
此外,我们还写了makefile自动化构建工具:
mycode:test.c 2 gcc -o $@ $^ 3 .PHONY:clean 4 clean: 5 rm -f mycode
至此完成了简单的倒计时效果。
实现进度条
version1
在实现进度条中,我们要创建三个文件:Processbar.h,Processbar.c,Main.c,Main.c负责将Processbar.h中的方法进行调用,最终想达到的效果如下图:
在Processbar.h中,我们定义了Process函数声明;
#pragma once #include <stdio.h>extern void Process();
在Processbar.c中实现了Process:
#include "Processbar.h" #include <string.h> #include <unistd.h> #define Length 101 #define Style '#' const char * label = "|/-//"; // version1 void Process() { char bar[Length]; memset(bar,'/0',sizeof(bar)); int cnt = 0; int len =strlen(label); while(cnt <= 100) { printf("[%-100s][%3d%%][%c]/r",bar,cnt,label[cnt%len]); fflush(stdout); bar[cnt++]=Style; usleep(20000); } printf("/n"); }
在上面这段程序中,我们用bar数组来实现进度条,大小Length设为101(包括最后的‘/0’),进度条符号为‘#’。[%-100s]表示字符串靠左对齐,保证了‘#’从左边往右增长。通过循环遍历label所指向的数组来实现旋转光标的效果。
需要注意的是,因为每次使用‘/r’来实现覆盖的效果,但是‘/r’不能让每次结果刷新到显示器上,需要用fflush(stdout)来刷新。usleep()函数的单位是微秒(包含unistd.h头文件)。
在Main.c中,调用Process.h:
#include "Processbar.h" int main() { Process(); return 0; }
最终的实现效果:
version2
上面我们单独写了一个进度条程序,但是,进度条会单独出现吗?肯定不会!它要和具体的场景结合。
假设我们要完成一个下载的场景:下载指定大小的文件,
在Main.c中,我们写出download()这样一个函数:
double bandwidth = 1024*1024*1.0; //download void download(double filesize,callback_t cb) { //double filesize = 100*1024*1024*1.0; double current = 0.0; printf("download begin,current:%lf/n",current); while(current <= filesize) { cb(filesize,current); //从网络中获取 current += bandwidth; usleep(10000); } printf("/ndownload done,filesize:%lf/n",filesize); }
其中,bandwidth是我们假设的带宽(可以理解为下载速度),download函数的filesize参数是我们要下载的文件大小,第二个参数是进度条打印函数,callback_t是函数指针,其定义在Processbar.h中,
typedef void(*callback_t)(double,double);
在while循环中,根据filesize和current的大小,打印出当前下载进度的进度条,打印完成后,继续下载,current继续增加,然后再打印下一次的进度条并覆盖之前的进度条。
在Processbar.h中,有如下声明:
#pragma once #include <stdio.h> typedef void(*callback_t)(double,double); void Process(double total,double current);
在Processbar.c中,定义了Process函数:
//version2void Process(double total,double current){ char bar[Length]; memset(bar,'/0',sizeof(bar)); int cnt = 0; int len =strlen(label); double rate = current*100.0/total; int loop_count = (int)rate; while(cnt <= loop_count) { bar[cnt++]=Style; } printf("[%-100s][%.1lf%%][%c]/r",bar,rate,label[cnt%len]); fflush(stdout); }
根据总文件大小total和当前已下载大小current,打印出当前的进度条,例如,当total=100、current=36时,会打印出如下内容:
在Main.c中,将Process函数作为实参传给download()函数,完成下载。
int main(){ download(100*1024*1024,Process); download(50*1024*1024,Process); download(80*1024*1024,Process); download(1*1024*1024,Process); download(18*1024*1024,Process); return 0;}
模拟下载了多个文件,其效果如下: