个人主页 : zxctscl
如有转载请先通知
文章目录
- [1. 前言](#1. 前言)
- [2. 回车和换行](#2. 回车和换行)
- [3. 缓冲区](#3. 缓冲区)
- [4. 进度条](#4. 进度条)
-
- [4.1 倒计时设置](#4.1 倒计时设置)
- [4.2 进度条](#4.2 进度条)
-
- [4.2.1 实现简单进度条](#4.2.1 实现简单进度条)
- [4.2.2 进度条完善](#4.2.2 进度条完善)
- [5. 附进度条代码](#5. 附进度条代码)
-
- [5.1 Processbar.h](#5.1 Processbar.h)
- [5.2 Processbar.c](#5.2 Processbar.c)
- [5.3 Main.c](#5.3 Main.c)
- [5.4 Makefile](#5.4 Makefile)
1. 前言
在之前已经了解了 【Linux】vim的使用和 【Linux】编译器-gcc/g++使用还有 【Linux】自动化构建工具-make/Makefile,有了这些工具,这次来实现一个进度条小程序。
2. 回车和换行
换行:是换到新的一行。
回车:是回到最开始。
举个例子:在格子里面写字的时候,第一行写完,要到第二行,此时到的第二行的结尾,这个叫换行。而从第二行结尾回到第二行开头是回车。
也可以先回车到第一行的开始,再换行到第二行。
回车和换行是两个动作。
在老式键盘上面就很明显,而现在的键盘为了省空间就不这样了:
平时在C语言上用到的"\n"叫回车换行,既回车,又换行。
如果想让只回车就用到"\r"
。
有的代码用"\r\n"
表示回车换行,如果在"\r"
和"\n"
同时存在时,"\n"
只是换行。
在老式打字机上面就会有下面这样的拨片,当打完一行之后,得手动把它往回拨,一拨纸就往上走(换行),写入位置就往左走,对应就是回车。
3. 缓冲区
在代码里面把"\n"
去掉:
在编译过程中就会发现,再休眠3秒的时候这个程序并没有输出,而是当程序结束的时候程序才输出。
加"\n"
消息就直接显示出来,然后再sleep。
不加"\n"
这个字符串打印就没有显示出来,先sleep,再到程序退出的时候才显示这个字符串。
那么在不加"\n"
那么printf
和sleep
哪个先运行呢?
C语言在执行代码时候,默认从上往下执行,这个叫顺序结构。
printf
只是没有把数据在显示器上显示出来,并不是没有执行。只是在sleep
期间这个字符串没有显示出来而已。
那么在sleep
期间字符串在哪里呢?
它被保存到缓冲区里面。它就是一块内存空间。
当printf
的时候,把字符串拷贝到缓冲区里面,然后定期把数据刷新到显示器上面,此时就能看到这个字符串了。
不带"\n"
的时候,字符串就在缓冲区里,当return 0
时,程序结束时,一般要自动冲刷缓冲区。
想要在程序结束之前就刷新缓冲区,有3种方式:
1. "\n"
直接就把缓冲区数据就刷出来了。包含"\n"
之前的全部刷新出来。
举个例子:
会发现先出来"\n"之前的,剩下的程序结束才出来:
2. 缓冲区满了进行刷新
3. 强制刷新。
ffush
把特定文件流进行刷新:
在Linux下一切皆文件,可以把显示器当文件看。
在C语言中程序在启动时默认会打开三个输入输出流:
stdin
对应的设备是键盘,stdout
和stderr
对应的是显示器。
在系统中不管是设备还是文件一律都是FILE。
一般打印输出用到的是stdout,显示到显示器上。
为什么会默认打开这输入输出流?
方便用户进行输入和输出。一般使用计算机的都有输入和输出的需求,所以一般就默认打开输入输出流,就不用在写代码打开上面键盘和显示器这些设备了。
在代码中使用一下fflush
:
bash
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 printf("hello world,hello...");
7 fflush(stdout);
8 sleep(3);
9 return 0;
10 }
编译发现缓冲区内容就直接冲刷出来了:
为什么会存在缓冲区?
缓冲区提高了用户效率。以前的printf是往硬件上写的,现在直接写到内存了。从内存拷贝内存数据的效率,肯定比从硬件拷贝到内存的效率高。
它刷新的次数越少,单次刷新的数据量越大,效率越高。
按行刷新是方便用户阅读。
4. 进度条
4.1 倒计时设置
假设将格子是光标的位置,一般在输入的时候就会是下面这样的:
但是如果想要实现光标在同一个位置,实现倒计时的感觉,就行下面这样:用8会覆盖这个9。
但是8会覆盖这个9后,光标会往后走,想要把在8的位置输出7,光标就得回到8的位置,7就把8覆盖,依此类推,就能实现一个动态的倒计时。
就是在同一个位置不停的覆盖,就能实现动态效果。
用代码来实现一下倒计时的感觉:
但这个代码输出并不是我们所想要的输出:
来修改一下代码,加上"\r"
:
这里发现暂停一段时间,数据并没有显示出来:
"\r"
相当于我们写了一个数回到最开始,就清了一个数,所以最后什么也不显示。
那就直接加一个强制刷新缓冲区:
bash
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int count=9;
7 while(count>=0)
8 {
9 printf("%d\r",count);
10 fflush(stdout);
11 count--;
12 sleep(1);
13 }
14 return 0;
15 }
来看看效果:
既然能够覆盖一个字符,那么肯定也能覆盖一个字符串。
bash
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int count=9;
7 while(count>=0)
8 {
9 printf("倒计时:%d\r",count);
10 fflush(stdout);
11 count--;
12 sleep(1);
13 }
14 printf("\n");
15 return 0;
16 }
来看看效果图:
如果从10开始:
就会出现这样的效果:
数字10,实际在显示器上显示的是字符,它转化成1字符和0字符。当在打印的9时候字符长度变短了,0就没有办法覆盖,就会一直在。
所以这里用%2d:
这时候就没有问题了:
4.2 进度条
这里用多文件来实现,Processbar.h用来声明,Processbar.c用来实现方法,Main.c用来调用Processbar.c里面的方法,再使用Makefile来实现自动化调用。
先写出基本的框架逻辑:
在编译的时候不用.h文件,因为在.c文件中已经包含.h是在当前目录下的,在编译时候会展开头文件的。
在Makefile里面写:
bash
1 processbar:Main.c Processbar.c
2 gcc -o $@ $^
3 .PHONY:clean
4 clean:
5 rm -f processbar
测试一下代码:
4.2.1 实现简单进度条
先画出进度条的示例:
进度条在增加的同时,当前的进度也在变化,光标也在不停的旋转。
这个进度条实现的时候,缓冲区的长度从0%到100%,但是还得考虑"\0",所以长度定义为101。
把缓冲区清空就用到memset
。
想要在缓冲区里面写特殊符号,就直接用define
:#define Style '#'
。
用循环来实现动态进度条打印,直接打印相对应的字符串,和倒计时一样用fflush(stdout);
来刷新缓冲区,随着时间的增加,进度条也在不断增加:
来看看效果:
发现这里打印时间太慢了。
使用usleep
,它休眠的时间比sleep
的小,所以这里就用usleep
来进行休眠。
然后将字符串输出改为左对齐,加上进度条对应的比率:
来看看效果:
4.2.2 进度条完善
但是一般进度条不会单独出现,只有当我们下载一个文件同时出现。
这里就先模拟下载过程,就在在Main.c
写一个download
来实现。一般在下载一个文件的时候,会有下载文件的大小和下载到多少,和网络的带宽。
分别用变量定义一下文件大小,当前下载进度和带宽:
bash
double filesize = 1024*1024*1024*1.0;
double current = 0.0;
double bandwidth = 1024*1024*1.0;
还可以加上一个开始下载的提示:
bash
printf("download begin,current: %lf\n",current);
和下载完成的提示:
bash
printf("\ndownload done,filesize: %lf\n",filesize);
在下载过程中把要下载的文件大小,和目前已经下载的大小传给进度条ProcBar
,让进度条时时交互。
在Processbar.h
中记得把在Processbar.c
使用的打印进度条函数声明一下:
bash
void ProcBar(double total,double current);
在Processbar.c
中,就和上面的简单实现进度条一样,把文件的长度,和目前下载的长度传进去:
bash
void ProcBar(double total,double current)
这里加了当前下载的进度百分比
double rate=(current*100.0)/total;
在循环的时候得将前下载的进度输出,循环的时候得把double
rate的类型转为int
:
bash
int loop_count=(int)rate;
最后打印出当前下载的进度,还得冲刷缓冲区:
bash
printf("[%-100s][%.1lf%%][%c]\r",bar,rate,lable[cnt%len]);
fflush(stdout);
来看看效果
在Processbar.h
中定义一个函数指针类型:
bash
typedef void(*callback_t)(double,double);
然后在Main.c
里面将download
改一下,可以根据文件的大小来下载:
bash
void download(double filesize,callback_t cb)
在download函数里面直接执行:cb(filesize,current);
方法。
为什么要这么写?
在之后如果用户写一个图形化界面的进度条,就能在下载的时候同时更新图形化界面。
也可以让进度条加上颜色,只需要在网上找一个关于用C语言输出有颜色相关的内容也就会有了。
这里是加了颜色效果:
bash
printf("\033[40;44m[%-100s][%.1lf%%][%c]\r\033[0m",bar,rate,lable[cnt%len]);
5. 附进度条代码
5.1 Processbar.h
bash
1 #pragma once
2
3 #include<stdio.h>
4
5 typedef void(*callback_t)(double,double);
6 void ProcBar(double total,double current);
7 //extern void ForTest();
5.2 Processbar.c
bash
1 #include "Processbar.h"
2 #include <string.h>
3 #include <unistd.h>
4
5 #define Length 101
6 #define Style '#'
7
8 const char *lable = "|/-\\";
9
10
11 void ProcBar(double total,double current)
12 {
13 char bar[Length];
14 memset(bar,'\0',sizeof(bar));
15 int len=strlen(lable);
16
17 int cnt = 0;
18 double rate=(current*100.0)/total;
19 int loop_count=(int)rate;
20 while(cnt<=loop_count)
21 {
22 bar[cnt++]=Style;
23
24 }
25
26 //没有加颜色前的输出 printf("[%-100s][%.1lf%%][%c]\r",bar,rate,lable[cnt%len]);
printf("\033[40;44m[%-100s][%.1lf%%][%c]\r\033[0m",bar,rate,lable[cnt%len]);
27 fflush(stdout);
28 }
5.3 Main.c
bash
1 #include"Processbar.h"
2 #include <unistd.h>
3
4 double bandwidth = 1024*1024*1.0;
5 void download(double filesize,callback_t cb)
6 {
7 double current = 0.0;
8
9
10 printf("download begin,current: %lf\n",current);
11 while(current<=filesize)
12 {
13 cb(filesize,current);
14 usleep(100000);
15 current+=bandwidth;
16
17 }
18 printf("\ndownload done,filesize: %lf\n",filesize);
19 }
20
21
22 int main()
23 {
24 download(101*1024*1024,ProcBar);
25 // ProcBar(100.0,56.9);
26
27 // ProcBar(100.0,1.0);
28 // ProcBar(100.0,99.9);
29
30 // ProcBar(100.0,100);
31 //ForTest();
32 return 0;
33 }
~
5.4 Makefile
bash
1 processbar:Main.c Processbar.c
2 gcc -o $@ $^
3 .PHONY:clean
4 clean:
5 rm -f processbar
有问题请指出,大家一起进步吧!!!