在 scanf() 格式字符串中添加尾随空格会产生什么影响?

38

在这段代码中,scanf("%d")scanf("%d ")有什么区别?区别在于格式字符串末尾的空格吗?

#include <stdio.h>

int main(void)
{
    int i, j;

    printf("enter a value for j ");
    scanf("%d  ",&j);
    printf("j is %d\n", j);
    printf("enter a value for i ");
    scanf("%d", &i);
    printf("i is %d\n", i);
    return 0;
}

如果我在格式说明符后添加空格,例如 scanf("%d ", &j);,那么 scanf() 函数实际上是如何工作的?


也许我们应该将标题改为更加通用的名称? - Stargateur
@Stargateur 你能提供建议吗? - Vikas Verma
我在考虑删除“尾随”的内容 - Stargateur
1
@Stargateur 你为什么想要这样做?问题(以及答案)明显集中在带有尾随空格的格式字符串上,而不是前导空格(当然可能有益,并且是其他SO问题的主题)。两种情况之间存在巨大的区别,因此我认为这个建议完全是无意义的,甚至是有害的。 - RobertS supports Monica Cellio
@RobertSsupportsMonicaCellio,前面、后面或中间的空格没有区别,你说得不对。如果我没记错的话,我想重命名以便更容易地找到这个问题。 - Stargateur
1
@Stargateur 正确的是实际行为相同,但实际效果取决于使用方式。跳过前导空格直到正确输入,如 " %c" 或在消耗时挂起(正如此问题所示)。除此之外,还有一个含义上的区别 - 尾随 空格 = 总是不好的;前导 空格 = 可以有用。我知道你想要表达什么,但问题已经很具体了,并且已经有了答案。更改标题将需要更改答案(包括示例等)。 - RobertS supports Monica Cellio
4个回答

38

在scanf格式中,空格字符会导致它显式读取并忽略尽可能多的空格字符。所以,在使用scanf("%d ", ...时,读取数字后,它将继续读取字符,丢弃所有空格,直到看到输入上的非空格字符。那个非空格字符将作为下一个要被输入函数读取的字符。

根据您的代码:

printf("enter a value for j ");

scanf("%d  ",&j);

printf("j is %d \n", j);

它将打印出第一行并等待您输入一个数字,然后在数字之后继续等待其他内容。因此,如果您只输入5Enter,它将似乎停滞不前——您需要在另一行上输入一些非空格字符才能继续。如果您随后输入6Enter,那么它将成为i的值,这样您的屏幕将显示类似于:

enter a value for j 5
6
j is 5
enter a value for i i is 6

此外,由于大多数 scanf 的 %-转换也会跳过前导空格(除了 %c, %[%n),因此在 %-转换之前的空格是无关紧要的("%d"" %d" 将表现相同)。因此,在 scanf 转换中大部分情况下应避免使用空格,除非您知道自己需要它们的奇特效果。


所以它挂起直到下一个非空格字符的原因是它想确保“好的,这是我能匹配的所有(聚合)空格”? - mzoz

6

在格式字符串中的空白字符(空格、换行、水平制表符和垂直制表符)匹配输入中任意数量的空白字符。

在您的第一种情况中

  scanf("%d  ",&j);

当程序遇到空格字符 (WSC) ' ' 时,它会吞噬用户输入的所有空白符,包括在按下 Enter 时输入的 \n,并期望输入非 WSC 字符。在这种情况下,您的程序将通过按下 Ctrl + Z 终止。


1
在一个 POSIX 系统上,如果该命令将顶部进程放入后台,它是否会真正终止? - Cloud
2
@Dogbert;不太确定,但是在MS-WIN上是:Ctrl + D - haccks

4

scanf格式字符串中的空白字符会匹配isspace所描述的任意数量的空白字符。因此,如果您有尾随空格、换行符、制表符或任何其他空白字符,则在scanf返回之前它也将被消耗。


1
这并没有清楚地表达关键点——即scanf()函数在输入非空白字符之前不会返回。这意味着你必须猜测程序接下来会询问什么,并在当前输入完成之前输入下一个值。(如果你没有输入完整的下一个值,那么你将会遇到更多问题;奇数个字符(以及在终端将字符发送给程序之前必须跟随的换行符)将被解释为下一个字段。交互式格式中的尾随空格是非常严重的! - Jonathan Leffler

0

虽然显而易见,但区别在于不同的格式字符串。如果您输入以下行:

"3 "

scanf()将成功返回。否则,它取决于您提供的输入。 scanf()本质上跳过空格(制表符、空格、换行符),并在输入流中搜索字母数字值。由于这是尾随空格,因此当按下ENTER时,它会与输入末尾的换行符一起合并,所以对结果影响不大。

scanf()期望提供的输入与您提供给它的格式字符串完全匹配,唯一的例外是连续的空格字符被压缩为单个空格字符。如果您想使用其字符串处理等效项sscanf()解析大量数据的话,这一点非常重要。

进一步测试这一点的好方法是进行类似以下的练习:

#include<stdio.h>

int main(void)
{
   int a=0,b=0,c=0;

   printf("Enter values for A, B, C, in the format: \"A B  -  C\"\n");
   scanf("%d %d  -  %d", &a, &b, &c);

   printf("Values: A:%d, B:%d, C:%d\n", a, b, c);
}

接下来,检查并查看这些整数的值在提供正确和不正确格式的控制台输入后是什么(例如:空格和连字符)。以下是几个示例运行。第一个使用了不正确的输入,第二个使用了正确格式的输入。请注意,在第一种情况下,C甚至没有被设置,因为如果输入和格式字符串不匹配,scanf()将提供意外的行为。通常,最好使用类似fgets()的东西从用户获取输入字符串,然后使用各种搜索函数(例如:strstr()、strch()、strcat、strcpy等)解析字符串,因为它比仅使用scanf()并假设用户不会犯错误更安全,无论是意外地还是故意地。

Enter values for A, B, C, in the format: "A B  -  C"
1 2 3
Values: A:1, B:2, C:0

Enter values for A, B, C, in the format: "A B  -  C"
1 2  -  3
Values: A:1, B:2, C:3

现在,考虑最后一次运行:您会发现scanf()将多个连续的空格字符压缩为一个字符,因此这些最终运行实际上成功了:
Enter values for A, B, C, in the format: "A B  -  C"
1 2 - 3
Values: A:1, B:2, C:3

Enter values for A, B, C, in the format: "A B  -  C"
1     2           -                     3
Values: A:1, B:2, C:3

1
-1:这些示例中格式字符串中的所有空格都是完全无关紧要的,可以删除而不会对结果产生任何影响,因此这并没有真正回答问题,问题是关于格式字符串中的尾随空格... - Chris Dodd
1
它总结了不同的空格字符如何被合并为一个,并且在这个特定的例子中尾随的空格是无关紧要的。它还描述了格式字符串本质上缩减为期望的空格和非空格模式,以及如何根据输入偏离格式模式的时间分配值到一些或所有目标变量。我认为这是没有必要的。 - Cloud
1
代码应该记录并报告scanf()的返回值。这将显示已设置多少个值。格式字符串中的尾随空格在交互式输入中并不是“无关紧要”的。这意味着用户必须在输入完成之前键入一个非空格字符(换行符是空格字符)。 - Jonathan Leffler
@ChrisDodd:在格式中破折号前的一个空格是有意义的,其他空格则不是。如果在破折号前没有空格(例如"%d%d-%d"),那么像23 34 - 45这样的输入会失败,因为输入中的空格与格式中的破折号不匹配(但"%d%d -%d"就可以)。你的主要观点是正确的;大多数空格在scanf()格式字符串中都是无关紧要的,但并非所有空格都是无关紧要的——哪些是无关紧要的,哪些不是,这种微妙之处困扰着scanf()等函数的使用。而格式字符串中的尾随空格尤其具有毒性! - Jonathan Leffler

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