Linux《进度条》

server/2025/3/18 13:46:48/

在之前的Linux基础开发工具当中我们已经了解了vim、gcc、makefile等基本的开发工具,那么有了这些开发工具我们就可以来实现我们Linux旅程当中的第一个程序——进度条。相信通过该项目的实现能让你对vim等开发工具更加的熟悉。一起加油吧!!!


1.补充知识回车与换行 

在实现进度条的项目之前我们先要来了解关于回车和换行的基础补充知识,在了解之后才能让接下来的项目的编写更加的顺畅。

首先我们要了解的是回车和换行有什么区别,此时你可能就会疑惑这两个实现的效果不是一样的吗?我们在点击回车的时候不就是实现了换行了吗?

其实这两个实现的效果是不同的,只不过在现代的计算机当中基本将这两个概念不进行区分了,但在上个实际使用的老式打字机上换行自是将对应的纸向下移动,而要将实现回车还需要将指针移动回起始位置。

那通过以上的示例就可以理解在现代的计算机当中回车其实是 换行+回车 的,通过以下的老式键盘就可以看出这一特点

其实在之前在C语言当中使用的\n就是本质上就是实现了换行+回车的功能,\r实现的就是回车的功能。 


2.练手小程序——倒计时

以上我们了解了回车的本质,那么接下来就来通过一个倒计时的小程序来作为实现进度条项目之前的联手项目,不过在实现之前先要通过以下的代码得出一个规则。

首先来看以下的代码:

#include<stdio.h>
#include <unistd.h>int main()
{    printf("hello world\n");sleep(5);                                                                                                                                          return 0;
}

在以上的代码当中运行会出现什么样的现象呢?

通过运行形成可执行程序以上的代码输出的效果如下所示:
 

此时就可以看出首先会在显示器当中打印出hello world,之后在休眠5妙再让程序结束

再看以下的代码会有什么现象:

#include<stdio.h>
#include <unistd.h>int main()
{    printf("hello world");sleep(5);                                                                                                                                          return 0;
}

以上的代码相比原来的代码只是少了一个换行符\n,此时该程序运行的结果会和之前的一样吗?

通过将该程序形成可执行程序运行之后就可以看出和之前不同,该程序一开始没有将hello world输出到显示器上,而是到程序结束的时候才打印到显示器上,那么为什么会有这样的现象呢? 


 

要解答以上的问题就需要了解C语言当中进行输出的流程是怎么样的,其实在将对应的信息输出到显示器之前是会将数据先存储到缓冲区,之后再统一的将缓冲区内的数据刷新到显示器上,因此以上的两份代码再执行完printf("hello world\n")与printf("hello world")之后都是将执行完的结果存储到缓冲区当中,那么为什么第一份代码能直接将结果显示到显示器上呢?

这其实和其实和显示器行刷新的策略有关,当使用\n之后会将缓冲区当中的数据刷新到显示器上,而第二份代码当中未使用\n就使得程序缓冲区当中的数据需要在程序结束的时候才刷新到显示器上,这也是第二份代码形成的可执行程序在最后才会显示出来。

那么在第二份代码当中不使用\n但要使其与第一份代码执行出相同的效果,要使用什么样的方式呢?

在此就需要了解一个库函数fflush

在此fflush的作用就是将缓冲区当中的数据进行强制刷新。我们知道在C语言的程序启动时默认会打开stdin、stdout、stderr三个标准输入输出流,那么此时就可以将缓冲区当中的数据强制刷新到标准输出流stdout上。

改进的代码如下所示:

#include<stdio.h>
#include <unistd.h>int main()
{    printf("hello world");fflush(stdout);sleep(5);                                                                                                                                          return 0;
}

以上代码形成可执行程序之后输出结果如下所示:


了解了缓冲区之后接下来就来尝试编写倒计时程序的代码,在此要求是从10开是倒计时,倒计时的数字在同一行内一直进行刷新,直到最后数字未0时结束 

那么接下来就很容易的能实现出以下的代码

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

以上的代码就是使用了以上我们学习的fflush在进行每一次的printf之后就进行强制的刷新,并且在每次输出一个数字之后使用\r进行回车,这样就可以使得每一秒显示器上会出现一个数字且在同一行内输出。但是以后的代码还有一个问题,我们来通过运行看看。

通过以下的输出就可以看出问题的所在是在输出时一开始打印的10是两位数,之后再进行打印的是一位数,这就会使得打印出的结果变为了90、80……

这个问题形成的原因是由于printf在输出时默认的是右对齐,此时要修改为左对齐只需要在printf的占位符%之后加上一个-2即可,这样就可以限定最小宽度为2

修改之后的代码如下所示:

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

输出结果如下所示:

3.进度条项目实现

通过以上的倒计时小程序我们完成了在实现进度条项目之前的练手,那么接下来就可以开始实现我们Linux学习当中的第一个项目,在此会实现两个版本的进度条项目,其中第一个版本的代码较为简单,但是不具有实用性,第二份的代码才具有实用性。

在实现代码之前首先要来明确我们的实现的项目的要求是什么,在此实现出的效果需大致如下所示:

首先是有一个进度条进行进度的推进直到进度条满为止,在进度条之后有一个显示当前进度的百分比数,之后有一个旋转的光标来表示当前进度条是在推进的。

 

3.1 v1版本

在实现该进度条项目时分为三个文件,在process.h内进行实现函数的声明,在process.c内实现对应的函数,在main.c使用实现的函数来实现进度条

那么接下来就先来实现第一个版本的进度条,在此创建一个函数process_v1,在该函数内实现对应的代码。在该版本当中的进度条只需要每秒使得进度条向后推进指定的进度即可,并且在进度条之后的百分数也显示对应的进度值。

 但在实现process_v1内的函数代码之前我们先要将该程序对应的makefile进行构建

makefile内的内容如下所示:

BIN=process.exe    
SRC=$(wildcard *.c)                                                                                                                                          
OBJ=$(SRC:.c=.o)    
CC=gcc    $(BIN):$(OBJ)    $(CC) -o $@ $^    
%.o:%.c    $(CC) -c $<    .PHYNO:clean    
clean:    rm -f $(OBJ) $(BIN)    

接下来就来实现第一个版本的进度条

首先在实现进度条要从0到100,那么就创建一个大小为101的字符数组来存储对应的进度数据,使用'#'来表示进度元素,接下来使用memset函数将数组buffer内的元素都初始化为0。 

由于要在进度条的最后使用一个动态的翻转来表明进度条是在运行的,那么在此就创建一个数组tmp,该数组内存储对应的字符,进度条每增长一位就将对应的状态字符改变,

在此字符串内使用\\是因为两个\\才会被转译为\

接下来创建一个变量cnt来表示当前进度条的进度值,使用while喜欢来实现0到100进度。在此过程中每次打印完字符串、进度条百分比、状态元素之后都要回车并且进行强制刷新。 

每次打印完之后将buffer数组内对应cnt下标位置元素修改为'#',再让cnt++

注:在此每次状态元素都使用cnt%len,这样就可以使得得到的下标一直在tmp字符串的范围内

以上使用的函数usleep和sleep类似也是进行休眠,不过单位是毫秒

完整代码如下所示:

#include"process.h"    //函数实现    #define MAX 101    
#define STYLE '#'                                                                                                                                            void process_v1()    
{    char buffer[MAX];    memset(buffer,0,sizeof(buffer));    const char* tmp="|/-\\";    int len=strlen(tmp);    int cnt=0;    while(cnt<=100)    {    printf("[%-100s][%d%%][%c]\r",buffer,cnt,tmp[cnt%len]);    fflush(stdout);    usleep(50000);    buffer[cnt]=STYLE;    cnt++;    }    printf("\n");    }    

编写完process_v1的代码之后接下来就使用make命令生成对应的可执行程序process.exe


 

执行process.exe效果如下所示: 

3.2 v2版本

在上述内容中,我们已经实现了进度条的第一个版本。尽管上述进度条程序确实能够运行,但问题在于我们上面实现的进度条速度是均匀的。这在现实中几乎是不可能的情况。更多时候,进度条会呈现出速度的变化。例如,在下载文件时,由于网速的影响,网速快时进度条移动得快,网速慢时进度条移动得慢。因此,接下来实现的第二个版本的进度条将更符合实际情况。

在此创建一个process_v2来表示该版本进度条的实现

该函数的参数有两个分别为总的数据值以及当前完成任务的数据值。在该函数的内部就需要实现cur占totle总量的百分比进度条,在此数据的变化就不在该函数内实现。

首先和之前的process_v1函数类似也是先创建一个大小为101的数组buffer来存储进度条的内容,再创建一个数组tmp来存储动态变化的字符。

之后和之前实现函数不同的是使用cnt变量的值来确定对应状态数组的下标,创建一个浮点数的变量count来统计当前进度条的百分比,再使用变量sum来统计进度条内#的个数

 

完整代码如下所示:

#define MAX 101    
#define STYLE '#'    void process_v2(double totle,double cur)                                                                                                                     
{    char buffer[MAX];    memset(buffer,0,sizeof(buffer));    const char* tmp="|/-\\";    int len=strlen(tmp);    static int cnt=0;    double count=cur*100/totle;    int sum=(int)count;    int i=0;    for(;i<=sum;i++)    {    buffer[i]=STYLE;    }    printf("[%-100s][%.1lf%%][%c]\r",buffer,count,tmp[cnt]);    fflush(stdout);    cnt++;    cnt%=len;    }    

实现了process_v2函数之后接下来就需要在main.c当中实现一个具体的场景,假设在此实现的是文件的下载,下载的总量是1024mb,下载的速度是0.1mb每秒,那么接下来实现的函数就如下所示:

#include"process.h"    double totle=1024.0;    
double speed=1.0;    void DownLoad()    
{                                                                                                                                                            double cur=0;    while(cur<=totle)    {    process_v2(totle,cur);    usleep(10000);    cur+=speed;    }    }    int main()    
{    //函数使用    //process_v1();    DownLoad();    return 0;    }    

执行以上代码make之后形成的可执行程序,输出结果如下所示:

 

以上虽然实现的进度条能执行下去,但是以上还是匀速的,那么此时要使得呈现出来的进度条快慢是变化就需要使用随机数。

并且还将下载总的数据量的大小由mai函数内调用DownLoad时传参。

在此之前先在process.h内引用对应的头文件

#include"process.h"                                                                                     void DownLoad(double totle)                                                                                     
{    srand(time(NULL));    double speed=rand()%51;    double cur=0;    while(cur<=totle)    {    process_v2(totle,cur);    usleep(10000);    cur+=speed;    }    printf("\n");    }    int main()    
{    //函数使用    //process_v1();                                                                                     DownLoad(10240);                                                                                     return 0;                                                                                     }     

以上代码此时就会存在一个问题就是最终cur的值可能会无法和totle正好匹配,那么此时最后进度条就无法显示到100%,因此接下来就要对这种情况进行处理。

在此只需要按照以上的方式处理即可实现最终的进度条到100%。 

以上实现的进度条以及基本满足我们的预期了,接下来就是对以上代码进行优化,在以上代码中DownLoad和process_v2的耦合度较高,在此我们可以修改为回调函数的方式来实现函数的调用。

并且在process_v2函数当中再添加一个变量来表示当中进度进行的操作是什么。

这样就可以实现上传等其他的操作

完整代码如下所示:

process.h

#pragma once    
#include<stdio.h>    
#include<string.h>    
#include<unistd.h>    
#include<time.h>    
#include<stdlib.h>    //函数声明    
void process_v1();    
void process_v2(const char* s,double totle,double cur);                                                                                                      


process.c

#include"process.h"                                                                        //函数实现                                                                                       #define MAX 101                                                                          
#define STYLE '#'                                                                                void process_v2(const char* s,double totle,double cur)                        
{                                                                             char buffer[MAX];                                                         memset(buffer,0,sizeof(buffer));                                          const char* tmp="|/-\\";                                                  int len=strlen(tmp);                                                      static int cnt=0;                                                         double count=cur*100/totle;                                               int sum=(int)count;                                                       int i=0;                                                                  for(;i<=sum;i++)                                                          {                                                                         buffer[i]=STYLE;                                                      }                                                                         printf("[%s][%-100s][%.1lf%%][%c]\r",s,buffer,count,tmp[cnt]);                                                                                           fflush(stdout);                                                           cnt++;                                                                    cnt%=len;                                                                 }  void process_v1()
{char buffer[MAX];memset(buffer,0,sizeof(buffer));const char* tmp="|/-\\";int len=strlen(tmp);int cnt=0;                                                                                                                                               while(cnt<=100){printf("[%-100s][%d%%][%c]\r",buffer,cnt,tmp[cnt%len]);fflush(stdout);usleep(50000);buffer[cnt]=STYLE;cnt++;}printf("\n");}

main.c

#include"process.h"typedef void(*call_t)(const char*,double,double);    
void DownLoad(double totle,call_t cb)    
{    srand(time(NULL));    double speed=rand()%51;    double cur=0;    while(cur<=totle)    {    cb("下载中:",totle,cur);    if(cur>=totle)break;    usleep(1000);    cur+=speed;    if(cur>totle)cur=totle;    }    printf("\n");    }    
void UPLoad(double totle,call_t cb)
{srand(time(NULL));double speed=rand()%51;double cur=0;while(cur<=totle){cb("上传中:",totle,cur);if(cur>=totle)break;usleep(1000);cur+=speed;if(cur>totle)cur=totle;    }    printf("\n");
}int main()
{                                                                                                                                                            //函数使用//process_v1()printf("下载总量:%d\n",10000);DownLoad(10000,process_v2);printf("下载总量:%d\n",222222);DownLoad(222222,process_v2);printf("下载总量:%d\n",99);DownLoad(99,process_v2);printf("下载总量:%d\n",5555);DownLoad(5555,process_v2);printf("下载总量:%d\n",1);DownLoad(1,process_v2);printf("上传总量:%d\n",20000);UPLoad(20000,process_v2);return 0;}

 

以上代码运行结果如下所示:

 

以上就是本篇的全部内容了,希望通过本篇的学习能让你对这些基础的开发工具有更深的认识


http://www.ppmy.cn/server/175970.html

相关文章

时区转换工具

开发一个Python程序&#xff0c;将用户输入的北京日期时间转换为全球多个目标地区的对应时间&#xff0c;支持手动选择地区&#xff0c;并显示开始和结束两个时间段的转换结果 import pytz from datetime import datetime import pandas as pd from tabulate import tabulate …

Python 视频爬取教程

文章目录 前言基本原理环境准备Python安装选择Python开发环境安装必要库 示例 1&#xff1a;爬取简单直链视频示例 2&#xff1a;爬取基于 HTML5 的视频&#xff08;以某简单视频网站为例&#xff09; 前言 以下是一个较为完整的 Python 视频爬取教程&#xff0c;包含基本原理…

计算机基础:二进制基础13,十六进制与二进制的相互转换

专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏&#xff0c;故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 &#xff08;一&#xff09;WIn32 专栏导航 上一篇&#xff1a;计算机基础&#xff1a;二进制基础12&#xff0c;十进制数转换为…

EasyExcel动态拆分非固定列Excel表格

使用EasyExcel动态拆分非固定列Excel表格 在Excel数据解析场景中&#xff0c;​动态列结构拆分是典型挑战&#xff08;如供应链系统中不同品类的属性字段差异较大&#xff09;。传统基于POJO映射的方案无法应对列数量不固定的场景。本方案采用EasyExcel的动态模型解析和Map数据…

C++之list类及模拟实现

目录 list的介绍 list的模拟实现 定义节点 有关遍历的重载运算符 list的操作实现 &#xff08;1&#xff09;构造函数 (2)拷贝构造函数 &#xff08;3&#xff09;赋值运算符重载函数 &#xff08;4&#xff09;析构函数和clear成员函数 &#xff08;5&#xff09;尾…

C# ManualResetEvent‌的高级用法

一、ManualResetEvent 的核心作用‌ ManualResetEvent 是 C# 中用于 ‌线程同步‌ 的类&#xff08;位于 System.Threading 命名空间&#xff09;&#xff0c;通过信号机制控制线程的等待与执行。其核心功能包括&#xff1a; 阻塞线程‌&#xff1a;调用 WaitOne() 的线程会等…

【实测闭坑】LazyGraphRAG利用本地ollama提供Embedding model服务和火山引擎的deepseek API构建本地知识库

LazyGraphRAG 2024年4月&#xff0c;为解决传统RAG在全局性的查询总结任务上表现不佳&#xff0c;微软多部门联合提出Project GraphRAG&#xff08;大模型驱动的KG&#xff09;&#xff1b;2024年7月&#xff0c;微软正式开源GraphRAG项目&#xff0c;引起极大关注&#xff0c…

【前端面试题】宏任务与微任务的区别

宏任务与微任务的区别 JavaScript采用单线程模型&#xff0c;通过 事件循环&#xff08;Event Loop&#xff09; 机制处理异步操作。 类比于厨师上菜的过程&#xff0c;顾客点的菜可能存在容易处理的 “软菜” 与难处理的 “硬菜” &#xff0c;以及要加米饭酒水这些立马可以上…