Linux 编程入门:打造你的首个动态进度条

目录

1、补充:回车与换行

2、行缓冲区

[(1) make执行下述代码会出现何现象?](#(1) make执行下述代码会出现何现象?)

[(2) make执行下述代码会出现何现象?](#(2) make执行下述代码会出现何现象?)

[(3) 如何让不带【\n】的字符串自动行刷新?](#(3) 如何让不带【\n】的字符串自动行刷新?)

3、练手:倒计时代码

4、进度条代码

[(1) process.c](#(1) process.c)

[(2) process.h](#(2) process.h)

[(3) main.c](#(3) main.c)

[(4) 效果演示](#(4) 效果演示)


进度条动态效果演示

进度条

1、补充:回车与换行

(1) 回车:

解释为将光标移至行首,转义表达为【\r】。

(2) 换行:

解释为将光标移至下一行,转义表达为【\n】。

(3) 老式打字机:

在早期的打字机上,回车操作是指将打印机的打印头(Carriage)复位到行的起始位置,以便开始新的一行;换行操作是指使滚筒上卷,带动纸张向上移动一行的距离,以开启新的一行。

2、行缓冲区

前提:makefile里是下述内容,会方便执行。

bash 复制代码
$ vim Makefile
  1 SRC=$(wildcard *.c)
  2 OBJ=$(SRC:.c=.o)
  3 BIN=processbar
  4 
  5 $(BIN):$(OBJ)
  6     gcc -o $@ $^
  7 
  8 %.o:%.c
  9     gcc -c $<
 10 
 11 .PHONY:clean
 12 clean:
 13     rm -f $(OBJ) $(BIN)

(1) make执行下述代码会出现何现象?

cpp 复制代码
$ vim myproc.c
 1 #include <stdio.h>
 2 int main()
 3 {
 4     printf("hello Linux!\n");
 5     sleep(3);
 6     return 0;
 7 }

现象:我们会观察到显示屏先出现"hello Linux",再停留三秒后才启动下一命令行。

解释:【\n】可以行刷新,程序走到\n会自动刷新在缓冲区的这一行字符,所以先显示字符串了。

(2) make执行下述代码会出现何现象?

cpp 复制代码
$ vim myproc.c   
 1 #include <stdio.h>
 2 int main()
 3 {
 4     printf("hello Linux!");
 5     sleep(3);
 6     return 0;
 7 }

现象:显示器上三秒后才出现字符串,新的命令行也紧跟字符串后面。为什么printf明明在sleep前面,却在休眠后显示?

解释 :首先要明确的是,在程序内printf是比sleep先执行的 ,但是显示器没有显示是因为,【\r】没有行刷新功能,在休眠期间,"hello Linux"一直在缓冲区,没有被刷新 ,最后又被显示出来是因为程序退出会自动刷新一次缓冲区

(3) 如何让不带【\n】的字符串自动行刷新?

众所周知,**运行程序时,系统会默认打开三个文件:stdin,stdout,stderr,分别对应的设备文件是键盘,显示器,显示器。**而我们使用的printf会把字符串写到标准输出stdout中,但由于没有【\n】,字符串就留在缓冲区。

要解决该问题,我们介绍一个函数:fflush是 C 语言标准库中的一个函数,用于控制输出流。它定义在 <stdio.h> 头文件中,其作用是将指定的输出流(通常是文件流)中的缓冲区内容强制写入到文件或设备中。这个函数可以用来确保缓冲区中的数据被立即输出,而不是等到缓冲区满了或者程序结束时。

cpp 复制代码
$ vim myproc.c
 1 #include <stdio.h>
 2 int main()
 3 {
 4     printf("hello Linux!");
 5     fflush(stdout);
 6     sleep(3);
 7     return 0;
 8 }

执行上述代码,我们发现与第一组代码执行现象一致。

3、练手:倒计时代码

cpp 复制代码
  1 #include <stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5     int num = 10;
  6     while(num >= 0)                                                                                                       
  7     {
  8         printf("%-2d\r",num); // 左对齐,位宽为2
  9         num--;
 10         fflush(stdout); // [\r]不能行刷新,要用到fflush
 11         sleep(1);
 12     }
 13     printf("\n");
 14     return 0;
 15 }

4、进度条代码

(1) process.c

cpp 复制代码
$ vim process.c
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 
  5 #define NUM 101
  6 #define STYLE '='
  7 
  8 void process_v1()
  9 {
 10     char buffer[NUM];
 11     memset(buffer,0,sizeof(buffer)); // buffer数组里全设置成0
 12     const char* table = "|/-\\"; // [\\]表示[\]
 13     int len = strlen(table);
 14 
 15     int cnt = 0;
 16     while(cnt <= 100)
 17     {
 18         printf("[%-100s][%d%%][%c]\r",buffer,cnt,table[cnt%len]);
 19         fflush(stdout);
 20         buffer[cnt] = STYLE;
 21         cnt++;
 22         usleep(50000); // 以微秒为单位休眠
 23     }
 24     printf("\n");
 25 }

这样的进度条已经初具模型了,但是我们使用进度条一般是一般加载资源一边刷新进度条,而不是单纯的自己循环,接下来我们实现第二个版本。版本2的不用自主实现循环而是与资源下载相关联,资源下载实现在了main函数所在的文件。

cpp 复制代码
 27 void FlushProcess(double total, double current)
 28 {
 29     char buffer[NUM];
 30     memset(buffer,0,sizeof(buffer));
 31     const char* table = "|/-\\";
 32     int len = strlen(table);
 33 
 34     static int cnt = 0; // 全局静态
 35     int num =(int)(current*100/total); // 确定填充的大小范围
 36     for(int i = 0;i < num;i++) // 填充"=",不用自己循环
 37     {
 38         buffer[i] = STYLE;
 39     }
 40     // 比例
 41     double rate = current/total;
 42     cnt %= len; // 因为可能发生资源加载很慢,但旋转轴依然要保持速度旋转的情况
 43     printf("[%-100s][%.1f%%][%c]\r",buffer,rate*100,table[cnt]);
 44     cnt++;
 45     fflush(stdout);
 46 }

(2) process.h

cpp 复制代码
 $ vim process.h
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 void process_v1();
  4 void FlushProcess(double total,double current);

(3) main.c

cpp 复制代码
 $ vim main.c
  1 #include"process.h"
  2 #include<string.h>
  3 #include<unistd.h>
  4 
  5 double total = 1024.0;
  6 double speed = 1.0;
  7 
  8 void DownLoad()
  9 {
 10     double current = 0.0;
 11     while(current <= total)
 12     {
 13         FlushProcess(total,current);
 14         usleep(3000); // 充当下载数据
 15         current += speed;
 16     }
 17     printf("\nDownLoad %.2lfMB Done\n",current);
 18 }
 19 
 20 int main()
 21 {
 22     //process_v1();
 23     DownLoad();
 24     return 0;
 25 }

5、回调函数应用

回调函数是一种特殊的函数,它被作为参数传递给另一个函数,并在后者内部被调用。在这个代码中,DownLoadUpLoad 函数都接受一个 callback_t 类型的参数,这个参数就是回调函数。它接受一个 callback_t 类型的参数 cb,这个参数指向一个回调函数。在下载过程中,DownLoad 函数和UpLoad 函数会周期性地调用这个回调函数来更新下载进度。

main 函数中,DownLoadUpLoad 函数被调用,并且都传递了同一个回调函数 FlushProcess

cpp 复制代码
  1 #include"process.h"
  2 #include<string.h>
  3 #include<unistd.h>
  4 
  5 typedef void (*callback_t)(double total, double current); // 函数指针
  6 
  7 double total = 1024.0;
  8 double speed = 1.0;
  9 
 10 void DownLoad(callback_t cb) 
 11 {
 12     double current = 0.0;
 13     while(current <= total)
 14     {
 15         cb(total,current); // 回调函数
 16         usleep(3000); // 充当下载数据
 17         current += speed;
 18     }
 19     printf("\nDownLoad %.2lfMB Done\n",current);
 20 }
 21 
 22 void UpLoad(callback_t cb)
 23 {
 24     double current = 0.0;
 25     while(current <= total)
 26     {
 27         cb(total,current); // 回调函数
 28         usleep(3000); // 充当下载数据
 29         current += speed;
 30    }
 31     printf("\nUpLoad %.2lfMB Done\n",current);
 32 }
 33 
 34 
 35 int main()
 36 {
 37     //process_v1();
 38     DownLoad(FlushProcess);
 39     UpLoad(FlushProcess);
 40     return 0;
 41 }
相关推荐
蜜獾云22 分钟前
vscode通过ssh连接服务器实现免密登录
服务器·vscode·ssh
毒丐34 分钟前
GCC使用说明
linux·c语言·c++
babytiger35 分钟前
linux系统(ubuntu,uos等)连接鸿蒙next(mate60)设备
linux·运维·ubuntu
檀越剑指大厂37 分钟前
【Linux系列】Vim 编辑器中的高效文本编辑技巧:删除操作
linux·编辑器·vim
知本知至1 小时前
ubuntu18升级至ubuntu20
linux·ubuntu·gpu
喵叔哟1 小时前
23. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--预算
linux·微服务·.net
diaya1 小时前
RabbitMQ ubuntu 在线安装
运维·服务器·ubuntu
小参宿1 小时前
高效绘图不再受限!本地搭建Excalidraw与随时随地高效绘制流程图教程
运维·服务器·windows·docker·centos·流程图
猫咪-95272 小时前
mv指令详解
linux·指令
叶 落2 小时前
Ubuntu 下载安装 kibana8.7.1
服务器·ubuntu·kibana·es