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

目录

一、\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 
  2 #include 
  3 
  4 int main()
  5 {
  6   printf("hello world\n");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

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

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


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

  1 #include 
  2 #include 
  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 
  2 #include 
  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 
  2 #include 
  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 
  2 #include 
  3 
  4 int main()
  5 {
  6   printf("hello world");
  7   fflush(stdout);                                                                                                                                    
  8   sleep(1);                                                                                                               
  9   return 0;                                                                                                               
 10 }  

 编译运行: 

 三、小程序编写

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

1、倒数小程序

  1 #include 
  2 #include 
  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 
  2 #include 
  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      
  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  

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

你可能感兴趣的:(Linux,c语言,开发语言,linux)