1. 预备知识:回车和换行
回车(Carriage Return,CR):
- 在早期的机械打字机中,回车指的是将打字机的打印头移回到行首的操作,这样打印头就可以开始新的一行的打印。
- 在ASCII编码中,回车用控制字符CR表示,其编码为
\r
(即十进制的13)。换行(Line Feed,LF):
- 换行是指将打印头向下移动到下一行的操作。
- 在ASCII编码中,换行用控制字符LF表示,其编码为
\n
(即十进制的10)。即:回车,回到当前行的行首,不会切换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖;
换行,换到当前位置的下一行,而不会回到行首。
在Windows系统中,文本文件的换行符通常是回车加换行(CR+LF,即\r\n
)。而在Unix/Linux系统中,换行符仅仅是LF(\n
)(Unix/Linux下这个\n
就就包括了回车和换行)。
2. 预备知识:缓冲区
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello world!");
sleep(3);
return 0;
}
在linux下编译运行以上代码,发现在前三秒"hello world!"并未被打印到屏幕上,三秒之后被打印到屏幕上。那么此前三秒,它被储存到了那里呢?
答案是缓冲区,缓冲区(Buffer)主要应用于提高系统性能和效率。缓冲区通常指的是内存中的一段连续区域,用于临时存储数据,减少CPU、内存和外部设备(如硬盘、网络等)之间的交互次数。当应用程序向文件或设备进行读写操作时,数据会首先被存储到缓冲区中,然后再由缓冲区根据特定的刷新策略将数据写入磁盘或设备中。
缓冲区被刷新到显示器上的几种方式:
- 程序结束的时候,一般要自动冲刷缓冲区
\n
- 缓冲区满了,自动刷新
fflush()
函数强制刷新
3. 进度条
此文件夹下新建一个makefile
文件:
processbar: Main.c Processbar.c
gcc -o $@ $^ //选项 -o $@ 指定输出文件名,其中 $@ 是一个自动变量,代表当前规则的目标(在这里即为processbar)。而 $^ 是另一个自动变量,表示所有依赖文件的集合(即Main.c和Processbar.c)
.PHONY: clean
clean:
rm -f processbar
这是一个用于将Main.c
和Processbar.c
源文件编译并链接成名为processbar
的可执行文件的简单Makefile
。
这个Makefile
提供了两种操作:
1.运行make processbar
以编译并链接Main.c
和Processbar.c
源文件,生成processbar
可执行文件。
2.运行make clean
以删除processbar可执行文件,清理项目构建产物。
进度条代码版本一:(仅仅是进度条的模拟)
#include <string.h>
#include <unistd.h>
#include<stdio.h>
#define Length 101
#define Style '#' //进度条填充样式为'#'字符
const char* lable="|/-\\"; //在进度条前方显示动画效果
void ProcBar();
////version 1
void ProcBar()
{
char bar[Length]; //进度条的显示长度
memset(bar,'\0',sizeof(bar)); //先初始化为'\0'填充清零
int len=strlen(lable);
int cnt=0;
while(cnt<=100) //使用循环结构,从0迭代到100,每次迭代表示进度的1%
{
printf("[%-100s][%-3d%%][%c]\r",bar,cnt,lable[cnt%len]); //用回车并不是换行,每次打印进度条都从当前行头部开始打印
fflush(stdout); //强制刷新标准输出缓冲区,确保进度条立刻显示在屏幕上
bar[cnt++]=Style; //进度条数组bar的相应位置添加进度样式字符
usleep(200000); //用于休眠一段时间,模拟任务执行过程。(休眠单位为微秒)
}
printf("\n");
}
int main()
{
ProcBar();
return 0;
}
这段C语言代码实现了一个简单的进度条功能,用于模拟某个长时间运行的任务(如文件下载、处理任务等)的进度展示。
效果演示:
进度条代码版本二:(模拟一个下载任务,根据下载任务的进度打印进度条)
#include <string.h>
#include <unistd.h>
#include<stdio.h>
#define Length 101
#define Style '#' //进度条填充样式为'#'字符
const char* lable="|/-\\"; //在进度条前方显示动画效果
void ProcBar();
////version 2
////配合场景使用
//进度条每执行一次循环就刷新一次,会出现闪烁(我还挺喜欢这个的)
void ProcBar(double total, double current) //total代表总任务量(即文件大小),current代表当前已完成的任务量(即已下载的数据量)
{
char bar[Length];
memset(bar, '\0', sizeof(bar));
int len = strlen(lable);
int cnt = 0;
double rate = (current * 100.0) / total;
int loop_count = (int)rate; //计算进度百分比rate和对应的循环次数loop_count
while (cnt <= loop_count)
{
printf("[%-100s][%.1lf%%][%c]\r", bar, rate, lable[cnt % len]);
fflush(stdout);
bar[cnt++] = Style;
//usleep(200000); //这个也不需要了,在另一个调它的函数download()中有
}
//printf("\n");
}
//模拟下载单个文件
void download()
{
double filesize = 100 * 1024 * 1024 * 1.0; // 文件大小100兆
double current = 0.0; // 当前下载量
double bandwidth = 1024 * 1024 * 1.0; // 网络带宽1兆
printf("download begin, current: %lf\n", current);
while (current <= filesize) //使用一个循环来模拟下载过程,每次迭代都更新current的值,并调用ProcBar函数来更新和显示进度条。
{
// 打印进度条
ProcBar(filesize, current);
// 从网络获取数据
current += bandwidth;
usleep(10000); //模拟网络下载的速度
}
printf("\ndownload complete! filesize:%lf\n", filesize);
}
int main()
{
// 下载测试(模拟单个文件下载)
// download();
return 0;
}
这段代码实现了一个简单的进度条功能(ProcBar())和一个模拟单个文件下载的函数(download())。实际使用时,只需将download()中的相关参数替换为实际的下载信息,并调用此函数即可在控制台显示下载进度。
效果演示:
进度条代码版本三:(模拟多个下载任务,根据下载任务的进度打印进度条)
#include <string.h>
#include <unistd.h>
#include<stdio.h>
#define Length 101
#define Style '#' //进度条填充样式为'#'字符
const char* lable="|/-\\"; //在进度条前方显示动画效果
typedef void(*callback_t)(double,double); //函数指针
void ProcBar();
//闪烁版
//void ProcBar(double total, double current) //total代表总任务量(即文件大小),current代表当前已完成的任务量(即已下载的数据量)
//{
// char bar[Length];
// memset(bar, '\0', sizeof(bar));
// int len = strlen(lable);
//
// int cnt = 0;
// double rate = (current * 100.0) / total;
// int loop_count = (int)rate; //计算进度百分比rate和对应的循环次数loop_count
//
// while (cnt <= loop_count)
// {
// printf("[%-100s][%.1lf%%][%c]\r", bar, rate, lable[cnt % len]);
// fflush(stdout);
// bar[cnt++] = Style;
// //usleep(200000); //这个也不需要了,在另一个调它的函数download()中有
// }
//
// //printf("\n");
//}
// version 3
// 配合场景使用
// 每次调用函数时直接将bar拼接起来,循环执行完后再刷新,这样就不会出现闪烁
void ProcBar(double total, double current)
{
char bar[Length];
memset(bar, '\0', sizeof(bar));
int len = strlen(lable);
int cnt = 0;
double rate = (current * 100.0) / total;
int loop_count = (int)rate; //计算进度百分比rate和对应的循环次数loop_count
while (cnt <= loop_count)
{
bar[cnt++] = Style;
}
// 填充完成后,一次性打印出完整的进度条,并刷新输出,避免了闪烁现象
printf("[%-100s][%.1lf%%][%c]\r", bar, rate, lable[cnt % len]);
fflush(stdout);
}
//此版本进度条与之前版本不同的是,它在循环中先将进度条字符逐个拼接到bar数组中,待循环完成后一次性刷新输出,从而避免了进度条的闪烁现象。
// 模拟下载多个文件
double bandwidth = 1024 * 1024 * 1.0; // 网络带宽1兆
void download(double filesize, callback_t cb) // 此处用函数指针也很方便
{
double current = 0.0; // 当前下载量
printf("download begin, current: %lf\n", current);
while (current <= filesize) //使用一个循环来模拟下载过程,每次迭代都更新当前下载量current,并调用回调函数cb来更新和显示进度条
{
cb(filesize, current);
// 从网络获取数据
current += bandwidth;
usleep(10000); //模拟下载速度时,每次迭代都增加bandwidth(网络带宽)的值到current,并使用usleep函数来模拟网络延迟
}
printf("\ndownload complete! filesize:%lf\n", filesize);
}
int main()
{
// 多个文件下载测试
download(200 * 1024 * 1024, ProcBar);
download(400 * 1024 * 1024, ProcBar);
download(50 * 1024 * 1024, ProcBar);
download(10 * 1024 * 1024, ProcBar);
return 0;
}
这段C代码通过定义一个进度条更新函数ProcBar和一个模拟下载函数download,实现了在控制台中展示动态进度条的功能,并模拟了多个文件的下载过程。通过回调函数的使用,使得download函数可以灵活地与不同的进度条更新函数配合使用。
效果演示:
闪烁版演示效果: