【Linux 学习计划】-- 倒计时、进度条小程序

目录

[\r 、\n、fflush](#\r 、\n、fflush)

倒计时

进度条

进度条进阶版

结语


\r 、\n、fflush

首先我们先来认识这三个东西,这将会是我们接下来两个小程序的重点之一

首先是我们的老演员\n,也就是回车加换行

这里面其实包含了两个操作,一个叫做回车,一个叫做换行

而单纯的回车就是指,我们现在的光标,回到当前行的最前面

而换行 + 回车就是来到下一行的最前面

而我们的 \r 就是单纯的回车,回到当前行的最前面

但是我们还有一个缓冲区的概念

也就是,当我们使用了\n之后就会自动刷新缓冲区,但是\r不会

我们不想用\n是因为不想出现下面这样的情况:

所以我们只想回到第一行然后从头开始覆盖式地进行写入

但是如果是\r的话,就会出现一个情况就是,我们会等到程序结束之后才会把最后一次的结果打印出来,这时因为缓冲区不会因为\r刷新

所以我们这时候就需要fflush(stdout),这样就能在不回车的前提下达到提前刷新缓冲区的效果了

至于stdout,这就是系统默认打开的标准输出文件,其实我们这里可以粗浅的看作就是显示器,我们需要刷新缓冲区才能让显示器上显示结果

倒计时

至于倒计时,其实相当简单,我们只需要知道,这是在不停地打印数字,并且会在同一个位置打印,这就需要用到回车(\r)回到最前面,然后覆盖掉前面的结果以达到原地变化的效果

但是由于\r不会刷新缓冲区,所以我们就需要使用fflush刷新,这样,我们的倒计时就写好了

代码如下:

cpp 复制代码
void time_count_down(int total)
{
    int cnt = total;
    while(cnt >= 0)
    {
        printf("倒计时: %2d\r", cnt);
        fflush(stdout);
        cnt--;
        usleep(300000);
    }
    printf("\n");
}

进度条

首先,我们需要知道的是,进度条的本质和倒计时是一样的,就是\r移动光标,通过覆盖式写入达到变化的效果

我们先来看看成品的样子:

分析一下,首先我们需要预留出一百个空间,表现出递进的效果

这一步我们可以直接建一个数组,然后一个循环,每循环一次,就打印出当前数组里面的内容,并且在当前数组最后一个 " = " 后面再加一个等号,这样,我们就达到了循环递进的效果

但是,我们需要注意的是,这期间我们需要一直使用 \r 和 fflush,如下:

cpp 复制代码
#define LENGTH 101
#define STYLE '='

void ProcessBar()
{
    char bar[LENGTH];
    memset(bar, '\0', sizeof bar);

    int cnt = 0;
    while(cnt <= 100)
    {
        printf("[%-100s]\r", bar);
        fflush(stdout);
        bar[cnt++] = STYLE;
        usleep(10000);
    }
    printf("\n");
}

注意,上面的 "%-100d" 中,100就代表默认留出100个位置,-100则代表左对齐

接着,我们可以看到,进度条后面还有两个东西:

这两个,一个是实时显示当前加载进度的数字显示,还有一个是想通过 |/-\ 这四个字符,达成一条线在不停旋转的动态效果,也就是在提醒用户,可能用户看到进度条不动了以为是网卡了,但其实还在下载,只是进度比较慢,暂时卡住了而已,动态的话就是为了避免这个问题

首先来解决第一个

这个其实我们只需要把数字填进去就可以了,只是后面的%需要写两个而已,写一个显示不出来

而最后一个就是,我们先写一个数组:

cpp 复制代码
const char* label = "|/-\\";

我们只需要不停的从左往右选择即可,但是由于会越界,所以我们就需要%一个数组大小,这样才不会越界

而其中 \\ 有两个是因为只写一个不显示

比如,我现在数组大小是4,如果当前数字大小是4,4%4 = 0,那么就会直接回到第一个数组下标位置,5%4就是1,代表第二个位置,依此类推

总代码如下:

cpp 复制代码
#define LENGTH 101
#define STYLE '='
const char* label = "|/-\\";

void ProcessBar()
{
    char bar[LENGTH];
    memset(bar, '\0', sizeof bar);
    int len = strlen(label);

    int cnt = 0;
    while(cnt <= 100)
    {
        printf("[%-100s][%3d%%][%c]\r", bar, cnt, label[cnt % len]);
        fflush(stdout);
        bar[cnt++] = STYLE;
        usleep(10000);
    }
    printf("\n");
}

进度条进阶版

现实中,我们的进度条都是搭配了任务一起的

比如我们可以写一个下载的任务,影响下载的因素这里假设只有带宽,他就相当于下载速度吧在这里

然后我们只需要提供一个文件总大小,模拟这个过程即可

代码如下(模拟下载任务的代码):

cpp 复制代码
#define bandwidth 1024*1024*1.0;

void download(double file_size)
{
    double current = 0.0;
    while(current <= file_size)
    {
        //调用进度条
        ProcessBar(file_size, current);

        current += bandwidth;
        usleep(20000);
    }
    printf("\n");
}

这时候我们的进度条文件就需要更改一下了

由于会被频繁调用,所以我们的进度条在这里,每一次调用都代表那一瞬间的状态

所以我们在进度条那里的循环可以只是循环添加 =

接着,我们的进度这次就是按照百分比来算的了

所以进度条需要一个总的文件大小,以及现在文件加载到哪里了,这样我们将两个文件除出来之后再乘100,就是当前文件加载的百分比进度:

cpp 复制代码
double rate = current*100.0/total;

接下来的都是小小修改一下而已,代码如下:

cpp 复制代码
#define LENGTH 101
#define STYLE '='
const char* label = "|/-\\";


void ProcessBar(double total, double current)
{
    char bar[LENGTH];
    memset(bar, '\0', sizeof bar);
    int len = strlen(label);

    double rate = current*100.0/total;
    int loop_count = (int)rate;
    int cnt = 0;
    while(cnt <= loop_count)
    {
        bar[cnt++] = STYLE;
    }

    printf("[%-100s][%.1lf%%][%c]\r", bar, rate, label[cnt % len]);
    fflush(stdout);
}

最后,我们还可以再来一个小优化

试想一下,我们以后如果有了图形化界面,或者就是单纯更好的进度条,对于这个下载任务而言,如果我们只是用一个函数指针的话,到时候我们只需要修改指针即可,这样的话,修改起来就很方便,当我们有了更好的进度条的时候

如下,我们可以先在 .h 文件里面定义一个函数指针:

cpp 复制代码
typedef void (*pb)(double, double);

接着,我们的下载任务里面,就可以直接加上这个参数了:

cpp 复制代码
#define bandwidth 1024*1024*1.0;

void download(double file_size, pb procbar)
{
    double current = 0.0;
    while(current <= file_size)
    {
        procbar(file_size, current);
        current += bandwidth;
        usleep(20000);
    }
    printf("\n");
}


int main()
{
    download(100.0*1024*1024, ProcessBar);
    return 0;
}

结语

这篇文章到这里就结束啦!!~( ̄▽ ̄)~*

如果觉得对你有帮助的,可以多多关注一下喔

相关推荐
自强的小白36 分钟前
vlan(局部虚拟网)
网络·学习
一只乔哇噻1 小时前
java后端工程师进修ing(研一版 || day41)
java·开发语言·学习·算法
知识分享小能手1 小时前
React学习教程,从入门到精通,React 使用属性(Props)创建组件语法知识点与案例详解(15)
前端·javascript·vue.js·学习·react.js·前端框架·vue
知识分享小能手7 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
茯苓gao10 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾10 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT11 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa11 小时前
HTML和CSS学习
前端·css·学习·html
看海天一色听风起雨落12 小时前
Python学习之装饰器
开发语言·python·学习
speop13 小时前
llm的一点学习笔记
笔记·学习