1. 行缓冲区
- 完整可参考这篇文章——缓冲区
1.1 概念
- 在行缓冲区中,当在输入和输出中遇到 换行符 时,执行真正的I/O(输入/输出)操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是 标准输入(stdin) 和 标准输出(stdout)
1.2 结合缓冲区来深度理解getchar
- 当程序调用getchar()函数时,程序就等着用户按键, 用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止(回车字符也放在缓冲区中) 。
- 当用户键入回车之后,getchar()函数 才开始从键盘缓冲区中每次读入一个字符 。也就是说, 后续的getchar()函数调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才重新等待用户按键 。
- 打个比方,键盘缓冲区就像是一条水管连着你的程序,程序调用getchar()函数用户输入字符就相当于往水管里注水,这个水注多少取决于你输入多少,当你按回车停止注水时,getchar()函数才会开始从键盘缓冲区,
- 也就是我们的水管里取水,那每次只会读一个字符也就是每次取一定量的水,当你在这之后继续调用getchar()函数时,会接着在水管里取上次没用完的水,因为你的水管没清空(缓冲区的刷新),
- 那这个阶段就不用你再输入了,因为一调用getchar()函数就有水可取嘛,直到水管里没水了,你还调用getchar()函数,那这个时候你就得注水了也就是程序会等你按键。
- 通俗一点说,当程序调用getchar()函数时,程序就等着用户按键,并等用户按下回车键返回。期间按下的字符存放在缓冲区,第一个字符作为函数返回值。
- 继续调用getchar()函数,将不再等用户按键,而是返回您刚才输入的第2个字符;继续调用,返回第3个字符,直到缓冲区中的字符读完后,才等待用户按键。
- getchar()函数的执行就是采用了行缓冲。第一次调用getchar()函数,会让程序使用者(用户)输入一行字符并直至按下回车键 函数才返回。此时用户输入的字符和回车符都存放在行缓冲区。再次调用getchar()函数,会逐步输出行缓冲区的内容
- 下面我们用代码来验证一下
#include <stdio.h>
int main()
{
char c = getchar();
printf("%c\n", c);
system("PAUSE");
c = getchar();
printf("%c\n", c);
system("PAUSE");
return 0;
}
- 我们输入1 2 3来试试
- 我们会发现第二次输出的并不是2,而是空格,并且第二次我们并没有再输入,直接就读取了,这就印证了缓冲区的存在了
1.3 结合缓冲区来深度理解scanf
1.3.1 问题1
- 不知大家在学习C语言的过程中有没有过这样的问题:
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d\n", &a);
printf("%d", a);
return 0;
}
- 当我们输入10并按下回车的时候,发现程序没有结束,而且似乎还在等待我们输入数据
- 这到底是为什么呢?
- 我们先来回顾一下scanf输入的特点是什么?
- scanf() 处理用户输入的原理是,用户的输入先放入缓存,等到按下回车键后,按照占位符对缓存进行解读
- 解读用户输入时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为止,并且不会带走不符合条件的这个字符
- 并且scanf() 处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符等(前提是scanf的参数中没有出现这些字符)
- 从第三条我们会发现现在正好是scanf的参数中出现了换行符,并且我们还会发现如果没有出现这些操作符的时候,都是直接过滤掉的,
- 那是不是可以理解他们在scanf眼中的地位是一模一样的,所以这里如果把scanf参数中的 \n 换成空格也会出现一样的问题
- 接下来解释一下具体的原因:
- 当我们再输入20的时候:
- 确实如此,
- 此时就会有同学会有疑问,那这样的话,20是不是还留在缓冲区了?
- 我们加个getchar 来验证一下
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d\n", &a);
printf("%d\n", a);
printf("%c\n", getchar());
return 0;
}
- 运行结果为:
- 我们发现结果竟然不是20!,是不是我们翻车了呢?
- 当然不是,因为20在getchar看来是两个字符,所以只读取了2
- 我们可以再来验证一下:
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d\n", &a);
printf("%d\n", a);
printf("%c\n", getchar());
printf("%c\n", getchar());
return 0;
}
- 果然是这样,
- 如果我们再加上一条getchar呢?会发生什么神奇的事呢?
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d\n", &a);
printf("%d\n", a);
printf("%c\n", getchar());
printf("%c\n", getchar());
printf("%c\n", getchar());
return 0;
}
- 我们发现空白行多了一行,其实这就是我们按下回车后放在缓冲区的 \n
1.3.1 问题2
- 接下来我们再利用上面所学的来解释另一个问题
#include <stdio.h>
int main()
{
char arr[10] = "0";
scanf("%s", arr);
printf("%s\n", arr);
char a = '0';
scanf("%c", &a);
printf("%c\n", a);
return 0;
}
- 我们想先给数组输入一个字符串之后再输入一个字符
- 但是当我们输入字符串之后,程序直接就结束了,按理来说应该让我们再输入一个字符才对啊
- 学了行缓冲区后我们就可以解释其原因了
- scanf读取完后并不会把回车输入的换行符给带走,而是留在了缓冲区所以当scanf读取的时候缓冲区还有字符,所以scanf不等你输入直接就读取\n了,所以我们发现程序运行后会有三行空行
- 那应该怎么避免呢?
- 第一种方法:我们可以在第一个scanf读取完后利用getchar把缓冲区清空
#include <stdio.h>
int main()
{
char arr[10] = "0";
scanf("%s", arr);
while (getchar() != '\n')
{
;
}//利用getchar循环清空缓冲区
printf("%s\n", arr);
char a = '0';
scanf("%c", &a);
printf("%c\n", a);
return 0;
}
- 第二种方法:既然scanf有缺陷,那我直接把他换掉不就完了
- 那换谁了?换gets!输入字符串的时候,gets可以完全代替scanf
- gets在读取完字符串后会把后面的\n直接拉出来给扔了,并且遇到空格的时候仍然会读取,可以说在读取字符串方面完胜scanf
#include <stdio.h>
int main()
{
char arr[10] = "0";
gets(arr);
printf("%s\n", arr);
char a = '0';
scanf("%c", &a);
printf("%c\n", a);
return 0;
}
- 运行结果:
最后,
恭喜你又遥遥领先了别人!