文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Linux | 手把手教你写一个进度条小程序

2023-08-31 20:48

关注

在这里插入图片描述

文章目录

今天,就让我们利用前面所学习的知识,在Linux上写一个小程序,来检验一下自己掌握的程度

【成品展示】

在这里插入图片描述


在写进度条之间呢,我们要理清两个概念,首先来看看什么是\r\n

1、可显字符与控制字符

在C语言中呢,有很多的字符,大致分为【可显字符】和【控制字符】两大类

在我们日常写代码,写文章的过程中,写完一行后若是没有自动换行就需要敲下键盘中的Enter键来达到换行的效果。可是对于这个按键,实际上它在计算机内部做了两件事 —— 【换行】+【回车】

在这里插入图片描述

2、老式键盘

在我们使用的键盘中,看到的Enter回车键,莫过于下面这两种,第一种出现在台式机多一些,第二种出现在笔记本多一些。不过就这么看来,还是这种老式键盘符合【换行】+【回车】的这么一个概念,也就是新起一行,然后在回到当前行的行首

在这里插入图片描述

了解了\r\n的概念之后,我们继续来谈谈【缓冲区】的概念

1、五个代码段 + 现象分析

在这之前先普及两个Linux下的库函数

  1. sleep() —— 睡眠
    • Windows中的sleep()单位是毫秒;Linux中的sleep()单位是
    • 其包含在头文件中,我们通过【man 3 sleep】来进行查看

在这里插入图片描述

  1. fflush() —— 刷新流
    • 格式:int fflush(FILE *stream);
    • C语言呢一般默认会为我们提供三个流,即【标准输入stdin】、【标准输入stdout】、【标准错误stderror】,一般用来刷新输出流即stdout

接下去我们通过五段代码来逐步理解行缓冲的概念

代码1

#include #include int main(){printf("hello linux!\n");sleep(3);return 0;}

现象观察

在这里插入图片描述


代码2

#include #include int main(){printf("hello linux!");sleep(3);return 0;}

现象观察

在这里插入图片描述


代码3

#include #include int main(){printf("hello Makefile!");fflush(stdout);sleep(3);return 0;}

现象观察

在这里插入图片描述


代码4

#include #include int main(){printf("hello linux!\r");sleep(3);return 0;}

现象观察

在这里插入图片描述


代码5

#include #include int main(){printf("hello linux!\r");fflush(stdout);sleep(3);return 0;}

现象观察

在这里插入图片描述

2、观察现象,提出问题❓

通过观察上面5个代码段以及它们所产生的现象,我们可以提出这样的问题

  1. 当不加换行符\n时为何会先睡眠再打印?
  2. 为何带上\n后数据会立马先显示出来,睡眠后才显示提示符?
  3. 在加上回车\r后为什么看不到我们要的数据?刷新一下就有了呢?

3、行缓冲的概念 + 疑难解答

接下去我们就正式地来谈谈【缓冲区】的概念。文字居多、都是概念,还望理解😄

当我们编写完代码的时候,要将数据进行输出,此时在内存中会为其预留了一块空间,用来缓冲输入或输出的数据,这个保留的空间被称为缓冲区

  1. 当不加换行符\n时为何会先睡眠再打印?

    • 对于我们写的这段代码而言,属于顺序执行,所以一定是从上到下执行下来的,因此一定会先打印printf()语句中的内容。
    • 我们看不到这个内容不代表它不存在,只是它被预存在了缓冲区中,因为sleep()函数的缘故,导致这个缓冲区没有被刷新而已,所以它并没有丢失
  2. 为何带上\n后数据会立马先显示出来,睡眠后才显示提示符?

    • 无论带不带\n,数据都会被保存在缓冲区里。缓冲区有很多自己的刷新策略【往显示器上打印就是行缓冲】,只要碰到了换行符,就会把换行符之前的所有内容全部显示出来
    • 所以字符串是以行缓冲的方式保存在了行缓冲区里,最后会显示出来的原因是我们的程序要退出了,所以曾经缓冲在缓冲区里的数据就会被刷新出来
      在这里插入图片描述
  3. 在加上回车\r后为什么看不到我们要的数据?刷新一下就有了呢?

    • 上面我们有谈到\r\n的特点,知道了对于前者来说会回到当前行的行首,那是什么回到行首呢?通过观察动图可以发现是光标。当我们在输入一个字符时,光标就会后移,也就会移动到下一个要输入字符的位置。因此在我们什么都不加的时候便会顺着打印【命令提示符】
      在这里插入图片描述

    • 那其实就可以说得通了,因为\r的原因,光标回到了行首,因此在3秒的等待后shell输出了【命令提示符】,便覆盖了我们原本所想要输出的内容
      在这里插入图片描述

4、小结

好,看完行缓冲之后,也解答了遗留下来的疑难问题,我们来对其进行一个总结✒

通过前面学习的有关\r\n以及缓冲区的知识,相信解开了你一直以来的很多困惑,现在我们先利用前面所学的一些知识,去实现一个数字倒计时的功能

1、实现从9 ~ 0的倒计时

  1 #include <stdio.h>  2 #include <unistd.h>  3   4 int main(void)  5 {  6     int i = 9;  7     while(i >= 0)  8     {  9         printf("%d\n", i); 10         sleep(1); 11         i--;                  12     } 13     return 0; 14 }

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


2、进阶:10 ~ 0的倒计时

再加个码,若是我从10开始倒计时,该怎么修改程序呢?你可试着先自己写写看✍

在这里插入图片描述

显示器打印原理解释

那要如何去修改呢?关于这点我要先普及一下有关显示器打印的原理

所以我们一般把【键盘】【显示器】这些称做为字符设备

3、错误修改

知道了显示器字符打印的原理,接下去我们就可以对上面的代码做一个修改

printf("%2d\r", i); //C语言中的格式化占位符

在这里插入图片描述

好,终于来到了我们心心念念的【进度条】了,有关我为什么要将前面的这些知识做铺垫,你看完本模块就知道了😄

1、准备工作

在这里插入图片描述

在这里插入图片描述

2、进度条样式说明

准备工作做好之后,我们来看看需要实现的进度条需要是一个什么样子

在这里插入图片描述
这就是进度条的大致形状,要先有个数 [=======================>][100%][/]

3、主体进度推进实现【重点】

首先要实现的是主体进度条的推进时间,先简单地实现一下这个进度条不断变长的过程(๑•̀ㅂ•́)و✧

下面是详细介绍,想直接看整体代码的可以拉到最后面

#define SIZE 101
memset(bar, '\0', sizeof(bar));//初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小

下面是代码

  1 #include "proc.h"  2   3 #define SIZE 101  4   5 void progress()  6 {  7     char bar[SIZE];  8     memset(bar, '\0', sizeof(bar));  9     //初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小 10  11     int i = 0; 12     while(i <= 100)           13     { 14         printf("[%s]\n", bar); 15         bar[i] = '='; 16         i++; 17         sleep(1); 18     } 19 }

然后来看一下上面这段代码的演示过程

在这里插入图片描述

printf("[%s]\r", bar); 

在这里插入图片描述

不过可以看到,并没有显示出任何东西,此时相信你一定可以回答出来了,那就是:缓冲区没刷新!

fflush(stdout);

在这里插入图片描述

不过可以观察到这个进度条推进得很慢,那有没有办法使它快一点呢❓

在这里插入图片描述
在这里插入图片描述

//sleep(1); 1秒// usleep(1000000);  1秒                          usleep(100000);  //0.1秒

在这里插入图片描述

printf("[%100s]\r", bar);

在这里插入图片描述

printf("[%-100s]\r", bar);

在这里插入图片描述

在这里插入图片描述

  1 #include "proc.h"  2   3 #define SIZE 51  4   5 void progress()  6 {  7     char bar[SIZE];  8     memset(bar, '\0', sizeof(bar));  9     //初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小 10  11     int i = 0; 12     while(i <= 50) 13     { 14         printf("[%-50s]\r", bar);                         15         bar[i++] = '='; 16         fflush(stdout); 17        // usleep(1000000);  1秒 18         usleep(100000); //0.1秒 19     } 20     printf("\n"); 21 }
#define STYLE '='  #define ARR '>'  
#define SIZE 52
if(i != 50) bar[i] = ARR;  

此时来看的话我们进度条的主体部分就做完了

在这里插入图片描述

4、百分比递增实现

接下去我们来实现一下百分比递增这块

printf("[%-50s][%d]\r", bar, i * 2);

在这里插入图片描述

在这里插入图片描述

printf("[%-50s][%d%%]\r", bar, i * 2);

在这里插入图片描述

5、旋转字符轮回实现

接下去来实现最后的一个旋转字符的轮回,已到达图形化界面中的缓冲功能

const char* label = "|/-\\";
printf("[%-50s][%d%%][%c]\r", bar, i * 2, label[i % 4]);

最后,就实现了一个完整的进度条

在这里插入图片描述

void processBar(int speed)usleep(speed);
#define TOP 100#define BODY '='#define RIGHT '>'

6、整体代码展示

最后,再将我们上面所写的代码展示一下

processBar.h

  1 #include <stdio.h>  2 #include <unistd.h>  3 #include <string.h>  4   5 #define TOP 100  6 #define BODY '='       7 #define RIGHT '>'  8   9 extern void processBar(int speed);

processBar.c

  1 #include "processBar.h"               2                3 const char* label = "|/-\\";               4                5 void processBar(int speed)               6 {               7     char bar[TOP];               8     int len = strlen(label);               9     int cnt = 0;              10               11     memset(bar, '\0', sizeof(bar));              12    //初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小      13     while(cnt <= 100) 14     {  15         printf("[%-100s][%d%%][%c]\r", bar, cnt, label[cnt % len]);        16         fflush(stdout);     // 刷新缓冲区 17                  18         bar[cnt++] = BODY; 19         if(cnt < 100)   bar[cnt] = RIGHT; 20         usleep(speed);                  21     }                      22     printf("\n");                   23 }  

main.c

  1 #include "processBar.h"  2   3 int main(void)  4 {  5     processBar(100000);6     return 0;  7 }

好,接下去我们做一些进阶的训练,主要对一些基础好的同学,如果看不懂只掌握上面的即可

1、单次进度条推进逻辑

  1 #include "processBar.h"  2   3 const char* label = "|/-\\";  4 char bar[TOP];  5   6 // 单次的进度推进  7 void processBar(int rate)  8 {  9     if(rate < 0 || rate > 100)  return; 10                       11     int len = strlen(label);  12     printf("[%-100s][%d%%][%c]\r", bar, rate, label[rate % len]);                     13     fflush(stdout);     // 刷新缓冲区                     14     bar[rate++] = BODY;       15     if(rate < 100)  bar[rate] = RIGHT;                    16 } 
  1 #include "processBar.h"  2   3 int main(void)  4 {  5     int total = 1000;  6     int curr = 0;  7   8     while(curr <= total)  9     { 10         processBar(curr * 100 / total); 11         // 进行某种下载任务                       12         curr += 10;         13         usleep(100000);     14     }                       15     printf("\n");           16     return 0;               17 }       

在这里插入图片描述


2、回调函数解耦合

好,接下去我们再做一个提升,展现一点真实的业务场景

typedef void (callback_t)(int rate); 
void downLoad(callback_t cb)
  6 // 模拟一种安装或下载的场景(回调函数)                    7 void downLoad(callback_t cb)                    8 {                    9     int total = 1000;   // 1000B                   10     int curr = 0;       // 0B                   11                    12     while(curr <= total)                   13     {                   14                            15         usleep(50000);     // 模拟下载时间                   16                    17         // 计算下载速率                   18         int rate = curr * 100 / total;                   19         cb(rate);   // 通过函数指针去调用对应的函数                   20                    21         // 循环下载了一部分                   22         curr += 10;                   23     }                   24     printf("\n");                   25 }  
downLoad(processBar);

在这里插入图片描述

不过呢这样还彰显不出回调函数的功能,我们尝试传入多次试试,模拟一下通过回调函数去调用不同的函数场景

 27 int main(void) 28 { 29     // 将所需要的调用的函数地址传递给回调函数 30     printf("download 1:\n"); 31     downLoad(processBar); 32     33     printf("download 2:\n"); 34     downLoad(processBar); 35     36     printf("download 3:\n"); 37     downLoad(processBar); 38     39     printf("download 4:\n"); 40     downLoad(processBar); 41     return 0; 42 }

在这里插入图片描述

void initBar()            {                         memset(bar, '\0', sizeof(bar));}  
if(rate < 100)        bar[rate] = RIGHT;else                  initBar(); 

我们来看一下最终效果

在这里插入图片描述

3、整体代码展示

processBar.h

  1 #include <stdio.h>  2 #include <unistd.h>  3 #include <string.h>  4   5 #define TOP 100  6 #define BODY '='  7 #define RIGHT '>'  8   9 extern void processBar(int rate); 10 extern void initBar(); 

processBar.c

  1 #include "processBar.h"  2   3 const char* label = "|/-\\";      4 char bar[TOP];  5                        6 void initBar()  7 {               8     memset(bar, '\0', sizeof(bar));  9 }       10   11 // 单次的进度推进 12 void processBar(int rate) 13 {                         14     if(rate < 0 || rate > 100)  return; 15             16     int len = strlen(label); 17     printf("[%-100s][%d%%][%c]\r", bar, rate, label[rate % len]); 18     fflush(stdout);     // 刷新缓冲区 19     bar[rate++] = BODY;               20     if(rate < 100)      21         bar[rate] = RIGHT; 22     else                   23         initBar(); 24 }  

main.c

  1 #include "processBar.h"  2   3 // 函数指针类型  4 typedef void (callback_t)(int rate);  5   6 // 模拟一种安装或下载的场景(回调函数)  7 void downLoad(callback_t cb)  8 {  9     int total = 1000;   // 1000B 10     int curr = 0;       // 0B 11  12     while(curr <= total) 13     { 14          15         usleep(50000);     // 模拟下载时间 16       17         // 计算下载速率        18         int rate = curr * 100 / total;     19         cb(rate);   // 通过函数指针去调用对应的函数 20                         21         // 循环下载了一部分            22         curr += 10;     23     } 24     printf("\n");           25 }                     26       27 int main(void)    28 { 29     // 将所需要的调用的函数地址传递给回调函数 30     printf("download 1:\n"); 31     downLoad(processBar); 32                   33     printf("download 2:\n"); 34     downLoad(processBar); 35  36     printf("download 3:\n"); 37     downLoad(processBar); 38 }

好,我们来总结一下本文所学习的内容

以上就是本文所要阐述的所有内容,感谢您的阅读🌹

在这里插入图片描述

来源地址:https://blog.csdn.net/Fire_Cloud_1/article/details/129104100

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯