在C语言中使用scanf函数及其与输入缓冲区的关系

6

我尝试理解scanf和输入缓冲区之间的关系。我使用以下格式字符串来使用scanf:

int z1,z2;
scanf("%d %d", &z1,&z2);

请尝试理解一下为什么我可以在输入数字54后输入尽可能多的空格(回车,空格,制表符)。
据我所知,我按下的每个键都会放在输入缓冲区中,直到我按下Enter键。
因此,如果我输入54并按Enter键,输入缓冲区包含3个元素,即两个数字和换行符。因此我的缓冲区看起来像 [5][4][\n]。
现在scanf/formatstring是从左向右进行评估的。所以第一个%d匹配54,54存储在z1中。
由于格式字符串中有空格,因此由按下第一个Enter键引起的换行符(\n)被"消耗"了。
因此,在第一个%d和空格(\n)的评估之后,缓冲区再次为空。
现在scanf尝试评估格式字符串中的第二个(也是最后一个)%d。 因为缓冲区现在是空的,scanf等待进一步的用户输入(用户输入=在我的情况下从stdin读取,即键盘)。
因此,缓冲区状态/动作序列如下:
缓冲区为空 - > 调用scanf - > scanf阻塞用户输入 -> 用户输入为: 54 Enter - > 缓冲区包含: [5][4][\n] - > 评估第一个%d - > 缓冲区包含[\n] - > 评估空格 - > 缓冲区为空 - > scanf阻塞用户输入(因为评估第二个也是最后一个%d) - > ...。
我理解得对吗?(对不起,英语不是我的母语)
问候。

如果您使用终端,则终端对输入的处理也起到一定作用; 通常,程序只会收到完成的输入行,但这并不是必须的。更容易讨论程序从文件中进行输入重定向的行为,例如prog < prepared-inp.txt,在Windows命令 shell 和各种Linux shell 中的工作方式相似。 - Peter - Reinstate Monica
请注意,"%d%d"格式字符串的行为完全相同;解析第一个整数时会在遇到第一个非数字字符(包括换行符等空格字符)时停止,而第二个%d格式正确地跳过它遇到的所有空格字符,因为它只对数字感兴趣。格式字符串中的空格字符仅对%c%[...]有意义(否则将其分配给相应的参数)。 - Peter - Reinstate Monica
我本身也不喜欢“缓冲区”范式,因为可能没有任何缓冲区,或者可能有任意数量的缓冲区(在键盘、远程终端/计算机、接入点、网络卡、程序中)。后者可能是你的意思(可以使用setbuf()更改),但你也可以禁用它!有趣的是*getchar()看到的字符序列*。它们确切来自哪里以及它们是否被缓冲在某个地方是次要的。 - Peter - Reinstate Monica
@PeterSchneider 空格对于 %n 也非常重要。 - Spikatrix
@CoolGuy 你的意思是输入中的空白字符[不会]被跳过 -- 因此在赋值之前[不会]被计算 -- 取决于格式中 %n 之前是否存在空白字符?没错。当然了;-)。 - Peter - Reinstate Monica
2个回答

3
据我理解,我按下的每个键都会被放入输入缓冲区,直到我按下回车键为止。
是的。按下回车键会将数据刷新到stdin(标准输入流)中。请注意,它还会发送\n到stdin中。
所以如果我输入54并按Enter键,则输入缓冲区包含3个元素,两个数字和换行符。因此,我的缓冲区看起来像[5] [4] [\n]。
是的。
现在scanf/formatstring从左到右进行评估。因此第一个%d匹配54,54存储在z1中。
正确的。
由于格式字符串中的空格,因此从按下第一个回车键引起的换行符(\n)被“消耗”。
正确。
因此,在评估第一个%d和空格(\n)之后,缓冲区再次为空。
是的。
现在scanf尝试评估格式字符串中的第二个(也是最后一个)%d。
不完全正确。
两个%d之间的空格是一个空白字符,在scanf的格式字符串中,空白字符指示scanf扫描和丢弃所有的空白字符(如果有),直到第一个非空白字符。这可以在C11标准的委员会草案n1570中看到:

7.21.6.2 fscanf函数

[...]

  1. 由空格字符组成的指令通过读取输入直到第一个非空格字符(未读取的字符)或者无法再读取为止来执行。该指令永远不会失败。

这意味着执行仍然停留在%d之间的空格处,因为它还没有遇到非空白字符。

由于缓冲区现在为空,scanf等待进一步的用户输入(在我的情况下是从标准输入读取,即键盘输入)。

是的。

所以,

"缓冲区为空 -> 调用 scanf -> scanf 阻塞等待用户输入 --> 用户输入为:54\n --> 缓冲区包含:54\n --> 对第一个 %d 进行评估 --> 缓冲区包含 \n --> 评估空格 --> 缓冲区为空 --> scanf 阻塞等待用户输入 (因为评估了第二个和最后一个 %d) --> ..."
请注意,当在%d之间有许多空格字符或没有空格字符(在%d之前)时,scanf的行为方式将与%d相同,因为%d已经跳过了前导空格字符。实际上,唯一对于空格字符具有重要意义的格式说明符是%c%[%n,如n1570所示:

7.21.6.2 fscanf函数

[...]

  1. 输入的空白字符(由isspace函数指定)将被跳过,除非规范包括[cn说明符。284

1
基本上是这样的。
Scanf读取输入缓冲区(stdin)。
在Windows cmd.exe终端中,按下回车键会将您键入的内容刷新到输入缓冲区中,使您的第一个变量被填充。
然后再次提示,以填写第二个变量。

scanf在进行%c%[..]转换时不会停留在空格处。 - Peter - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接