前言:
在日常生活中,我们下载软件,文件,都会都一个进度显示,来告知我们的下载进度,接下来我们可以自己手搓一个进度条,在我们自己写扫雷、贪吃蛇等小游戏时,可以做一个游戏加载进度使用
在手搓进度条之前,我们需要一些知识作为铺垫,这些知识大家或多说少会有了解,但是我为了让大家更好的了解,我会把这些知识再细讲一下,方便理解。
一、回车换行
回车换行并不是一个相同的概念,回车是回车,换行是换行,回车换行是两个概念
假设下面两行是我们的一张纸,我们一行只能写四个字,写完之后,光标(红色竖线)停留在第四格内,一般都是直接按回车换行使光标切换到第二行的第一个位置
1、回车概念
但是如果我们单独回车,光标就会回到第一行的开始
这就是回车
2、换行概念
如果我们单独换行,光标所在列位置不变,直接切换到下一行
这就是换行
所以说我们回车换行,可以说是先回到最左边的位置,再往下跳一行,也可以说是先往下跳一行,再回到最左边的位置
3、相关历史
1、老式键盘
老式键盘就很明确,这个形状就像是先换行再回车
2、老式打字机
在老式打字机的使用时,把纸张放上去,打完一行字的时候,拨动滚轮纸张会往上走完成换行,然后再向左拨动针尖位置完成回车
二、缓冲区
我们对缓冲区这个名字应该已经很熟悉了,或多或少都都有了解,我在帮助大家回忆一下
我们几段代码来演示缓冲区的存在:
1、printf函数里带\n
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello Makefile!\n");
sleep(3);
return 0;
}
编译运行后我们查看现象:
现象是先打印出来Hello World,之后等三秒才会退出程序
2、printf函数里不带\n
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello Makefile!");
sleep(3);
return 0;
现象是前三秒没有打印
三秒后打印出来
毫无疑问的是,我们的代码肯定是先执行printf再执行sleep的,之所以打印的东西在sleep后出现的原因就是缓冲区的存在
我们sleep的三秒中,我们所打印的东西被保存在缓冲区里:
一开始我们要显示的内容会拷贝一份放在缓冲区里,之后再定期刷新,显示到我们的显示器上
在程序结束时,会强制刷新缓冲区,所以我们的Hello World在程序结束时会显示到我们的屏幕上
那为什么我们带上\n就会在sleep之前从缓冲区里刷新到显示器上呢?
刷新缓冲区还有其它三种方式
3、刷新缓冲区
我们在这里只讨论显示器的缓冲区刷新
1、\n
碰到\n立即自动刷新
2、缓冲区满
缓冲区满了自动刷新
3、强制刷新
认识一个新函数,fflush()
flush就是刷新,他的意思就是刷新特定的输出流
演示:
我在这段代码里用fflush强制刷新,我们会发现,即使printf不带\n,我们要打印的东西依然会在sleep之前显示到我们的屏幕上。
三、倒计时
我们了解了回车换行与缓冲区的概念,那我们在写进度条前尝试写一个倒计时出来
我们想要的倒计时模样:
倒计时:
代码实现:
#include<stdio.h>
#include<unistd.h>
int main()
{
int i=10;
for(i=10;i>=0;i--)
{
printf("倒计时:%2d\r",i);
fflush(stdout);
sleep(1);
}
printf("\n");
return 0;
}
我们在打印时,每次打印完一个数字,\r都会使光标回到最开始的位置,然后循环再打印会将原来的覆盖
视频演示:
倒计时演示
四、进度条
做完倒计时,我们开始进入手搓进度条,我们为什么要先做倒计时呢,因为我们要做的进度条,原理和倒计时类似,都是不断地覆盖原来的数据。
我们根据这个GIF,来思考一下我们应该如何制作
1、倒计时模型
大致模型:
2、创建文件
我们先创建三个文件,头文件,函数实现源文件,测试源文件
3、项目自动化构建
首先创建一个makefile文件,进入该文件并写入以下代码
这样我们在编译时一个make命令就可以完成了
4、代码实现
1、初版模型实现
1)Processbal.h文件代码
1 #pragma once
2 #include<stdio.h>
3 //void ForTest();
4 void ProBal();
2)Processbal.c文件代码
1 #include"Processbal.h"
2 #include<unistd.h>
3 #include<string.h>
4 #define Length 101
5 #define Style '='
6 const char*lable="|/-\\";
7 //void ForTest()
8 //{
9 // printf("!!!");
10 //}
11 void ProBal()
12 {
13 char bar [Length];
14 memset(bar,'\0',sizeof(bar));
15 int len=strlen(lable);
16 int cnt=0;
17 while(cnt<=100)
18 {
19 printf("[%-100s][%-3d%%][%c]\r",bar,cnt,lable[cnt%len]);
20 fflush(stdout);
21 bar[cnt++]=Style;
22 usleep(50000);
23 }
24 printf("\n");
25 }
3)main.c文件代码
1 #include"Processbal.h"
2 int main()
3 {
4 ProBal();
5 return 0;
6 }
编译运行:
但是我们这个显然是不够用的,我们只是弄了一个进度条出来,而现实是我们下载的东西是有大小的,包括我们网速大小,我们下载时有对应的带宽,我们呢接下来让他更切合实际。
2、最终版本
1)Processbal.h文件代码
1 #pragma once
2 #include<stdio.h>
3 //void ForTest();
//函数指针方便我们把函数作为参数传给另一个函数
4 typedef void (*callback_t)(double,double);
5 void ProBal(double total,double current);
2)Processbal.c文件代码
1 #include"Processbal.h"
2 #include<unistd.h>
3 #include<string.h>
4 #define Length 101
5 #define Style '='
6 const char*lable="|/-\\";
7 //void ForTest()
8 //{
9 // printf("!!!");
10 //}
11 void ProBal(double total,double current)
12 {
13 char bar [Length];
14 memset(bar,'\0',sizeof(bar));
15 int len=strlen(lable);
16 int cnt=0;
// current的占比,每次推动的百分比
17 double rate=(current*100.0)/total;
18 int loop_count=(int)rate;
19 while(cnt<=loop_count)
20 {
21 bar[cnt++]=Style;
22 }
23 printf("[%-100s][%-3d%%][%c]\r",bar,cnt,lable[cnt%len]);
24 fflush(stdout);
25 }
3)main.c文件代码
1 #include"Processbal.h"
2 #include<unistd.h>
3 double bandwidth=1024*1024*1.0;
4 void download(double filesize,callback_t cb)
5 {
6 double current=0.0;
7 printf("download begin,current:%lf\n",current);
8 while(current<=filesize)
9 {
10 cb(filesize,current);
11 usleep(100000);
12 current+=bandwidth;
13 }
14 printf("\n");
15 printf("download done,filesize:%lf\n",filesize);
16 }
17 int main()
18 {
// 我们可以自己传自己的目标数据大小进行下载
19 download(1024*1024*50*1.0,ProBal);
20 return 0;
21 }
编译运行: