关于scanf、getchar、getch、getche缓冲区分析——C语言

缓冲区

根据数据刷新的时机可以将缓冲区的类型分为:全缓冲行缓冲无缓冲

(注意:Windows下的输出设备没有缓冲区,意思是printf是无缓冲的,但是在Linux下printf就是行缓冲的,至于为什么Windows下printf是无缓冲的,后文会提到)

全缓冲:当缓冲区被填满以后才进行真正的输入输出操作

行缓冲:当在输入或者输出的过程中遇到换行符时,才执行真正的输入输出操作

无缓冲:没有缓冲区,立即进行输入输出

(图片来源:https://www.cnblogs.com/mydomain/p/9817320.html) 

(缓冲区其实是一块内存空间,它用在硬件设备和用户程序之间,用来缓存数据,目的是让快速的 CPU 不必等待慢速的输入输出设备,同时减少操作硬件的次数)

为什么Windows下C语言的printf是无缓冲的?

C语言作为一个面向过程的语言,算不上高级语言也不能说是低级语言,C语言完成的工作大多都是偏底层开发工作(再底层一点可能就是汇编了),C语言常常用在嵌入式、单片机之类的小缓冲区的东西上,

嵌入式设备的缓冲区通常远小于PC,如果printf是行缓冲的,可能刚写进去一点东西,就可能被别的玩意儿盖掉了,所以嵌入式做开发带流的函数进行I/O的时候通常一次I/O马上就要fflush()

缓冲区的刷新遵循以下的规则

1、不管是行缓冲还是全缓冲,缓冲区满时会自动刷新

2、行缓冲遇到换行符 时会刷新

3、关闭文件时会刷新缓冲区

4、程序关闭时一般也会刷新缓冲区,这个是由标准库来保障的

5、使用特定的函数也可以手动刷新缓冲区

C语言标准规定,输入输出缓冲区要具有以下特征:

1、当且仅当输入输出不涉及交互设备时,它们才可以是全缓冲的

2、错误显示设备不能带有缓冲区

所谓交互设备,就是现代计算机上的显示器和键盘。C标准虽然规定它们不能是全缓冲的,但并没有规定它们到底是行缓冲还是无缓冲,这就导致不同的平台有不同的实现

输入设备

Windows、Linux、Mac OS 在实现时都给输入设备带上了行缓冲,所以 scanf()、getchar()、gets() 在每个平台下的表现都一致

Windows 下特有的 getche() 和 getch()函数,都不带缓冲区

输出设备

Windows 平台下,输出设备是不带缓冲区的

Linux 和 Mac OS 平台下,输出设备带有行缓冲区

scanf函数——行缓冲

这个可能是最常用到的输入函数了,scanf() 是从标准输入设备(键盘)读取数据,当遇到 scanf() 函数时,程序会先检查输入缓冲区中是否有数据,是带有行缓冲区的,如果没有,就等待用户输入,

用户从键盘输入的每个字符都会暂时保存到缓冲区,直到按下回车键,产生换行符(' '),输入结束

需要注意的是:当输入的数据与存储该数据的变量类型不符合,scanf() 还会尝试忽略一些空白符,例如空格、制表符、换行符

例如:

#include <stdio.h>
#include <Windows.h>

int main()
{    
    int a = 10, b = 20;

    scanf("%d%d", &a, &b);
    printf("%d %d", a, b);

    return 0;
}

如果输入1(空格)2,中间的空格会被自动忽略到,也就是上面所说的会尝试忽略掉一些空白符(像前面这样的数字(空格)数字输入格式,无论中间输入多少的空白符会被忽略)

scanf还有一个比较奇怪的特性:只有当控制字符串以格式控制符开头时,才会忽略换行符、空格、制表符等空白符,为了读者理解这句话的含义,下面上代码

情况一:不会忽略换行符

 1 #include <stdio.h>
 2 #include <Windows.h>
 3 
 4 int main()
 5 {    
 6     int a = 10, b = 20;
 7 
 8     scanf("%d", &a);
 9     scanf("b=%d", &b);
10     printf("%d %d", a, b);
11 
12     return 0;
13 }

当输入1回车时,回车符虽然是属于空白符,但是此时不能被忽略,不能被忽略的换行符(' ')和格式控制字符串%d又不匹配,所以只能读取失败了,b的值不变还是20

情况二:会忽略换行符

 1 #include <stdio.h>
 2 #include <Windows.h>
 3 
 4 int main()
 5 {    
 6     int a = 10, b = 20;
 7 
 8     scanf("%d", &a);
 9     scanf("%d", &b);
10     printf("%d %d", a, b);
11 
12     return 0;
13 }

此时控制字符串变成以格式控制字符串(%d)开头,此时输入1回车,第二个scanf("%d", &b)就会把回车符(' ')忽略掉,等待用户输入下一个数字(在输入1回车后无论输入多少个空格制表符回车都会被忽略掉)

下面来分析下scanf("%s")输入字符串时的特点

scanf使用格式控制字符串%s读入字符串时,遇到回车、空格、制表符结束输入,下面将举多个栗子来分析scanf输入字符串时的特点

例一:

 1 #include <stdio.h>
 2 #include <Windows.h>
 3 
 4 int main()
 5 {    
 6     char ss[2][10];
 7 
 8     scanf("%s%s", ss[0], ss[1]);
 9 
10     printf("%s%s
", ss[0], ss[1]);
11 
12     return 0;
13 }

输入为:Hello(空格)World

从结果可以看出,中间的空格被忽略掉了,现在来分析下过程:

前半部分的 Hello(空格) 碰到空格时就结束第一次输入,将Hello存储在ss[0]这个字符串中(注意:此时空格符仍然存在于缓冲区中,并没有被读走)继续读取第二个字符串,

此时scanf会先瞧一瞧缓冲区中有没有东西,发现里面有一个空格符,忽略掉,继续读取World,发现后面有个换行符,结束第二次输入,将World存储在ss[1]这个字符串中,换行符仍然留在缓冲区

(这里的特点和输入单个字符串一致,即在输入第一个非空白符前,输入多少个空格符(空格制表符回车)都会被忽略掉)

 

getchar()——行缓冲

getchar()可以看作scanf("%c");的一个简化版本,它和scanf("%c")的特性相同,这里就不再赘述了

gets()函数——行缓冲

gets(ss)遇到回车结束输入,读取的换行符被转换为NULL值,做为字符数组的最后一个字符,来结束字符串(意思就是将' '变成了''),gets()以回车作为字符串的终止符,同时将回车符从输入缓冲区读走,

但不作为字符串的一部分。而scanf()不读走回车符,回车符仍留在输入缓冲区中,除了输入结束标志的不同外,gets和scanf在读取字符串时还有一个很大的区别,gets在读入字符串时不会忽略掉空白符,

意思是如果有语句gets(ss[0]),如果只按一下回车,那么回车就被当作字符串读入ss[0]这个字符串中,而不像scanf那样在读入到第一个非空白符之前忽略掉输入的所有空白符

 

代码示例:

 1 #include <stdio.h>
 2 #include <Windows.h>
 3 
 4 int main()
 5 {    
 6     char ss[2][10];
 7 
 8     gets(ss[0]);
 9     gets(ss[1]);
10     
11     printf("%s%s", ss[0], ss[1]);
12 
13     return 0;
14 }

 

getch()函数——不带缓冲区(无缓冲)

函数功能:当有输入时,立即读取(不需要按回车),不显示在屏幕上(不带回显),需要注意的是这个函数并非标准函数,跨平台使用时需要考虑移植性(getch() 位于 conio.h 头文件中,而这个头文件是 Windows 特有的,

Linux 和 Mac OS 下没有包含该头文件。换句话说,getche() 并不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用)

getche()函数——不带缓冲区(无缓冲)

函数功能:当有输入时,立即读取(不需要按回车),并显示在屏幕上(带回显),和getch()一样位于conio.h中,是非标准函数,认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用

关于清空缓冲区的三种方法

在谈到清空缓冲区的方法时,可能很多读者都会想到fflush(stdin)来清空缓冲区,但是fflush(stdin)存在一个问题,就是C语言标准规定,当 fflush() 用于stdout时,必须要有清空输出缓冲区的作用

但是C语言标准并没有规定 fflush() 用于stdin时的作用,编译器的实现者可以自由决定,所以它的行为是未定义的

(fflush(stdin) 这种不标准的写法只适用于一部分编译器,通用性比较差)

方法一:使用getchar()来清空缓冲区

特点:getchar() 是带有缓冲区的,并且一切字符通吃,或者说一切字符都会读取,不会忽略

int c;
while((c = getchar()) != '
' && c != EOF);

方法二:使用scanf()来清空缓冲区

scanf("%*[^
]"); scanf("%*c");

上面的语句分成两部分来看,第一部分:scanf("%*[^ ]"),这个语句作用是读取缓冲区中回车符(' ')之前的所有字符,并丢弃,在遇到回车符(' ')时便停止读取(注意:此时缓冲区中还有一个回车符),

第二部分:scanf("%*c"),这个语句的作用是读取一个字符,并丢弃,这样就将缓冲区中仅剩的一个回车符也读走了,此时就已经清空了缓冲区

方法三:使用fflush(stdin)来清空缓冲区

fflush(stdin);

不太推荐使用这种方法(还是因为通用性的问题,建议使用前面两种方法,因为前两种方法在任何平台、任何编译器、任何情景下都有效

原文地址:https://www.cnblogs.com/lanhaicode/p/10575049.html