当前位置: 首页 > news >正文

进度条的实现(配合make和makefile超详细)

前言:

这里不是为了引流,但是如果进度条和make命令写在同一篇会时人有种长篇大论的感觉,大家可以先去看之前的make讲解的文章(make命令和makefile文件详解-CSDN博客)。本篇我们要在Linux中实现一个比较常见的进度条,因为每当我们使用yum的时候都会看到这个进度条。

学完本篇你也可以自己实现!

一、前置知识

这里当然是使用C语言来实现,但是不可能从C语言开始讲起(默认大家都有该基础)。完成进度条需要有以下只是做铺垫。

1.回车和换行

回车和换行是一个概念吗?其实不是,看下图:

这里有一个知识,在C语言中,回车是/r,换行是/n。但是我们平时/n是回车+换行,是因为C语言做了特殊处理。

此时我们更改proc.c内容(注意在里面加入了sleep函数),并看运行结果:

因为sleep函数是一个API,不同系统下API函数不同,这里Windows中sleep函数要引入windows.h头文件;而我们目前是Linux,要引入unistd.h头文件,函数名还是sleep函数。

因为作者是个废物,不知道怎么搞动图(也是懒得去搞),这里先允许作者通过口头描述来形容执行过程。 

以上注意我们printf语句中有"\n"语句,先打印之后睡眠两秒执行完毕。

此时我们把"\n"去掉,并再次运行观察结果:

当然执行顺序依然是从上到下执行的,但是为什么这里先睡眠2s了呢?

在这2s中,"hello Linux"已经被执行了,在输出缓冲区中,而/n是强制刷新,所以直接把缓冲区的内容打印了出来。

所以此时在文件中加上fflush(stdout)语句,执行并观察结果:

2.\r回车 

此时我们再次编辑Count.c文件:

此时我们使用/r做回车,之后需要强制刷新一次屏幕才会输出内容: 

此时就已经完成了在原地的倒计时程序。 

但是此时我们无法看到结束的内容,此时我们可以在代码再进行修改,此时直接在尾部追加printf("\n");即可。

#include<stdio.h>
#include<unistd.h>int main()
{int count = 3;while (count >= 0){printf("%d\r", count);fflush(stdout);count--;sleep(1);}printf("\n");return 0;
}

3.格式化输出 

此时我们把count的起始值改为10,并再次观察输出结果:

这是因为在显示器上比如int a = 123,打印出a的值123。你以为它是int型,其实计算机已经将每个123都变成了一个字符,并打印出来。

这也就是我们平时的格式化输出。 此时我们是否还记得,%2d的功能,当整形不超过2位,右对齐并打印一位,前面打印空格,超出2位还是正常输出。

但是我们应该使用左对齐,所以加上负号。

所以修改Count.c内容:

#include<stdio.h>
#include<unistd.h>int main()
{int count = 10;while (count >= 7){printf("%-2d\r", count);fflush(stdout);count--;sleep(1);}printf("\n");return 0;
}

此时就已经做好了知识的铺垫,我们已经可以正式开始进度条的小项目了。

二、简单进度条

对于一个项目,我们一般要有3个文件(test.c文件,process.c文件,process.h头文件)。

具体原因是方便修改,我们一般在自定义头文件中引入要使用的所有头文件,声明所有的函数,一些静态变量和各种宏定义;process.c文件中引入自定义的头文件process.h,之后实现对所有函数的定义;而test.c就是写入主函数,调用process.c中的函数。

此时我们新创建一个目录,并在在目录中写这几个文件:

此时编辑Makefile文件:

并分别向他们中写入以下内容(具体操作看vim编辑器和gcc编译器-CSDN博客):

此时我们先测试一下文件:

这里千万不要嫌麻烦,因为我们要写一个项目必须保证前面的小步骤没有问题,必须一步一个脚印。 OK这里没有问题,继续。

1.进度条样式预览

我们先来看我们其中的一个进度条样式:

2.process.c初步实现 

嗯……大家看看注释,当然这是一个简单的实验版进度条:

#include"process.h"#define NUM 4
//此时[]只打印3个#
#define STYLE '#'void Process() 
{char bar[NUM];//因为打印的是一个字符数组的进度条//我们先将内存设置为'\0'memset(bar, '\0', sizeof(bar)); int cnt = 0;while (cnt < NUM - 1) {printf("[%-3s]\r", bar);//这里要记得回车才能在原地显示bar[cnt] = STYLE;cnt++;//强制刷新fflush(stdout);sleep(1);}printf("\r[%s]\n", bar);
}

OK,这就是最初的进度条。我们将数组长度设置为4,因为最后要留一个'\0',所以进度条中最多显示3个#,最多生成3次,我们每次都要强制刷新一次,之后回车使光标回到首字符位置,之后重新打印一次bar数组。 

3.usleep函数

因为sleep函数是以秒为单位睡眠的,太慢了,我们要以毫秒进行睡眠,就需要使用usleep函数,注意它的单位是微秒。

1秒 = 1000000微秒

4.显示百分比

我们要计算百分比,这里以整数为例,要计算当前打印多少个#并和一个多少个#进行相除,这里要注意类型转化。

#include"process.h"#define NUM 80
//此时[]只打印79个#
#define STYLE '#'void Process() 
{char bar[NUM];//因为打印的是一个字符数组的进度条//我们先将内存设置为'\0'memset(bar, '\0', sizeof(bar)); int cnt = 0;int persent = 0;while (cnt < NUM - 1) {persent = (int)((float)(cnt + 1) / (NUM - 1) * 100);printf("[%-79s][%-3d%%]\r", bar, persent);//这里要记得回车才能在原地显示bar[cnt] = STYLE;cnt++;//强制刷新fflush(stdout);usleep(300);}printf("\r[%s]\n", bar);
}

5.旋转光标

我们可以有一个动态的加载过程,就好比一个小圈圈。

此时我们就可以用一个字符串数组来完成:

#include"process.h"#define NUM 80
//此时[]只打印79个#
#define STYLE '#'#define STYLE1 '-'
#define STYLE2 '\\'
#define STYLE3 '|'
#define STYLE4 '/'void Process() 
{char bar[NUM];//因为打印的是一个字符数组的进度条//我们先将内存设置为'\0'memset(bar, '\0', sizeof(bar));char set[5] = {STYLE1, STYLE2, STYLE3, STYLE4, '\0'};int cnt = 0;int persent = 0;while (cnt < NUM - 1) {persent = (int)((float)(cnt + 1) / (NUM - 1) * 100);printf("[%-79s][%-3d%%][%c]\r", bar, persent, set[cnt % 4]);//这里要记得回车才能在原地显示bar[cnt] = STYLE;cnt++;//强制刷新fflush(stdout);usleep(1000);}printf("\r[%s]\n", bar);
}

当然也可以使用字符串指针来完成,就像下面:

#include"process.h"#define NUM 80
//此时[]只打印79个#
#define STYLE '#'void Process() 
{char bar[NUM];//因为打印的是一个字符数组的进度条//我们先将内存设置为'\0'memset(bar, '\0', sizeof(bar));const char *lable = "|/-\\";int len = strlen(lable);int cnt = 0;int persent = 0;while (cnt < NUM - 1) {persent = (int)((float)(cnt + 1) / (NUM - 1) * 100);printf("[%-79s][%-3d%%][%c]\r", bar, persent, lable[cnt % len]);//这里要记得回车才能在原地显示bar[cnt] = STYLE;cnt++;//强制刷新fflush(stdout);usleep(1000);}printf("\r[%s]\n", bar);
}

6.其他风格进度条

这种风格的进度条我们就实现了,但是你肯定也见过以下形式的进度条:

[=======>          ][20%][\]

此时我们还是通过修改process.c文件来实现:

#include"process.h"#define NUM 80
//此时[]只打印79个#
#define STYLE '='void Process() 
{char bar[NUM];//因为打印的是一个字符数组的进度条//我们先将内存设置为'\0'memset(bar, '\0', sizeof(bar));const char *lable = "|/-\\";int len = strlen(lable);int cnt = 0;int persent = 0;while (cnt < NUM - 1) {persent = (int)((float)(cnt + 1) / (NUM - 1) * 100);printf("[%-79s][%-3d%%][%c]\r", bar, persent, lable[cnt % len]);//这里要记得回车才能在原地显示bar[cnt] = STYLE;cnt++;if (cnt < NUM - 1) {bar[cnt] = '>';}//强制刷新fflush(stdout);usleep(10000);}bar[--cnt] = '=';printf("\r[%s]\n", bar);
}

此时版本二就做好了(第一个字符从=开始)。 

还有第三版(第一个字符从">"开始):

#include"process.h"#define NUM 80
//此时[]只打印79个#
#define STYLE '='
#define STYLE1 '>'void Process() 
{char bar[NUM];//因为打印的是一个字符数组的进度条//我们先将内存设置为'\0'memset(bar, '\0', sizeof(bar));const char *lable = "|/-\\";int len = strlen(lable);int cnt = 0;int persent = 0;while (cnt < NUM - 1) {bar[cnt] = STYLE1;persent = (int)((float)(cnt + 1) / (NUM - 1) * 100);printf("[%-79s][%-3d%%][%c]\r", bar, persent, lable[cnt % len]);//这里要记得回车才能在原地显示//强制刷新fflush(stdout);usleep(50000);bar[cnt] = STYLE;printf("[%-79s][%-3d%%][%c]\r", bar, persent, lable[cnt % len]);fflush(stdout);cnt++;//if (cnt < NUM - 1) {//    bar[cnt] = '>';//}//usleep(10000);}bar[--cnt] = '=';printf("\r[%s]\n", bar);
}

三、正式版进度条

但是此时功能依旧不完整,此时我们需要正式模拟下载过程,考虑的必须全面,包括网速,当前下载量,总下载量。

我们先在test.c中模拟一次。

#include"process.h"//调整网速
const int base = 100;//声明总下载量
double total = 2048.0; // 2048MB//但此下载
double once = 0.1;void download() 
{//声明当前总下载量double current = 0.0;while(current < total) {//产生 [1 - 20] 的随机数int r = rand() % base + 1;//当前网速double speed = r * once;current += speed;//此时可能会产生问题,current + 网速下载量 > totalif (current >= total) {current = total;}printf("cur = %.1lf,total = %.1lf\r", current, total);fflush(stdout);usleep(5000);}printf("\n");
}int main()
{srand(time(NULL));//种下随机数种子download();//Process();    return 0;
}

接下来步骤其实就是和之前的进度条结合起来,直接上代码了。

注意:此时我们又换了一种风格,后面的旋转光标更换为.  。而且打印的时候要 

1.全部代码

 process.c源文件:

#include"process.h"#define NUM 101
#define STYLE '='
#define STYLE1 '>'
#define STYLE2 ' ' 
#define STYLE3 '.' const int pnum = 6 ;//version2:
void Process(double total, double current) 
{//1.更新当前进度百分比double rate = current / total * 100;//printf("test:%.1lf%%\r", rate);//2.更新进度条主体//当前认为 1% 就是一个等号char bar[NUM];memset(bar, '\0', sizeof(bar));int i = 0;for(i = 0; i < (int)rate; ++i){bar[i] = STYLE;}//3.更新旋转光标或者其他风格//此时我们用一个延长声明周期的static修饰一个整数static int num = 0;//以便控制打印下标num++;//这里解释一下:num最多到5,之后生命周期延长,到6的时候执行下面语句会为0num %= pnum;//也就是说:num一直都在 [0 - 5]循环//这里声明一个字符串数组char points[pnum + 1];//此数组大小为7,最后是'\0'占据//注意:Linux中编译器使用的是C99版本以上,所以可以这样写memset(points, '\0', sizeof(points));//这里我们要打印这个字符数组for (i = 0; i < pnum; ++i) {//我们先打印. 之后将后面数组中的内容换为空格即可if (i < num) {//此时初始化为.points[i] = STYLE3;} else {//否则打印空格points[i] = STYLE2;}}//3.打印printf("[%-100s][%.1lf%%]%s\r", bar, rate, points);
}//void Process() 
//{
//    char bar[NUM];
//    //因为打印的是一个字符数组的进度条
//    //我们先将内存设置为'\0'
//    memset(bar, '\0', sizeof(bar));
//    const char *lable = "|/-\\";
//    int len = strlen(lable);
//    int cnt = 0;
//    int persent = 0;
//    while (cnt < NUM - 1) {
//        persent = (int)((float)(cnt + 1) / (NUM - 1) * 100);
//        printf("[%-79s][%-3d%%][%c]\r", bar, persent, lable[cnt % len]);
//        //这里要记得回车才能在原地显示
//        
//        bar[cnt] = STYLE;
//
//        /制刷新
//        fflush(stdout);
//        usleep(50000);
//        cnt++;
//        if (cnt < NUM - 1) {
//            bar[cnt] = '>';
//        }
//
//
//    }
//    bar[--cnt] = '=';
//    printf("\r[%s]\n", bar);
//}

process.h头文件: 

#pragma once#include<stdio.h>
#include<unistd.h>
#include<string.h>//引入随机数头文件
#include<time.h>
#include<stdlib.h>void Process(double total, double current);

test.c源文件:

#include"process.h"//调整网速
const int base = 100;//声明总下载量
double total = 2048.0; // 2048MB//但此下载
double once = 0.1;void download() 
{//声明当前总下载量double current = 0.0;while(current < total) {//产生 [1 - 20] 的随机数int r = rand() % base + 1;//当前网速double speed = r * once;current += speed;//此时可能会产生问题,current + 网速下载量 > totalif (current >= total) {current = total;}Process(total, current);fflush(stdout);usleep(5000);}printf("\n");
}int main()
{srand(time(NULL));//种下随机数种子download();//Process();    return 0;
}

这个旋转光标的目的就是当网速很慢的时候,我们也可以看到并不是卡死了,而是一直在下载。

四、更改为函数指针(附加)

我们将Prosess重命名,之后主函数可以使用回调函数的方式调用。

1.使用函数指针的全部代码

process.h头文件:

#pragma once#include<stdio.h>
#include<unistd.h>
#include<string.h>//引入随机数头文件
#include<time.h>
#include<stdlib.h>void FlushProcess(double total, double current);

process.c源文件:

#include"process.h"#define NUM 101
#define STYLE '='
#define STYLE1 '>'
#define STYLE2 ' ' 
#define STYLE3 '.' const int pnum = 6 ;//version2:
void FlushProcess(double total, double current) 
{//1.更新当前进度百分比double rate = current / total * 100;//printf("test:%.1lf%%\r", rate);//2.更新进度条主体//当前认为 1% 就是一个等号char bar[NUM];memset(bar, '\0', sizeof(bar));int i = 0;for(i = 0; i < (int)rate; ++i){bar[i] = STYLE;}//3.更新旋转光标或者其他风格//此时我们用一个延长声明周期的static修饰一个整数static int num = 0;//以便控制打印下标num++;//这里解释一下:num最多到5,之后生命周期延长,到6的时候执行下面语句会为0num %= pnum;//也就是说:num一直都在 [0 - 5]循环//这里声明一个字符串数组char points[pnum + 1];//此数组大小为7,最后是'\0'占据//注意:Linux中编译器使用的是C99版本以上,所以可以这样写memset(points, '\0', sizeof(points));//这里我们要打印这个字符数组for (i = 0; i < pnum; ++i) {//我们先打印. 之后将后面数组中的内容换为空格即可if (i < num) {//此时初始化为.points[i] = STYLE3;} else {//否则打印空格points[i] = STYLE2;}}//4.打印printf("[%-100s][%.1lf%%]%s\r", bar, rate, points);
}

test.c源文件:

#include"process.h"//调整网速
const int base = 100;//声明总下载量
double total = 2048.0; // 2048MB//但此下载
double once = 0.1;//此时重命名函数指针
typedef void(*flush_t)(double, double);void download(flush_t f) 
{//声明当前总下载量double current = 0.0;while(current < total) {//产生 [1 - 20] 的随机数int r = rand() % base + 1;//当前网速double speed = r * once;current += speed;//此时可能会产生问题,current + 网速下载量 > totalif (current >= total) {current = total;}f(total, current);fflush(stdout);usleep(5000);}printf("\n");
}int main()
{srand(time(NULL));//种下随机数种子//因为参数类型为函数指针//此时只需要传入函数名即可download(FlushProcess);//Process();    return 0;
}

这里我们使用回调函数,可以使以后调用函数更加方便。

总结: 

大家最好跟着完成一遍,即使是抄的,抄明白以后自己完成,就会很有成就感。这是我们第一个在Linux中实现的小项目,其实是很有意义的。


http://www.mrgr.cn/news/65655.html

相关文章:

  • 【论文阅读】Associative Alignment for Few-shot Image Classification
  • Dockerfile的使用
  • OpenAI 提示工程指南详解
  • React中管理state的方式
  • 什么是 OpenTelemetry?
  • vue3uniapp实现自定义拱形底部导航栏,解决首次闪烁问题
  • Python绘制正弦函数图形
  • 集成框架 -- 自定义二方包 starter
  • 分析自动下载电路是如何工作的以及CH340的选型
  • Autocad2018
  • LeetCode:3259. 超级饮料的最大强化能量(DP Java)
  • git原理与上传
  • Backbone网络详解
  • Docker部署Portainer CE结合内网穿透实现容器的可视化管理与远程访问
  • Python 枚举enum
  • 时间服务器
  • 【操作系统】基于环形队列的生产消费模型
  • 堆heap的讨论、习题与代码
  • 架构师考试系列(8)论文专题:信息系统安全设计
  • 探索 Intersection Observer API:提升网页性能的新途径
  • 主体Subject和客体Object-西方哲学的思维方式
  • mysql笔记-索引
  • 游游的游戏大礼包
  • windows完结---清风
  • 数据结构---自定义动态数组
  • 从零开发操作系统-为什么磁盘的扇区为 512 byte