# 缓冲区 缓冲区位于用户程序和硬件设备之间,用来缓存数据,目的是让快速的 CPU 不必等待慢速的输入输出设备,同时减少操作硬件的次数。对于 IO 密集型的网络应用程序,比如网站、数据库、DNS、CDN 等,缓冲区的设计至关重要,它能十倍甚至一百倍得提高程序性能。 ## 缓冲区的刷新 所谓刷新缓冲区,就是将缓冲区中的内容送达到目的地。缓冲区的刷新遵循以下的规则: - 不管是行缓冲还是全缓冲,缓冲区满时会自动刷新; - 行缓冲遇到换行符`\n`时会刷新; - 关闭文件时会刷新缓冲区; - 程序关闭时一般也会刷新缓冲区,这个是由标准库来保障的; - 使用特定的函数也可以手动刷新缓冲区 ## 清空输出缓冲区 清空输出缓冲区很简单,使用下面的语句即可: ```c fflush(stdout); ``` `fflush()` 函数是一个专门用来清空缓冲区的函数,stdout是standard output的缩写,表示标准输出设备,就是显示器。整个语句清空标准输出缓冲区。 需要注意的是 Windows 平台下的 `printf()` `puts()` `putchar()` 输出函数都是不带缓冲区的,所以不用清空。而Linux和Mac OS平台才带有缓冲区。 ```c #include #include int main(int argc, char *argv[]){ printf("wangyuedong."); fflush(stdout); sleep(5); printf("hahahaha."); return 0; } ``` 程序运行后,第一个 pirntf() 立即输出,等待 5 秒以后,第二个 printf() 才输出,这就符合我们的惯性思维了。如果不加`fflush(stdout)`语句,程序运行后,第一个 printf() 并不会立即输出,而是等待 5 秒以后和第二个 scanf() 一起输出。 ## 清空输入缓冲区 首先,很遗憾地说,没有一种既简洁明了又适用于所有平台的清空输入缓冲区的方案。只有一种很蹩脚的方案能适用于所有平台,那就是将输入缓冲区中的数据都读取出来,但是却不使用。 我们先说两种通用的方案,虽然它很蹩脚,但是却行之有效。 方式一、使用getchar()清空缓冲区 getchar() 是带有缓冲区的,每次从缓冲区中读取一个字符,包括空格、制表符、换行符等空白符,只要我们让 getchar() 不停地读取,直到读完缓冲区中的所有字符,就能达到清空缓冲区的效果。 ```c int c; while((c = getchar()) != '\n' && c != EOF); ``` 该代码不停地使用 getchar() 获取缓冲区中的字符,直到遇见换行符`\n`或者到达文件结尾才停止。由于大家所学知识不足,这段代码暂时无法理解,我也就不细说了,在实际开发中,大家按照下面的形式使用即可: ```c #include int main(){ int a = 1, b = 2; char c; scanf("a=%d", &a); while((c = getchar()) != '\n' && c != EOF); scanf("b=%d", &b); printf("a=%d, b=%d\n", a, b); return 0; } ``` 按下第一个回车键后,只有第一个 scanf() 读取成功了,第二个 scanf() 并没有开始读取,等我们再次输入并按下回车键后,第二个 scanf() 才开始读取,这就符合我们的操作习惯了。 方式二、使用scanf()清空缓冲区 scanf() 还有一种高级用法,就是使用类似于正则表达式的通配符,这样它就可以读取所有的字符了,包括空格、换行符、制表符等空白符,不会再忽略它们了。并且,scanf() 还允许把读取到的数据直接丢弃,不用赋值给变量。 ```c scanf("%*[^\n]"); scanf("%*c"); ``` 第一个 scanf() 将逐个读取缓冲区中`\n`之前的其它字符,% 后面的 * 表示将读取的这些字符丢弃,遇到`\n`字符时便停止读取。此时,缓冲区中尚有一个`\n`遗留,第二个 scanf() 再将这个`\n`读取并丢弃,这里的星号和第一个 scanf() 的星号作用相同。由于所有从键盘的输入都是以回车结束的,而回车会产生一个`\n`字符,所以将`\n`连同它之前的字符全部读取并丢弃之后,也就相当于清除了输入缓冲区。 ```c #include int main(){ int a = 1, b = 2; scanf("a=%d", &a); scanf("%*[^\n]"); scanf("%*c"); scanf("b=%d", &b); printf("a=%d, b=%d\n", a, b); return 0; } ``` ## 两种不通用、不建议的方案 以上两种清空输入缓冲区的方案是通用的,在任何平台、任何编译器、任何情景下都奏效。除此以外,有些教材和老师可能还讲解过其它的方案,常见的有两种,分别是`fflush(stdin)`和`rewind(stdin)`。 #### 1) fflush(stdin) fflush(stdin) 常用于 Windows 平台,在 VC 6.0、VS2010 等较老的编译器下确实能够清空缓冲区。 C语言标准规定,当 fflush() 用于 stdout 时,必须要有清空输出缓冲区的作用;但是C语言标准并没有规定 fflush() 用于 stdin 时的作用,编译器的实现者可以自由决定,所以它的行为是未定义的。 较老的微软编译器进行了扩展,赋予了 fflush(stdin) 清空输入缓冲区的功能,例如 VC 6.0、VS2010 等;但是,较新的微软编译器又取消了这种扩展,不再支持 fflush(stdin),例如 VS2015、VS2017 等,在这些版本的编译器下,fflush() 是无效的。 较老的 GCC 是不支持 fflush(stdin) 的,但是最新的 GCC 又开始支持 fflush(stdin) 了。 LLVM/Clang 编译器始终不支持 fflush(stdin)。 总之,fflush(stdin) 这种不标准的写法只适用于一部分编译器,通用性非常差,所以不建议使用。如果你由于个人习惯坚持使用,请测试你的编译器是否支持。 #### 2) rewind(stdin) rewind() 函数并没有清空缓冲区的功能,但是 rewind(stdin) 偏偏在某些编译器下会导致清空缓冲区的假象,例如 VS2015、LLVM/Clang。在 GCC 下,rewind(stdin) 是没有任何效果的。