【Linux】简易进度条的实现


🎉博主首页: 有趣的中国人

🎉专栏首页: Linux

🎉其它专栏: C++初阶 | C++进阶 | 初阶数据结构

小伙伴们大家好,本片文章将会讲解Linux进度条的实现的相关内容。
如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!

文章目录

  • [`1. 关于回车 & 换行`](#1. 关于回车 & 换行)
  • [`2. 简述缓冲区`](#2. 简述缓冲区)
  • [`3. 倒计时程序的编写`](#3. 倒计时程序的编写)
  • [`4. 简易进度条的实现`](#4. 简易进度条的实现)

1. 关于回车 & 换行

回车和换行是文本显示和处理的相关术语,我们常常认为这两个是同一概念,实则不然。

👨‍💻回车换行的概念

回车(Carriage Return):在打字机时代,回车指的是将打字机的打印头(称为"carrier")移回到行首的操作。在计算机时代,回车通常表示将光标移动到当前行的开头,而不会换到下一行。在ASCII字符集中,回车通常用"\r"表示。
换行(Line Feed)::换行是指将光标移动到下一行的操作,使得文本在纵向上向下移动一个行高。在ASCII字符集中,换行通常用"\n"表示。

在Unix和类Unix系统(如LinuxmacOS)中:通常使用换行字符("\n")来表示换行。

在Windows系统中:,通常使用回车和换行的组合来表示换行,即"\r\n"。


2. 简述缓冲区

缓冲区(Buffer)是计算机内存中的一块特定区域,用于临时存储数据。它在许多计算机系统和应用程序中发挥着重要作用,通常用于临时存储输入数据、输出数据或在内存和其他设备之间进行数据传输。

输入缓冲区:用于暂时存储从输入设备(如键盘、鼠标、网络接口等)接收到的数据 ,直到程序能够处理它们。输入缓冲区使得程序可以按需处理输入,而不必担心输入数据的速度与程序处理速度不匹配的问题
输出缓冲区:用于暂时存储将要发送到输出设备(如显示器、打印机、网络接口等)的数据 ,直到设备准备好接收它们。输出缓冲区可以提高数据传输的效率 ,因为程序不必等待设备就绪就可以继续执行。

👨‍💻缓冲区何时被清理

拿C语言举个例子:

在C语言中,标准库函数printf()用于将格式化的数据打印到标准输出流(通常是终端)。但是,printf()函数并不会立即将数据显示到终端上。相反,它会将数据写入到输出缓冲区中 。输出缓冲区是一个临时存储区域,用于存放printf()函数打印的数据,直到满足一定条件时才将其刷新(即将数据发送到终端并显示出来)。

这些条件包括:

1. 遇到换行符 \nprintf()函数遇到换行符时,输出缓冲区会被自动刷新 ,将缓冲区中的数据输出到终端并显示出来。

**2. 缓冲区满:**当输出缓冲区满了,它也会被自动刷新。

**3.调用fflush()函数:**显式调用fflush(stdout)函数可以强制刷新输出缓冲区,将其中的数据输出到终端。

**4. 程序结束:**当程序正常终止时,所有的缓冲区都会被刷新。


3. 倒计时程序的编写

有了以上的知识储备,咱们就可以尝试编写一下简单的倒计时程序了,思路如下:

  • 首先新建一个time.c文件,然后再用我们之前讲的makefile工具来实现time.c文件的自动构建:
  • 编写time.c这个文件,实现思路:
  1. 假设我们倒数10s,到0s时结束,因此需要一个循环;
  2. 循环中,我们要实现每次出来一个数,都要对之前的数进行覆盖,所以要用到回车"\r";
  3. 由于printf()函数会会将输出的结果先输出到缓冲区,回车不会冲刷缓冲区,因此每次要用fflush(stdout)强制冲刷缓冲区;
  4. 每次循环秒数减1,并让程序休眠1s

🌝详细代码如下:

c 复制代码
#include <stdio.h>
#include <unistd.h>
int main()
{
    int cnt = 10;

    while(cnt >= 0)
    {
    	// 打印的时候每次覆盖上一次出现的数字
        printf("倒计时:%2d\r",cnt);
        // 强制冲刷缓冲区
        fflush(stdout);
       --cnt;
        sleep(1);
    }
    printf("\n");
    return 0;
}
  • make命令进行编译:(⏳这边就可以动态运行了哈,感兴趣的可以自己试一下⌛)
  • 这里有个小拓展,如果我们要覆盖上次的数字是4位,这次是三次(比如1000到999),可以用%4d这个输出形式来解决,也可以用下面这种方法:
c 复制代码
#include <stdio.h>
#include <unistd.h>
int main()
{
    int cnt = 1000;
    int tmp = cnt;
    int num = 0;
    while (tmp)
    {
        ++num;
        tmp /= 10;
    }
    while(cnt >= 0)
    {
    	// 主要就是这里的变化,用最大数字的位数来做占位符
        printf("倒计时:%*d\r",num, cnt);
        fflush(stdout);
       --cnt;
        sleep(1);
    }
    printf("\n");
    return 0;
}

4. 简易进度条的实现

好啦,有了以上的知识作为基础,咱们就可以进入正题啦!😎编写简易的进度条。😎

👨‍💻效果图展示

总共有三个部分:

  1. 我们要实现的进度条用#来进行加载;

  2. 后面要有数据来表示现在加载的进度是多少(百分数);

  3. 最后用一个动态旋转的类来表示程序还在继续加载

👨‍💻实现思路

1. 动态加载的过程

动态加和之前的倒计时差不多,每次都要覆盖上次出现的#,具体思路如下:

  1. 定义一个字符类型数组 char *str,用memset()函数进行初始化('\0');

  2. 循环100次,每次循环都在数组中加一个#,并打印str('\r'进行覆盖);

  3. 强制冲刷缓冲区;

2. 进度加载 我们可以用每次循环的次数来当作是当前加载的进度,当然还要进行覆盖,具体思路如下:

  1. 每次循环都以当前的循环次数作为加载进度;

  2. 每次覆盖上一次的进度;

  3. 强制冲刷缓冲区。

  4. 程序休眠(可以用usleep()函数,单位是微秒)

3. 动态旋转 定义一个数组,并初始化为-\\/-,覆盖的方法和之前类似,就不详细说了。

👨‍💻具体代码实现

c 复制代码
#include "process_bar.h"
#include <memory.h>
#include <unistd.h>
#define style '#'
#define round "-\\/-"
void test()
{
    int i = 0;
    char str[100];
    memset(str,'\0',sizeof(str));
    while (i <= 100)
    {
        str[i] = style;
        printf("[%-100s][%d%%][%c]\r",str,i,round[i % 4]);
        fflush(stdout);
        ++i;
        usleep(10000);
    }
    printf("\n");
}

👨‍💻第二版本

我们正常用进度条肯定不是单独使用的,会结合其他的场景,例如下载界面,登陆界面

对于要下载的文件,肯定有文件大小,下载的时候网络也有它的带宽,所以在下载的时候,每次下载的大小都是一个带宽,我们可以先写一个下载的函数:

download函数:

go 复制代码
void download()
{
   double bandwidth = 1024 * 1024 * 1.0;
   double filesize = 1024 * 1024 * 10.0;
   double cur = 0.0;
   while (cur <= filesize)
   {
   	   // 调用进度条函数
       test(filesize, cur);
       // 每次增加带宽
       cur += bandwidth;
       usleep(20000);
   }
   printf("\n");
   printf("this file has been downloaded\n");
}

进度条函数:

go 复制代码
void test(double total, double current)
{
    char str[101];
    memset(str,'\0',sizeof(str));
    int i = 0;
    // 这次的比率
    double rate = (current * 100) / total;
    // 循环次数
    int loop_count = (int)rate;
    while (i <= loop_count)
    {
        str[i++] = style; 
    }
    printf("[%-100s][%.1lf%%][%c]\r",str,rate,round[loop_count % 4]);
    fflush(stdout);
}

回调函数版本(完整):

c 复制代码
// 头文件 process_bar.h
#include <stdio.h>

typedef void(*callback_t)(double, double);// 函数指针(回调函数)

void test(double total, double current);

// 函数实现文件 process_bar.c
#include "process_bar.h"
#include <memory.h>
#include <unistd.h>
#define style '#'
#define round "-\\/-"

void test(double total, double current)
{
    char str[101];
    memset(str,'\0',sizeof(str));
    int i = 0;
    double rate = (current * 100) / total;
    int loop_count = (int)rate;
    while (i <= loop_count)
    {
        str[i++] = style; 
    }
    printf("[%-100s][%.1lf%%][%c]\r",str,rate,round[loop_count % 4]);
    fflush(stdout);
}

// main.c 主函数和 download 函数
#include "process_bar.h"
#include <unistd.h>

double bandwidth = 1024 * 1024 * 1.0;
void download(double filesize, callback_t cb)
{
   double cur = 0.0;
   while (cur <= filesize)
   {
       cb(filesize, cur);
       cur += bandwidth;
       usleep(20000);
   }
   printf("\n");
   printf("this file has been downloaded\n");
}

int main()
{
    download(1024*1024*100.0,test);
    download(1024*1024*20.0,test);
      
    return 0;
}
相关推荐
不想起昵称9299 分钟前
Linux SHELL脚本中的变量与运算
linux
the丶only38 分钟前
单点登录平台Casdoor搭建与使用,集成gitlab同步创建删除账号
linux·运维·服务器·docker·gitlab
ccubee1 小时前
docker 安装 ftp
运维·docker·容器
枫叶红花2 小时前
【Linux系统编程】:信号(2)——信号的产生
linux·运维·服务器
yaosheng_VALVE2 小时前
探究全金属硬密封蝶阀的奥秘-耀圣控制
运维·eclipse·自动化·pyqt·1024程序员节
_微风轻起2 小时前
linux下网络编程socket&select&epoll的底层实现原理
linux·网络
dami_king2 小时前
SSH特性|组成|SSH是什么?
运维·ssh·1024程序员节
启明真纳2 小时前
elasticache备份
运维·elasticsearch·云原生·kubernetes
苹果醋32 小时前
SpringBoot快速入门
java·运维·spring boot·mysql·nginx