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

发布于:2025-05-23 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

\r 、\n、fflush

倒计时

进度条

进度条进阶版

结语


\r 、\n、fflush

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

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

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

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

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

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

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

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

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

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

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

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

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

倒计时

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

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

代码如下:

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,如下:

#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则代表左对齐

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

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

首先来解决第一个

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

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

const char* label = "|/-\\";

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

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

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

总代码如下:

#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");
}

进度条进阶版

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

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

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

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

#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,就是当前文件加载的百分比进度:

double rate = current*100.0/total;

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

#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 文件里面定义一个函数指针:

typedef void (*pb)(double, double);

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

#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;
}

结语

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

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


网站公告

今日签到

点亮在社区的每一天
去签到