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.cgcc -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_countwhile (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_countwhile (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函数可以灵活地与不同的进度条更新函数配合使用。
效果演示:
闪烁版演示效果: