【Linux】在Linux上写一个进度条小程序

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

👑作者主页@安 度 因
🏠学习社区安度因的学习社区
📖专栏链接Linux

文章目录

如果无聊的话就来逛逛 我的博客栈 吧! 🌹

一、前言

在前三篇文章中我们分别学习了 vim 、gcc 以及 make/makefile 。而在今天我们将基于前三节课认识的基础上并结合一些与回车换行、缓冲区有关的知识在 Linux 上写下一个简易的进度条小程序。

成品展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jK64mBQM-1673533983679)(https://anduin.oss-cn-nanjing.aliyuncs.com/%E8%BF%9B%E5%BA%A6%E6%9D%A17.gif)]

今天的内容比较轻松只需要了解两个知识点这个小程序就很容易写出来了让我们开始今天的学习。

二、理解 ‘\r’ 与 ‘\n’

C 语言中有很多字符而字符大体分为两类可显字符、控制字符。

控制字符不可显示例如 \r 和 \n 就是控制字符。

而在我们平时打字时一行写满了需要换行但是新起一行有很多种例如

image-20230112173730693

这样虽然新起一行了但是不是我们想要的结果。

我们通常新起一行是在第二行的最左端但是对于这个结果其实有两个操作

  1. 跳转到第二行
  2. 回到第二行的最左端

有了这个基本概念再来谈 \r 和 \n 的作用

  • \r 回车 - 回到文本行的开头
  • \n换行 - 新起一行

所以其实我们 平时泛指的换行实际上是 回车 + 换行

且在语言范畴下例如 C 语言换行就可以达到 回车 + 换行 的效果。在平常这一操作还是两个步骤。

三、行缓冲

行缓冲这个概念认识。

1、提出问题

首先先了解一下两个库函数

  • sleep Linux 下的休眠函数单位是秒。头文件为 #include <unistd.h>
  • fflush 刷新缓冲区

代码1

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

现象

在这里插入图片描述

分析

光标停留在文本行的开头但是字符串没有被打印。

反而像是 sleep 函数先起作用然后 printf 函数再从光标处开始打印。

打印完之后shell 提示符紧跟着字符串后显示。

代码2

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

现象

在这里插入图片描述

分析

printf 打印的字符串先显示在终端上光标位于字符串的下一行。

sleep 函数使程序休眠 3 秒后shell 提示符从光标位置开始显示。

代码3

#include <stdio.h>
int main()
{
    printf("hello xxx\r");
    sleep(3);
    return 0;
}

现象

在这里插入图片描述

分析

printf 打印的字符串没有显示到终端光标一直停留在该打印字符串的一行

sleep 函数休眠三秒后shell 提示符直接打印在了屏幕上。

并没有看到字符串。

观察上面的现象我们提出几个问题

  1. 代码 1 好像是先执行了 sleep 在执行 printf 是这样吗
  2. 代码 2 加上了 ‘\n’ 字符串一开始就显示了为什么
  3. 代码 3 好像什么都没打印这是为什么

在解答这些问题之后我们先了解一下行缓冲。

2、认识行缓冲

在内存中预留了一块空间用来缓冲输入或输出的数据这个保留的空间被称为缓冲区。

我们之前或多或少都听说过缓冲区。

在代码 1 中由于程序是按照数据执行的所以必定是先执行 printf 。

但是数据没有显示所以这时候数据就一定被保存在某个位置保存的位置就是缓冲区。

而要让数据显示是需要刷新缓冲区的。

行缓冲是缓冲区刷新策略的一种在行缓冲模式下当输入和输出中遇到 ‘\n’ 换行时就会刷新缓冲区

有了这个概念我们继续分析问题。

3、解答与拓展

解答

问题1代码 1 好像是先执行了 sleep 在执行 printf 是这样吗

当然不是。

由于程序是按照顺序执行的所以必定是先执行完 printf 在执行 sleep 。

而数据没有被显示出来的原因是数据保存在缓冲区中但是没有主动刷新当程序退出后保存在缓冲区中的数据被自动刷新出来了。

所以才会造成这种现象。

问题2代码 2 加上了 \n 字符串一开始就显示了为什么

这里由于是直接往显示器上打印所以采用的刷新方式为行缓冲。

所以执行碰到 ‘\n’ 时就会把在缓冲区中的(换行符之前)的内容全部刷新出来。

所以这段代码一开始就会有数据显示然后再 sleep 休眠。

问题3代码 3 好像什么都没打印这是为什么

之前说过 \r 是换行所以当 printf 遇到 \r 时就把光标移到开头。

sleep 睡眠后当程序退出shell 打印提示符时就覆盖了字符串。

拓展

数据真的是临时保留在缓冲区里的吗光标如何理解

我们用一段代码来理解这两个问题

#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("hello xxx\r");
    fflush(stdout);
    sleep(3);
    return 0;
}

现象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCFKMagU-1673533983682)(https://anduin.oss-cn-nanjing.aliyuncs.com/%E8%BF%9B%E5%BA%A6%E6%9D%A14.gif)]

观察现象我们发现当我们使用 fflush 主动刷新缓冲区后数据就显示在了屏幕上且因为 ‘\r’ 的原因光标指向字符串开头当打印 shell 提示符时就直接从光标位置开始覆盖。

所以对于这两个问题我们已经得到了答案

  1. 数据被临时保存在于缓冲区中通过刷新就可以显示
  2. 数据是从光标位置开始打印的。

一句话理解光标光标和显示器匹配光标在哪里显示器打印的时候就从哪里开始打印

4、倒计时

基于对上面的理解我们先实现一个简单的倒计时。

倒计时就是在屏幕上不断显示数字每次在同一位置显示并将之前的数据覆盖。

既然是每次要从头开始覆盖那么就可以用 ‘\r’ 来实现每次回到行首并且可以通过相应的格式化控制显示多位打印。

但是 ‘\r’ 不会主动刷新所以要用 fflush 函数主动刷新缓冲区。

在每次刷新之后使用 sleep 函数间隔一定的时间。

由此我们可以很轻松写出代码例如写一个从 10 开始的倒计时

#include <stdio.h>
#include <unistd.h>

int main()
{
    int i = 10;
    for (; i >= 0; i--) {
        // 位宽控制\r 回到开头
        printf("%2d\r", i);
        fflush(stdout); // 主动刷新
        sleep(1); // 休眠
    }
    printf("\n"); // 换行打印提示符
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vNYKYWQJ-1673533983682)(https://anduin.oss-cn-nanjing.aliyuncs.com/%E8%BF%9B%E5%BA%A6%E6%9D%A15.gif)]

四、进度条

好了接下来进入正题我们开始写 进度条

进度条样式

  • 主体样式为两个中括号包裹中间 => 推进的方式呈现比如[======>]
  • 主体右侧中括号位置保持不变中间元素不断推进比如[=> ]
  • 显示当前加载进度用 [num%] 显示num 随着进度条的不断推进而变化
  • 显示加载样式可以利用一个旋转的字符例如 [\] 的样式顺时针不断旋转

大约呈现状态为[========>] [15%] [\]

采用多文件

文件存放在 proc 目录中

  • proc.h 函数声明
  • proc.c 进度条逻辑
  • main.c 函数调用

makefile 准备

由于采用多文件所以依赖关系可以写成依赖文件列表的样式

image-20230112212907590

分块逻辑

  1. 进度条主体

预留进度条大小为 100 个 = 外加 1 个 > 加上保存 '\0' 的位置用数组存储为 102 个单位。

进度条是一行中的所以需要用到 '\r' 每次都需要使用 fllush 主动刷新缓冲区。

每次刷新出数据之后将 = 填充到数组中并且显示 > 。在最后一次显示时控制 > 不要显示。

然后休眠一小会。由于休眠用 sleep 函数太慢。所以可以用 usleep 函数休眠usleep 函数的参数单位是微秒。

根据这个写出代码

image-20230112220450323

在这里插入图片描述

  1. 百分比显示

%% 显示为一个 % 而 %d 为数字这步很简单只要在 printf 语句中加上内容

printf("[%-100s][%d%%]\r", bar, i);
  1. 旋转光标

光标旋转方向为顺时针旋转那么旋转时就可以用数组保存。

旋转每次显示内容分别为 | / - \ \\ 代表一个 \ 因为和 \ 结合的会被解析为转义字符将其保存到字符串中。

而由于字符串一共就四个字符所以输出的时候需要控制输出位置。

代码

const char* str = "|/-\\"; // 字符串
printf("[%-100s][%d%%][%c]\r", bar, i, str[i % 4]); // 输出语句

完整代码

proc.h

#pragma once 

#include <stdio.h>

extern void process();

proc.c

#include "proc.h"
#include <string.h>
#include <unistd.h>

#define SIZE 102 // 数组大小
#define STYLE '='
#define FLAG '>'

void process()
{
    const char* str = "|/-\\";
    char bar[SIZE];
    memset(bar, '\0', sizeof(bar));
    int i = 0;
    while (i <= 100) {
      printf("[%-100s][%d%%][%c]\r", bar, i, str[i % 4]); // 格式控制
      fflush(stdout); // 刷新
      bar[i++] = STYLE; // 填充数据
      if (i != 100) {
          bar[i] = FLAG; // 如果不是最后一次则显示 >
      } 
      usleep(100000); // 休眠
    }
    printf("\n");
}

test.c

#include "proc.h"

int main()
{
    process();
    return 0;
}

进度条展示

在这里插入图片描述

五、结语

到这里本篇博客就到此结束了。

今天的内容相对来说还是很简单的我们的今天核心就是了解 ‘\r’ 和 ‘\n’ 的区别并认识了行缓冲。只要了这两块知识写起进度条就很轻松了。

大家感兴趣也可以下去试一试。

如果觉得 a n d u i n anduin anduin 写的不错的话可以 点赞 + 收藏 + 评论 支持一下哦我们下期见~

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“【Linux】在Linux上写一个进度条小程序” 的相关文章