在Linux字符界面中,使用yum、apt下载东西时会有一个图形化的进度条,可以告诉我们任务的执行进度。
我们也可以通过C语言实现一个类似的进度条,并且可以做得更加美观。以后我们自己写的程序需要显示进度时就可以去调用我们自己实现的进度条。
进度条实现
下面这个是apt的进度条,可以看到这个进度条是通过在 [ ] 中填充 '#' 来实现的进度递增,同时还会显示当前进度的百分比。
实际上要实现这个进度条,并不复杂。我们可以创建一个数组,每次进度递增就往里面填充 '#'。
#include <stdio.h>
#include <unistd.h>
char bar[101] = ""; //100个#号,一个\0
int main()
{
int rate = 0; //速率
while(rate <= 100)
{
usleep(50000) //睡眠时间,不让进度条很快就执行完
printf("[%s]\n", bar);
bar[rate++] = '#';
}
return 0;
}
这个程序的执行结果如下:
可以发现我们的进度条是打印出来了,但是还存在很多问题。首先这个进度条并不是在同一行打印出来的,并且 ']' 是紧跟最右边的 '#' 的。
我们在打印进度条时,使用了 '\n' 换行,这是不正确的。我们应该使用 '\r' 回车。
'\n' 换行:使光标下移一格
'\r' 回车:使光标回到本行首位
接下来,我们对代码进行改进。
#include <stdio.h>
#include <unistd.h>
char bar[101] = ""; //100个#号,一个\0
int main()
{
int rate = 0;
while(rate <= 100)
{
usleep(50000);
printf("[%-100s]\r", bar);
bar[rate++] = '#';
}
return 0;
}
可以看到进度条确实是在同一行执行了,但是程序打印时并没有一个一个 '#' 的进行打印。而是一段一段的。
这是因为缓冲区没有立刻刷新,当时使用 '\n' 没有这个问题,是因为换行时就会刷新缓冲区。但是 '\r' 回车并不会刷新缓冲区,我们想实现立刻刷新缓冲区可以使用 fflush() 函数。
#include <stdio.h>
#include <unistd.h>
char bar[101] = ""; //100个#号,一个\0
int main()
{
int rate = 0;
while(rate <= 100)
{
usleep(50000);
printf("[%-100s]\r", bar);
fflush(stdout); //stdout标准输出
bar[rate++] = '#';
}
printf("\n"); //换行
return 0;
}
我们已经实现了一个正常的进度条了,现在可以对进度条进行美化加工,让它变得更加美观。
#include <stdio.h>
#include <unistd.h>
char bar[101] = ""; //100个#号,一个\0
char* table = "|/-\\";
int main()
{
int rate = 0;
while(rate <= 100)
{
usleep(50000);
printf("[%-100s][%d%%][ %c ]\r", bar, rate, table[rate%4]);
fflush(stdout);
bar[rate++] = '#';
if(rate < 100)
bar[rate] = '>';
}
printf("\n");
return 0;
}
现在进度条实现了查看进度百分比和旋转光标,已经可以和 apt 的进度条媲美了。但是还不够,我们还可以给进度条加上颜色实现彩色的进度条。
C语言输出颜色-CSDN博客
#include <stdio.h>
#include <unistd.h>
#define NONE "\033[m"
#define LIGHT_GREEN "\033[1;32m"
#define LIGHT_CYAN "\033[1;36m"
#define LIGHT_PURPLE "\033[1;35m"
char bar[101] = ""; //100个#号,一个\0
char* table = "|/-\\";
int main()
{
int rate = 0;
while(rate <= 100)
{
usleep(50000);
printf(LIGHT_GREEN"[%-100s]"NONE LIGHT_CYAN"[%d%%]"NONE LIGHT_PURPLE"[ %c ]"NONE"\r", bar, rate, table[rate % 4]);
fflush(stdout);
bar[rate++] = '#';
if(rate < 100)
bar[rate] = '>';
}
printf("\n");
return 0;
}
现在进度条已经写好了,现在我们要对它进行封装。把它的主体封装起来,方便我们后面去调用它。
创建一个头文件和源文件。因为进度条本身是不知道一件事情的进度目前是多少,进度只有去调用进度条的人才知道,所以进度条的 rate 进度不能封装进去。
ssddffaa@Server-Ubuntu:~/mini-program/ProcessBar$ ls
main.c Makefile processbar.c processbar.h process-debug.out process.out
processbar.h
#pragma once
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BAR 101
#define BODY '='
#define CUR '>'
#define NONE "\033[m"
#define LIGHT_GREEN "\033[1;32m"
#define LIGHT_CYAN "\033[1;36m"
#define LIGHT_PURPLE "\033[1;35m"
extern void processbar(int rate);
processbar.c
#include "processbar.h"
char bar[BAR] = "";
const char* table = "|/-\\";
void processbar(int rate)
{
printf(LIGHT_GREEN"[%-100s]"NONE LIGHT_CYAN"[%d%%]"NONE LIGHT_PURPLE"[ %c ]"NONE"\r", bar, rate, table[rate % 4]);
fflush(stdout);
bar[rate++] = BODY;
if (rate < 100)
bar[rate] = CUR;
if (rate == 101)
memset(bar, '\0', sizeof(bar));
}
main.c
#include "processbar.h"
typedef void (*callbar)(int);
void Download(callbar cb)
{
int total = 1100;
int cnt = 0;
while (cnt <= total)
{
usleep(50000);
int rate = cnt*100 / total;
cb(rate);
cnt += 10;
}
printf("\n");
}
int main()
{
Download(processbar);
return 0;
}
至此,进度条就写完啦。我们后面想在自己的项目中去使用时就可以通过包含头文件的方式去调用进度条函数。