C语言清空(刷新)缓冲区,从根本上消除那些奇怪的行为
缓冲区的优点很明显,
- 它加快了程序的运行速度,
- 减少了硬件的读写次数,
- 让整个计算机变得流畅起来;
但是,缓冲区也带来了一些负面影响,经过前面几节的学习相信读者也见识到了。
那么,该如何消除这些负面影响呢?思路其实也很简单,在输入输出之前清空(刷新)缓冲区即可:
- 对于输出操作,清空缓冲区会使得缓冲区中的所有数据立即显示到屏幕上;很明显,这些数据没有地方存放了,只能输出了。
- 对于输入操作,清空缓冲区就是丢弃残留字符,让程序直接等待用户输入,避免引发奇怪的行为。
本节的代码用到了一些暂时没有学到的知识,估计很多初学者会不理解,没关系,那就先记住吧,记不住就复制吧,总之,按照葫芦画瓢就完了。
清空输出缓冲区
清空输出缓冲区很简单,使用下面的语句即可:1 | fflush (stdout); |
fflush() 是一个专门用来清空缓冲区的函数,stdout 是 standard output 的缩写,表示标准输出设备,也即显示器。整个语句的意思是,清空标准输出缓冲区,或者说清空显示器的缓冲区。
Windows 平台下的 printf()、puts()、putchar() 等输出函数都是不带缓冲区的,所以不用清空,下面的代码演示了如何在 Linux 和 Mac OS 平台下清空缓冲区:
1 2 3 4 5 6 7 8 9 10 | #include<stdio.h> #include<unistd.h> int main() { printf ( "百度" ); fflush (stdout); //本次输出结束后立即清空缓冲区 sleep(5); printf ( "http://www.baidu.com\n" ); return 0; } |
程序运行后,第一个 pirntf() 立即输出,等待 5 秒以后,第二个 printf() 才输出,这就符合我们的惯性思维了。如果不加fflush(stdout)语句,程序运行后,第一个 printf() 并不会立即输出,而是等待 5 秒以后和第二个 scanf() 一起输出(已在《C语言数据输出大汇总以及轻量进阶》中进行了演示),这有点不符合我们的思维习惯。
清空输入缓冲区
首先,很遗憾地说,没有一种既简洁明了又适用于所有平台的清空输入缓冲区的方案。只有一种很蹩脚的方案能适用于所有平台,那就是将输入缓冲区中的数据都读取出来,但是却不使用。
大家不要以为我很轻松地就能说出这句话,我FQ查阅了很多英文资料,又测试了很多平台和编译器才敢说的。
我们先说两种通用的方案,虽然它很蹩脚,但是却行之有效。
1) 使用 getchar() 清空缓冲区
getchar() 是带有缓冲区的,每次从缓冲区中读取一个字符,包括空格、制表符、换行符等空白符,只要我们让 getchar() 不停地读取,直到读完缓冲区中的所有字符,就能达到清空缓冲区的效果。请看下面的代码:
1 2 | int c; while ((c = getchar ()) != '\n' && c != EOF); |
该代码不停地使用 getchar() 获取缓冲区中的字符,直到遇见换行符\n或者到达文件结尾才停止。由于大家所学知识不足,这段代码暂时无法理解,我也就不细说了,在实际开发中,大家按照下面的形式使用即可:
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h> 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; } |
输入示例:
1 2 3 | a=100↙ b=200↙ a=100, b=200 |
按下第一个回车键后,只有第一个 scanf() 读取成功了,第二个 scanf() 并没有开始读取,等我们再次输入并按下回车键后,第二个 scanf() 才开始读取,这就符合我们的操作习惯了。如果没有清空缓冲区的语句,按下第一个回车键后,两个 scanf() 都读取了,只是第二个 scanf() 读取失败了,让人觉得很怪异,这点已在《使用scanf从键盘输入数据》中进行了演示。
改变输入方式,再次尝试一下:
1 2 3 | a=100b=200↙ b=300↙ a=100, b=300 |
你看,第一次输入的多余内容并没有起作用,就是因为它们在第二个 scanf() 之前被清空了。
这种方案的关键之处在于,getchar() 是带有缓冲区的,并且一切字符通吃,或者说一切字符都会读取,不会忽略。不过这种方案有个缺点,就是要额外定义一个变量 c,对于有强迫症的读者来说可能有点难受。
2) 使用 scanf() 清空缓冲区
scanf() 还有一种高级用法,
- 就是使用类似于正则表达式的通配符,这样它就可以读取所有的字符了,包括空格、换行符、制表符等空白符,不会再忽略它们了。
- 并且,scanf() 还允许把读取到的数据直接丢弃,不用赋值给变量。
请看下面的语句:
1 | scanf ( "%*[^\n]" ); scanf ( "%*c" ); |
- 第一个 scanf() 将逐个读取缓冲区中\n之前的其它字符,% 后面的 * 表示将读取的这些字符丢弃,遇到\n字符时便停止读取。
- 此时,缓冲区中尚有一个\n遗留,第二个scanf()再将这个\n读取并丢弃,这里的星号和第一个 scanf() 的星号作用相同。
由于所有从键盘的输入都是以回车结束的,而回车会产生一个\n字符,所以将\n连同它之前的字符全部读取并丢弃之后,也就相当于清除了输入缓冲区。
相信很多读者都不明白这种写法,没关系,下节我们在讲解 scanf() 的高级用法时还会再解释。
我们来演示这种方案的效果:
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h> 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; } |
输入示例 ①:
1 2 3 | a=100↙ b=200↙ a=100, b=200 |
输入示例 ②:
1 2 3 | a=100b=200↙ b=300↙ a=100, b=300 |
相比使用 getchar(),这种方案不用额外定义一个变量,看起来更加整洁。
两种不通用、不建议的方案
以上两种清空输入缓冲区的方案是通用的,在任何平台、任何编译器、任何情景下都奏效。除此以外,有些教材和老师可能还讲解过其它的方案,常见的有两种,分别是fflush(stdin)和rewind(stdin)。
1) fflush(stdin)
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) 这种不标准的写法只适用于一部分编译器,通用性非常差,所以不建议使用。如果你由于个人习惯坚持使用,请测试你的编译器是否支持。
(个人:实验之,cmingw64支持fflush(stdin),vstudio2022里面fflush(stdin)是无效的,没有用)
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h> int main() { int a = 1, b = 2; scanf_s( "a=%d" , &a); fflush (stdin); scanf_s( "b=%d" , &b); printf ( "a=%d, b=%d\n" , a, b); return 0; } |
2) rewind(stdin)
rewind() 函数并没有清空缓冲区的功能,但是 rewind(stdin) 偏偏在某些编译器下会导致清空缓冲区的假象,。
(个人:实验,cmingw和vstudio2022都有效)
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h> int main() { int a = 1, b = 2; scanf ( "a=%d" , &a); rewind (stdin); scanf ( "b=%d" , &b); printf ( "a=%d, b=%d\n" , a, b); return 0; } |
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h> int main() { int a = 1, b = 2; scanf_s( "a=%d" , &a); rewind (stdin); scanf_s( "b=%d" , &b); printf ( "a=%d, b=%d\n" , a, b); return 0; } |
总结
最靠谱、最通用、最有效的清空输入缓冲区的方案就是使用 getchar() 或者 scanf() 将缓冲区中的数据逐个读取出来,其它方案都有或多或少的问题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
2021-05-12 torch.nn.ZeroPad2d
2021-05-12 文本分类算法之DPCNN模型