带你理解输入输出缓冲区(超详解)
1.缓冲区的意义
缓冲区(Buffer)是内存空间的一部分。也就是说,在内存中预留了一定的存储空间,用来暂时保存输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
举个例子,相信大家在洗衣服的时候往往不会有了一件旧衣服就马上拿去洗衣机洗了,而等多几件了之后一起洗,这还可以避免水电资源浪费。同理,计算机的缓存区也是运用了同样的思想。
简而言之,缓存区就是一块内存,用来做数据的临时存放点,在输入输出操作者起至关重要的作用
2.缓冲的分类
1.完全缓冲
就是等整个缓存区被填满,才会被刷新缓存。最典型的例子就是磁盘的文件,文件用标准IO打开,默认都是全缓存。当缓存区填满或进行flush操作时才进行磁盘操作。
比如内存中有一段存储区域,比如有64个字节大小,有一个程序会从这段存储区域中读取数据。系统把一个文件内容放入这个区域,只要64个字节都放满了,那么程序会立即来读取这64个字节的数据。只要64个字节没有放满,哪怕只放了63个字节,程序都不会来读取,这就是完全缓冲的意思。
2.无缓冲
不对输入输出操作进行缓存,对流的读写可以立即操作实际文件。标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来.
3.行缓冲
即当一行结束的时候便开始刷新缓存.想要刷新行缓存,只要使用换行符’\n’便成功,即回车键(enter),其中我们最常见的也就是输入输出缓冲(printf,scanf)就是行缓冲.(这也是我们今天的重点!!!)
补充:如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是 512个字节 的大小。缓冲区大小由 stdio.h 头文件中的宏 BUFSIZ 定义
接下来我们着重讲一下我们最常接触到的输入输出缓冲(行缓冲)
3.1输出缓冲区
在C语言中很多输出函数(如printf, putchar)便存在输出缓存. 在window系统下,使用printf()函数后,数据被写入到输出缓冲区,随后立即刷新缓冲区,所有我们在使用printf()函数时往往会很快就在控制台打印出来了(Linux操作系统略有不同,在此我们便不做演示)
3.2输入缓冲区
类似于输出函数,输入函数(scanf,getchar)也存在输入缓存,并且这些输入函数属于阻塞函数,当缓存区没有内容时,程序将阻塞在输入函数中,等待用户从键盘输入内容,并按下回车键(即换行’\n’)确认,之后,输入的字符将进入输入缓存区,输入函数便从缓存区获取字符,并且删除缓冲区中已经获得的字符,并解除阻塞状态继续执行代码
下面举例一个十分经典的C语言代码来展示输入缓存的存在
#include<stdio.h>
int main()
{
char str[10];
int i;
for(i=0;i<10;i++)
scanf("%c",&str[i]);
for(i=0;i<10;i++)
printf("%c",str[i]);
return 0;
}
上面这个程序,获取10个字符,然后打印出来,如果我们正常的输入10个字符,打印正常
但是如下程序,如果我们输入a,然后回车,再输入b,再回车… 结果只能输入5个字符便打印了出来程序结束,这其实就是所谓的scanf() 吃掉回车
分析:首先这个程序运行到第1次循环的scanf的时候,此时缓冲区为空,即发生阻塞现象,我们输入a,之后按回车,此时缓存区其实是两个字符,‘a’和’\n’,然后此时的scanf拿到了字符a,第一次循环结束(注意此时缓存区还剩一个’\n’). 开始第2次循环. 但是scanf发现缓冲区有’\n’,便不会发生阻塞,此时scanf读取这个’\n’. 然后又开始第三次循环,此时缓冲区是空的,便又发生阻塞,所以又等待键盘输入,我们输入b,再次按下回车,此时缓存区又是两个字符,‘b’和’\n’,接下来重复上面过程.综上,相当于这个循环十次的for循环在第偶数次得到的都是’\n’,这也是为什么后面打印的时候这abcde是竖着打印出来的
3.不带缓冲的输入函数
前面介绍了带缓存的输入函数,只有当按下了回车键之后,输入的字符串才能进入缓存区,等待程序读取。接下来介绍一个不带缓存的输入函数( _getch ),只要按下键盘,程序立即获得输入的字符串。_getch直接从键盘获取键值,不等待用户按回车,只要用户按一个键,_getch就立刻结束输入了,换言之,不需要将输入的字符保存在缓存区,也就是说,输入一个字符,它马上读取
#include<stdio.h>
#include<conio.h>
int main()
{
while (1)
{
char c;
c = _getch(); //输入后用putchar打印
putchar(c);
if ('q' == c)
break;
}
return 0;
}
输入“abcq”,程序立即显示‘abcq’并退出,如下
这是因为_getch相当于无缓存的getchar(或者单个读取的sancf函数),程序运行到_getch函数将进入阻塞状态,并等待键盘直接输入一个字符,按下一个键后(不需要回车送入输入缓存区),getch函数就立刻能收到对应的字符(但是不会显示在控制台上),随后通过putchar打印在控制台。简而言之,这是一个不带回显的函数,即控制台abcq完全是putchar的功能,_getchar是不会显示内容的(不带回显),所以这个函数可以应用在密码中
接下来对比如果换成scanf控制台结果.
#include<stdio.h>
#include<conio.h>
int main()
{
while (1)
{
char c;
scanf("%c",&c); //输入后用putchar打印
putchar(c);
if ('q' == c)
break;
}
return 0;
}
打印出的结果为:
此时有两排,因为scanf是带回显的,第一排是键盘输入,第二排才是打印的。
相当于上面_getch打印出来的abcq是没有第一排的,只有第二排。