【Linux】缓冲区 & 进度条小程序

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

目录

一、\r && \n

二、缓冲区的概念

 三、小程序编写

1、倒数小程序

2、进度条小程序


一、\r && \n

C语言中有很多字符但是宏观上可以分成两类可显字符、控制字符。

可显字符包括我们见到的 1、2、3....a、b、c....等等。控制字符则包括 '\n'、'\t'、'\r'、'\b'等等。

换行操作就是使用控制字符来完成的换行的过程包括两个部分1、换到下一行2、光标移动到下一行的开头。分别对应到控制字符'\n'换行 '\r'回车

至于我们在写C语言代码时只需要输入字符 '\n' 就直接自动换行到下一行的开头是因为在语言范畴中默认把 '\n' 解释成了回车加换行。

因为回车与换行是两个动作所以我们可以观察到键盘上 enter 键上的图标是一个“竖折”。

二、缓冲区的概念

我们先写一个程序

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world\n");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

我们在打印hello world之后添加了一个休眠函数执行程序现象如下

 可以看到屏幕上打印出字符后延时了一段时间程序才结束下一个命令行出现。


现在把 printf 函数中的 '\n' 符号删除掉再次编译

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

 

 可以看到去除掉 '\n' 符号后不仅命令行不会换行显示了而且 hello world 是经过了一段延时之后与命令行一起显示的。

 首先我们知道程序在执行时一定时按照从上往下的顺序执行的也就是说一定是先打印的 hello world 再执行休眠函数。只不过 hello world 在休眠期间没有被刷新出来而已。此时 hello world 被保存在了 缓冲区

 那么为什么我们加上 '\n' 符号数据就可以直接显示出来呢原因很简单不管我们带不带上 '\n' 符号数据都会以 行缓冲 的方式被保存在缓冲区里。缓冲区有自己的刷新策略如果遇到了换行符 '\n' 就把换行符之前的所有数据都刷新出来。否则就会默认在程序退出时自动刷新缓冲区里的数据。


接下来我们再来修改一下代码

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world\r");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

 在字符串 hello world 后面加上回车控制符 '\r' 编译后运行现象如下

延时一段时间后hello world 竟然直接不显示了直接出现了下一行命令行。

这是因为 hello world 被打印出来之后由于回车符 '\r' 的作用光标直接移动到了该行的最开头并在此光标位置打印下一个命令行直接把 hello world 覆盖住了。

为了更好的观察这一过程我们在 printf函数 与 sleep 函数之间添加一个主动刷新函数 fflush 即直接刷新出缓冲区里的数据

                                                                                                                            
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world\r");
  7   fflush(stdout);                                                                                                                                    
  8   sleep(1);                                                                                                               
  9   return 0;                                                                                                               
 10 }  

 编译运行现象如下

hello world 先被刷新出来休眠一段时间程序结束下一个命令行出现并覆盖 hello world。 


 现在我们再把 '\r' 去掉就会有一个更深的理解

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world");
  7   fflush(stdout);                                                                                                                                    
  8   sleep(1);                                                                                                               
  9   return 0;                                                                                                               
 10 }  

 编译运行 

 三、小程序编写

在了解了以上知识之后我们就可以进行一些小程序的编写。

1、倒数小程序

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   int i = 9;
  7   for(i = 9; i > 0; i--) //从9开始倒数
  8   {
  9     printf("%d\r", i); // 使用\r覆盖上一个数 
 10     fflush(stdout);  //直接输出缓冲区里的数据
 11     sleep(1);   //休眠延时
 12   }                                                                                                                                                  
 13   printf("\n"); //保留最后一个数字不被下一个命令行覆盖
 14   return 0;
 15 }

执行结果如下

这里会有一个问题就是如果我们把 i 的初值设为 10 执行出来的结果与上面的结果有所差别会像这样

这时因为凡是向显示器打印的所有内容都是字符。就像我们写 printf("%d", 10); 看起来好像是打印了一个十进制数字 10实际上这个数字根本不是一个 4 个字节的整数而是两个字符 1 和 0 。因此我们每次打印都只覆盖了第一个字符而第二个字符没有发生变化。

补充内容

printf工作原理是先获取指定数据再把这个数据全部转换成字符串然后把字符串遍历一遍并使用 putc 来把这些字符串显示出来。

 那么我们怎么解决呢也很简单我们只需要把 "%d" 改成 "%2d" 就可以了保留两位字符。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   int i = 10;
  7   for(i = 10; i > 0; i--) //从9开始倒数
  8   {
  9     printf("%2d\r", i); // 使用\r覆盖上一个数 
 10     fflush(stdout);  //直接输出缓冲区里的数据
 11     sleep(1);   //休眠延时
 12   }                                                                                                                                                  
 13   printf("\n"); //保留最后一个数字不被下一个命令行覆盖
 14   return 0;
 15 }

明白了这个原理之后任何数的倒数我们都能够实现了。 

2、进度条小程序

先讲一下思想

  1. 我们先预留出 100 个字符的空间用 [ ] 括起然后在这个空间里不断的填充 '#' 号每隔一段时间填充的 '#' 号就多一个。在 '#' 不断增多的过程中右边的 ' ] ' 的位置保持不变。
  2. 在进度条的后面使用百分比数字来显示当前进度条的进度。
  3. 在进度条最后打印一个不断旋转的字符来表示当前程序正在运行。

程序采用多文件实现

  • proc.h
  • proc.c
  • main.c 

进度条程序编写

我们创建一个字符数组通过打印不同的字符来模拟旋转字符的实现

const char* lable = "|/-\\"

因为 '\' 是特殊字符所以我们使用 '\\' 转义一下。

因为要打印 100 个字符 '#' 加上一个字符串结束字符 '\0' 所以我们给字符串数组 bar 初始化空间为 101 初始化字符全为 '\0'

#define SIZE 101 

char bar[SIZE];
memset(bar, '\0', sizeof(bar));//先把字符串里的所有字符都设置为'\0'

打印进度条主体时我们使用循环语句每次打印都覆盖上一次打印的内容

int i = 0;
while(i <= 100)//因为百分之0 到 百分之100 是101个数所以循环101次
{
  printf("[%-100s][%d%%][%c]\r", bar, i, lable[i%4]);//先预留100个字符的空间向左对齐,每一次 
                                                      //打印都覆盖前一次打印
  fflush(stdout); //直接刷新出缓冲区里的内容
  bar[i++] = STYLE; //给字符串赋值
  usleep(50000);  //每次休眠0.5ms
}

printf 函数里打印字符串使用 %-100s 是为了提前预留出 100 个字符的位置并向左对齐以便进度条是从左向右打印的。

因为 '%' 是特殊字符所以为了打印出 '%' 需要写成 '%%'

最终程序编译完成后执行结果如下

完整代码

proc.h

  1 #pragma once                                                                                                                                         
  2 #include <stdio.h>     
  3 extern void process(); 

proc.c

  1 #include "proc.h"
  2 #include "string.h"
  3 #include "unistd.h"
  4 
  5 #define SIZE 101                                                                                                                                     
  6 #define STYLE '#'
  7 
  8 void process()
  9 {
 10   const char* lable = "|/-\\"; //顺时针旋转字符
 11   char bar[SIZE];
 12   memset(bar, '\0', sizeof(bar));//先把字符串里的所有字符都设置为'\0'
 13   int i = 0;
 14   while(i <= 100)//因为百分之0 到 百分之100 是101个数所以循环101次
 15   {
 16     printf("[%-100s][%d%%][%c]\r", bar, i, lable[i%4]);//先预留100个字符的空间向左对齐, 
                                                           //每一次打印都覆盖前一次打印
 17     fflush(stdout); //直接刷新出缓冲区里的内容
 18     bar[i++] = STYLE; //给字符串赋值
 19     usleep(50000);  //每次休眠0.5ms
 20   }
 21   printf("\n"); //最后换行防止下一个命令行覆盖进度条
 22 }

 main.c

  1 #include "proc.h"                                                                                                                                    
  2 int main()
  3 {         
  4   process();
  5 }  

Makefile 编写

  1 process:main.c proc.c
  2     gcc main.c proc.c -o process 
  3 .PHONY:clean
  4 clean:
  5     rm -f process  

有关缓冲区的内容就讲到这里这个进度条小程序大家在自己编写的时候可以增加点新的东西比如进度条颜色、背景颜色等等。希望同学们多多支持如果有不对的地方还请大佬指正谢谢

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