目录
一、\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、进度条小程序
先讲一下思想:
- 我们先预留出 100 个字符的空间,用 [ ] 括起,然后在这个空间里不断的填充 '#' 号,每隔一段时间,填充的 '#' 号就多一个。在 '#' 不断增多的过程中,右边的 ' ] ' 的位置保持不变。
- 在进度条的后面,使用百分比数字来显示当前进度条的进度。
- 在进度条最后打印一个不断旋转的字符,来表示当前程序正在运行。
程序采用多文件实现:
- 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
有关缓冲区的内容就讲到这里,这个进度条小程序大家在自己编写的时候可以增加点新的东西,比如进度条颜色、背景颜色等等。希望同学们多多支持,如果有不对的地方还请大佬指正,谢谢!