Linux第一个小程序-进度条-初步理解缓冲区
1. 换行和回车
我们在C语言中学习的字符\n实际上是由两个部分组成的,分别是换行和回车。
在以前的打字机上,打字的时候,打满一行以后,打字机需要换行进行打印,换行以后打印的位置还是在那一行的最结尾,但是我们应当要从行首进行打印,此时就需要先回到这一行的行首,这个动作就叫做回车。

在C语言中的\r的作用就是回车(回到这一行的行首),而\n在C语言中的作用是回车+换行。
2. 缓冲区
接下来看这两段代码:


在上面代码中,sleep(3)函数的意思是,代码运行到这里时,休眠三秒,然后再往下运行,使用这个函数需要包含头文件unistd.h,具体介绍可以通过man 3 sleep进行查询。

这两段代码,逻辑上来说,结果应该是一摸一样的,实际上最后的结果也确实是一样的,但是不一样的是,第一张图的代码编译运行后,123456789马上就会被打印出来,而第二张图的代码运行后,123456789将会隔3秒后再运行。
这是为什么呢?原因在于其实我们使用printf时,它并不是马上就打印出来的,在使用printf时,它会先将我们需要打印的内容存在一个缓存区里面,当下面几种情况下,会立即将缓存区内的内容刷新到屏幕上:
-
当缓存区内需要进行换行时:也就是当有一个\n进入了缓存区时,缓存区中的这一行内容将会立即被刷新到屏幕上。
-
当缓存区存满时:也就是printf的缓存区已经被需要打印到屏幕上的信息存满了,此时缓存区的内容会被立即刷新到屏幕上,然后接收后面的内容。
-
当程序即将结束时:当程序马上要退出的时候,此时缓存区确定不会再有新的内容进来了,就会将缓存区中的内容立即刷新到屏幕上。
-
当使用fflush函数强制刷新时:我们可以使用fflush(stdout)强制刷新缓存区,使里面的内容立即被打印到屏幕上。
关于流,在C语言程序运行的时候,默认打开了三个流,分别是:标准输入流stdin、标准输出流stdout、标准错误流stderr,其中,stdin从键盘输入的流,stdout和stderr都是默认输出到屏幕的流,其实我们使用到printf就是向默认打开的stdout输出,关于这些内容在后面会详细介绍。
fflush(stdout)就是将stdout的缓存区内容刷新到stdout中。
3. 基于前面的内容写一个进度条
基于上面学习的回车和缓存区的知识,我们就可以写一段代码,实现一个进度条小程序。
// Processbar.h
#pragma once
// 将void(double, double)函数类型重命名为callback_t(回调函数)
typedef void(*callback_t)(double, double);
// 进度条函数的声明
void ProcBar(double total, double current);
// Processbar.c
#include "Processbar.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Length 52 // 进度条的长度
#define Fill '-' // 用于初始化填充进度条的字符
#define Style '=' // 用于填充进度中进度的字符
const char *lable = "|/-\\"; // 用于显示加载时转圈的效果
// 进度条函数的定义,打印单次进度条,打印完后使光标回到行首(\r),根据输入的total和current计算出当前的完成的进度比例,根据完成的进度打印出进度条。
void ProcBar(double total, double current)
{
char bar[Length]; // 进度条字符串
memset(bar, Fill, sizeof(bar)); // 初始化进度条字符串
int len = strlen(lable); // 计算加载圈字符串的长度
double rate = (current * 100) / total; // 计算当前进度比例
int cnt = 0;
while (cnt * 2 <= rate)
bar[++cnt] = Style; // 使用Style填充进度条完成的部分
printf("[%c][%-20s][%5.1lf%%]\r", lable[cnt % len], bar + 1, rate); // 打印进度条
fflush(stdout); // 刷新缓存区的内容到屏幕
}
c
// main.c
#include "Processbar.h"
#include <stdio.h>
#include <unistd.h>
// 设置时间间隔(单位时间)
int interval = 100000;
// 设带宽(每单位时间能获取到的数据量)
double bandwidth = 1000000;
// 下载函数,filesize为需要下载的文件大小,cb为回调函数,也就是上面写的打印进度条的函数。
void download(double filesize, callback_t cb)
{
double current = 0.0;
printf("download begin, current: %lf\n", current);
// 每轮获取数据后打印一次进度条
while (current <= filesize)
{
// 单次打印进度条
cb(filesize, current);
// 模拟从网络中获取数据,每interval从网络中获取bandwidth大小的数据
usleep(interval);
// 文件传输完成后跳出循环,完成下载。
if (current == filesize) break;
current += (filesize - current > bandwidth ? bandwidth : filesize - current); // 如果当前还差的数据量>带宽,则增加带宽大小的数据量,使当前数据量等于文件大小(即传输完成)。
}
printf("\ndownload begin, filesize: %lf\n", filesize);
}
int main()
{
download(100 * 1024 * 1024, ProcBar);
download(73829483, ProcBar);
return 0;
}
运行效果:
