2024 年 09 月 29 日 23:03:21 周日
问题 1
下面这段程序在 Clion 终端中运行时,会先要求用户输入 (scanf),然后再打印 (printf) 文本,执行顺序反了:
#include <stdio.h>
int main(void)
{
float money;
printf("输入金额: ");
scanf("%f", &money);
return 0;
}
原因分析
unix 上标准输入输出都是带有缓存的,一般是行缓存。
对于标准输出,需要输出的数据并不是直接输出到终端上,而是首先缓存到某个地方,当遇到行刷新标志或者该缓存已满的情况下,才会把缓存的数据显示到终端设备上。
ANSI C 中定义换行符
\n
可以认为是行刷新标志。所以,printf 函数没有带\n
是不会自动刷新输出流的,直至缓存被填满。
在问题 1 的情况中,printf 函数的输出结果并不会立即输出到屏幕上,而是会先存储在缓冲区中。当程序执行到 scanf 函数时,会等待用户的输入,直到用户输入完成并按下回车键时,scanf 函数才会返回,此时缓冲区的内容才会被刷新并输出到屏幕上。
缓冲区刷新的条件:
- 进程结束。
- 遇到
\n
。 - 缓冲区满(
printf()
函数的缓冲区大小为 1024 个字节,当超出缓冲区大小时缓冲区就会被刷新)。 - 手动刷新缓冲区
fflush(stdout)
。 - 调用
exit(0)
;但是还可以调用_exit(0)
,不刷新缓冲区。
一般的解决办法
在 printf()
里加 \n
或新增一条语句输出 \n
printf("输入金额: ");
printf("\n");
scanf("%f", &money);
实测在问题 1 中这个方法无效,略。
使用 fflush(stdout);
强制刷新输出缓冲区
printf("输入金额: ");
fflush(stdout);
scanf("%f", &money);
使用 setvbuf(stdout,NULL,_IONBF,0);
或 setbuf(stdout, NULL);
禁用输出缓冲区
#include <stdio.h>
int main(void)
{
setvbuf(stdout,NULL,_IONBF,0); //或 setbuf(stdout, NULL);
float money;
printf("输入金额: ");
scanf("%f", &money);
return 0;
}
setbuf(stdout, NULL);
用于将标准输出设置为无缓冲,这意味着每次调用printf
时,输出会立即刷新,不会在内存中积累。它简单直接,但只支持无缓冲模式。
setvbuf(stdout, NULL, _IONBF, 0);
也是将标准输出设置为无缓冲,但提供了更多灵活性,允许用户自定义缓冲方式(如行缓冲或全缓冲)。因此,setvbuf
更适合需要控制缓冲行为的场景。
问题 2
运行以下程序时,在终端输入 a=3
,会立即输出 a=23, b=2
并结束程序,跳过了 b
的输入:
#include <stdio.h>
int main()
{
int a = 1, b = 2;
char c;
scanf("a=%d", &a);
scanf("b=%d", &b);
printf("a=%d, b=%d\n", a, b);
return 0;
}
如果改为要求直接输入数字的话是能正确执行的:
scanf("%d", &a); scanf("%d", &b);
这种情况下即使在开头使用 setvbuf(stdout,NULL,_IONBF,0);
或者在中间使用 fflush(stdout);
都不能解决问题:
setvbuf(stdout,NULL,_IONBF,0);
scanf("a=%d", &a);
fflush(stdout);
scanf("b=%d", &b);
彻底的解决方法
使用 getchar()
清空缓冲区
使用 while((c = getchar()) != '\n' && c != EOF);
:
关键之处在于,getchar() 是带有缓冲区的,并且一切字符通吃,或者说一切字符都会读取,不会忽略。
该代码不停地使用
getchar()
获取缓冲区中的字符,直到遇见换行符\n
或者到达文件结尾才停止。
scanf("a=%d", &a);
while((c = getchar()) != '\n' && c != EOF); //在下次读取前清空缓冲区
scanf("b=%d", &b);
使用 scanf()
清空缓冲区
使用 scanf("%*[^\n]"); scanf("%*c");
:
scanf()
有一种高级用法,就是使用类似于正则表达式的通配符,这样它就可以读取所有的字符了,包括空格、换行符、制表符等空白符,不会忽略它们。并且,scanf()
还允许把读取到的数据直接丢弃,不用赋值给变量。第一个
scanf()
将逐个读取缓冲区中\n
之前的其它字符,%
后面的*
表示将读取的这些字符丢弃,遇到\n
字符时便停止读取。
此时,缓冲区中尚有一个\n
遗留,第二个scanf()
再将这个\n
读取并丢弃,这里的星号和第一个scanf()
的星号作用相同。由于所有从键盘的输入都是以回车结束的,而回车会产生一个
\n
字符,所以将\n
连同它之前的字符全部读取并丢弃之后,也就相当于清除了输入缓冲区。
scanf("a=%d", &a);
scanf("%*[^\n]"); scanf("%*c"); //在下次读取前清空缓冲区
scanf("b=%d", &b);
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于