Liunx 小程序之进度条
效果
先来看效果,这其实是一个动态的进度条,后有源码,运行即可:
前提条件
在制作之前有两个前提条件需要了解
回车和换行
这个概念,一般人均会混为一谈,而程序员因为熟知 '\n'
,所以深刻理解 换行符 是 将当前光标位置换到下一行的开头
而 '\n'
这个字符其实是两个动作,一个是 回车 ,一个是 换行
- 换行 :顾名思义,只是将当前光标位置换进下一行,但是其在下一行的什么位置,取决于之前上一行的所在位置
- 回车 :这是 将光标置于此行开头位置
所以一般来说, 换行符 所完成的是这 两个动作 的总和
而在 C 语言里,只 回车 为 '\r'
,所以有些人会写出 "\r\n"
这样的代码,这时候的 '\n'
就只表示 换行 了
缓冲区
有了上面 回车 和 换行 的解释,对于 缓冲区 ,我们需要对比观察这样两份代码:
c
// 第一份代码:
#include <stdio.h>
#include <unidstd.h>
int main()
{
printf("Hello World...\n");
sleep(3);
return 0;
}
c
// 第二份代码:
#include <stdio.h>
#include <unidstd.h>
int main()
{
printf("Hello World...");
sleep(3);
return 0;
}
不同之处 仅在于代码 printf("Hello World...");
打印的字符串是否含有 '\n'
换行符
那么请将这两份代码放在命令行终端进行验证,分别编译运行后,眼睛看到的结果将不一样:
- 第一份会将
Hello World...
打印出来并空出一行,休眠 3 秒 - 第二份会先休眠 3 秒,再打印出
Hello World...
这时由于没有 换行符 ,下一个 命令行提示符 会紧跟字符串打印出来
请注意,这是肉眼观察到的结果,那为什么第二份代码肉眼观察是先休眠呢?
首先我们知道,C 语言对上面的两份代码一定是 顺序执行 的,也就是说 均为先打印,后休眠 ,既如此,那第二份代码的字符串为何等休眠后才出现呢?真正的字符串又被打印在了哪里?
首先,缓冲区可以看作是内存里的一块空间; printf
函数也并不是把字符串直接显示在显示器屏幕上,而是将字符串拷贝进这块缓冲区,再由缓冲区刷新到显示器屏幕上
第二段程序由于没有 '\n'
,会将字符串一直放在缓冲区里,直到程序结束,自动冲涮缓冲区时才被刷新到屏幕上
而 目前来说 缓冲区的刷新策略是:
- 含有
'\n'
,会将'\n'
前面所有的内容都刷新出来(行刷新) - 缓冲区满即刷新
- 使用函数
fflush(stdout);
强制刷新
倒计时
有了上面的解释,咱就可以先着手写一个 倒计时 ,代码如下:
c
// pre-test.c 源文件
#include <stdio.h>
#include <unistd.h>
int main()
{
for (int i = 10; i >= 0; --i)
{
printf("倒计时:%2d\r", i);
fflush(stdout);
sleep(1);
}
printf("\n");
return 0;
}
bash
# makefile 文件
bin=pre-test
src=pre-test.c
$(bin):$(src)
gcc $^ -o $@ -std=c99
.PHONY:clean
clean:
rm -f $(bin)
试一试咯 ^ ^
进度条
纯进度条
我们先完成一个 纯进度条 的代码:
c
#define Length 101
#define Style '#'
const char* lable = "|/-\\";
void ProsBar()
{
char bar[Length];
memset(bar, '\0', sizeof(bar));
int cnt = 0;
int len = strlen(lable);
while (cnt <= 100)
{
printf("[%-100s][%3d%%][%c]\r", bar, cnt, lable[cnt % len]);
fflush(stdout);
bar[cnt++] = Style;
usleep(20000);
}
printf("\n");
}
如果能看懂前面 倒计时 的代码,那这里的代码也就可以看懂了,原理都一样,如果运行你会发现,这是个自主加载的进度条,不表示任何事物的进度,那怎么行,接下来我们模拟 下载的进度条
模拟下载的进度条
只要将 纯进度条 看懂,那接下来的代码也一定可以看懂,其实原理和 倒计时 一样,代码不做解释
下面我将把代码写成结构化形式,利用 make
自动化构建
Progressbar.h
c
#pragma once
#include <stdio.h>
#include <unistd.h>
// 定义函数指针,用于回调函数
typedef void(* callback_t)(double, double);
void ProsBar(double total, double current);
Progressbar.c
c
#include "Progressbar.h"
#include <string.h>
#define Length 101
#define Style '#'
const char* lable = "|/-\\";
void ProsBar(double total, double current)
{
static char bar[Length] = { 0 };
// memset(bar, '\0', sizeof(bar));
static int cnt = 0;
int len = strlen(lable);
double rate = (current * 100.00) / total;
int loop_count = (int)rate;
while (cnt <= loop_count)
{
bar[cnt++] = Style;
// usleep(10000);
}
printf("[%-100s][%.2lf%%][%c]\r", bar + 1, rate, lable[(cnt - 1) % len]);
fflush(stdout);
if (cnt >= 100)
{
cnt = 0;
memset(bar, '\0', sizeof(bar));
}
}
main.c
c
#include "Progressbar.h"
void Download_Simulation(callback_t cb)
{
double FileSize = 100 * 1.0; // 文件大小
double Current = 0.0; // 下载进度
double BandWidth = 1.0; // 网络带宽
printf("Download start!\n");
while (Current <= FileSize)
{
cb(FileSize, Current);
Current += BandWidth;
usleep(50000);
}
printf("\nThe file size is %.2lf MB\nDownload complete!\n", FileSize);
}
int main()
{
// 回调函数
Download_Simulation(ProsBar);
return 0;
}
makefile
bash
bin=Progressbar
src=main.c Progressbar.c
$(bin):$(src)
gcc $^ -o $@
.PHONY:clean
clean:
rm -f $(bin)