scanf() 将换行符保留在缓冲区中

scanf() leaves the newline character in the buffer

提问人:ipkiss 提问时间:3/9/2011 最后编辑:chqrlieipkiss 更新时间:9/8/2022 访问量:143859

问:

我有以下程序:

int main(int argc, char *argv[])
{
    int a, b;
    char c1, c2;
    printf("Enter something: ");
    scanf("%d", &a); // line 1
    printf("Enter other something: ");
    scanf("%d", &b); // line 2

    printf("Enter a char: ");
    scanf("%c", &c1); // line 3
    printf("Enter another char: ");
    scanf("%c", &c2); // line 4

    printf("Done"); // line 5

    system("PAUSE");

    return 0;
}

正如我在 C 书中读到的那样,作者说在缓冲区中留下了一个换行符,因此,程序不会在第 4 行停止供用户输入数据,而是将换行符存储在第 5 行并移动到第 5 行。scanf()c2

是吗?

但是,这是否只发生在数据类型上?因为我没有看到第 1、2、3 行的数据类型存在此问题。这是对的吗?charint

c 扫描

评论

1赞 Jonathan Leffler 12/2/2018
有时建议可以在调用单个字符之前使用。请阅读 使用 fflush(stdin) 来讨论该方法的优缺点和替代方案(该方法或多或少在 Windows 上有效,并且在大多数其他地方不起作用)。fflush(stdin)scanf()
0赞 surya kiran 4/1/2020
你能告诉我们你指的是哪本书吗?
1赞 Zakk 5/22/2022
@JonathanLeffler 使用是普通的未定义行为。fflush(stdin)

答:

123赞 Jeremiah Willcock 3/9/2011 #1

scanf() 函数在尝试解析字符以外的转换之前会自动跳过前导空格。字符格式(主要是;也是扫描集 - 和)是例外;它们不会跳过空格。%c%[…]%n

与前导空格一起使用可跳过可选的空格。不要在格式字符串中使用尾随空格。" %c"scanf()

请注意,这仍然不会消耗输入流中留下的任何尾随空格,甚至不会消耗到行的末尾,因此,如果在同一输入流上也使用 getchar()fgets(),请注意这一点。我们只是在转换之前跳过空格,就像它对其他非字符转换所做的那样。%d


请注意,除转换之外的非空格“指令”(使用 POSIX scanf 术语),例如 中的文字文本也不会跳过空格。文本必须与要读取的下一个字符匹配。scanf("order = %d", &order);order

因此,如果您想跳过上一行的换行符,但仍然需要固定字符串上的文字匹配,您可能需要那里,就像这个问题一样" order = %d"

评论

15赞 chux - Reinstate Monica 12/12/2015
%c、 是 3 个指定的期望值,它们不占用前导空格。%n%[]
0赞 Suraj Jain 2/19/2017
@chux 在其他情况下,scanf 会清除缓冲区中的所有空格,还是忽略它们,因为它们输入了,但它们仍然存在?
0赞 chux - Reinstate Monica 2/20/2017
@SurajJain 是的,
5赞 Jonathan Leffler 10/17/2017
请参阅 scanf() 格式字符串中的尾随空白和 scanf() 请求两次输入,而我希望它只询问一次以讨论格式字符串中的尾随空白。它们是个坏主意——如果你期望人与人之间的交互,那就太糟糕了,而对程序交互来说却是坏事。
0赞 Priyanshul Govil 4/12/2021
这个答案很好地解释了读取然后格式化,而不仅仅是根据格式读取。scanf()
42赞 Shweta 3/9/2011 #2

用。这将解决您的问题。scanf(" %c", &c2);

8赞 Jiwon 8/3/2018 #3

在调用第二个之前使用。getchar()scanf()

scanf("%c", &c1);
getchar();  // <== remove newline
scanf("%c", &c2);

评论

2赞 Jonathan Leffler 12/2/2018
只要用户没有键入任何其他内容(例如尾随空格),就可以这样做。但它不如扫描到下一个换行符的循环好:(不在注释中时写在三行上)。这通常就足够了;它不是万无一失的(你必须记住,傻瓜在崩溃方面非常聪明)。int c; while ((c = getchar()) != EOF && c != '\n') ;
0赞 john 2/15/2021
@JonathanLeffler你的意思是写在两行上?int c; while ((c = getchar()) != EOF && c != '\n') ;
3赞 Jonathan Leffler 2/15/2021
@john:它可以是一行、两行、三行或更多行——编译器不在乎。留给我自己的设备,它将是三行:声明、循环控制和循环体。表示空循环体的分号将单独在一行上,以强调它是故意的(正如K&R所建议的那样)。
14赞 brandizzi 1/28/2019 #4

另一个选项(我从这里得到的)是使用 assignment-supression 选项读取和放弃换行符。为此,我们只需设置一种格式来读取 和 之间带有星号的字符:%c

scanf("%d%*c",&a); // line 1
scanf("%c%*c",&c1); // line 3

scanf然后将读取下一个字符(即换行符),但不会将其分配给任何指针。

然而,最后,我会支持FAQ的最后一个选项

或者,根据您的要求,您也可以忘记 scanf()/getchar(),使用 fgets() 从用户那里获取一行文本并自行解析。

评论

6赞 Jonathan Leffler 1/12/2020
这种技术的问题在于,如果用户先键入空格,然后再输入换行符,则抑制的字符转换会读取空格,但仍会保留换行符。如果用户在您期望的时候键入,则有很多额外的字符需要处理。您也无法判断尾随抑制的转化是否成功 - 它们不计入 的返回中。asupercalifragilisticexpialidociousascanf()
3赞 Weather Vane 1/25/2022
这只会丢弃一个字符。在前面放置一个空格将丢弃任意数量的前导空格(包括无空格)。%
10赞 ilkkachu 2/19/2021 #5

为了回应我在另一个关于 C++ 的答案中发布的内容:我建议扔掉它,永远不要使用它,而是使用 和.scanf()fgets()sscanf()

这样做的原因是,至少在类Unix系统中,默认情况下,运行CLI程序的终端在程序看到用户输入之前会对其进行一些处理。它缓冲输入,直到输入换行符,并允许进行一些基本的行编辑,例如使退格键起作用。

所以,你永远不可能一次得到一个字符,或者几个单一的字符,只有一整行。但这不是例如 进程,相反,它只处理数字,并停在那里,将其余部分缓冲在 C 库中,以供将来的函数使用。如果您的程序具有例如scanf("%d")stdio

printf("Enter a number: ");
scanf("%d", &a);

printf("Enter a word: ");
scanf("%s", word);

然后你输入行,它同时完成两个 s,但只有在给出换行符之后。当用户点击空格时,第一个不会返回,即使这是数字结束的地方(因为此时该行仍在终端的行缓冲区中);第二个不会等待您输入另一行(因为输入缓冲区已经包含足够的内容来填充转换)。123 abcdscanf()scanf()scanf()%s

这不是用户通常所期望的!

相反,他们希望按回车键完成输入,如果你按回车键,你要么得到一个默认值,要么得到一个错误,并可能建议真的给出答案。

你不能真的用 .如果用户只是按回车键,则不会发生任何事情。因为还在等号码。终端会继续发送线路,但您的程序看不到它,因为会吃掉它。你没有机会对用户的错误做出反应。scanf("%d")scanf()scanf()

那也不是很有用。

因此,我建议一次使用 Or 读取一整行输入。这与终端提供的内容完全匹配,并且始终在用户输入线路后为您提供程序控制权。你对输入行的处理取决于你,如果你想要一个数字,你可以使用 、 ,甚至解析这个数字。 与 没有相同的不匹配,因为它从中读取的缓冲区大小有限,当它结束时,它就结束了——函数不能等待更多。fgets()getline()atoi()strtol()sscanf(buf, "%d", &a)sscanf()scanf()

fscanf() 如果文件格式支持它如何像任何空格一样浏览换行符,那么常规文件也可以。对于面向行的数据,我仍然会使用 fgets()sscanf()。


所以,而不是我上面的内容,使用这样的东西:

printf("Enter a number: ");

fgets(buf, bufsize, stdin);
sscanf(buf, "%d", &a);

或者,实际上,检查 too 的返回值,以便您可以检测空行和其他无效数据:sscanf()

#include <stdio.h>

int main(void)
{
    const int bufsize = 100;
    char buf[bufsize];
    int a;
    int ret;
    char word[bufsize];

    printf("Enter a number: ");
    fgets(buf, bufsize, stdin);

    ret = sscanf(buf, "%d", &a);

    if (ret != 1) {
        fprintf(stderr, "Ok, you don't have to.\n");
        return 1;
    }

    printf("Enter a word: ");
    fgets(buf, bufsize, stdin);

    ret = sscanf(buf, "%s", word);
    if (ret != 1) {
        fprintf(stderr, "You make me sad.\n");
        return 1;
    }

    printf("You entered %d and %s\n", a, word);
}

当然,如果你想让程序坚持下去,你可以创建一个简单的函数来循环 和 直到用户屈尊去做他们被告知的事情;或者立即退出并出现错误。取决于你认为如果用户不想打球,你的程序应该做什么。fgets()sscanf()


你可以做类似的事情,例如,通过循环读取字符,直到返回换行符,从而清除缓冲区中留下的任何垃圾,但这对用户只是在空行上按回车键的情况没有任何作用。无论如何,会一直读到换行符,所以你不必自己做。getchar()scanf("%d")fgets()

评论

1赞 Andrew Henle 2/19/2021
fgets()/getline()而then or ,等等,也比直接使用还有另一个巨大的优势。程序遇到意外输入时,输入流不会处于未知状态,在这种状态下,在不丢失数据的情况下进行恢复是不可能的。sscanf()strtol()scanf()
0赞 Andreas Wenzel 9/11/2022
有关以下替代方案的进一步阅读:我可以用什么来代替 scanf?此外,本指南可能会有所帮助: 远离 scanf() 的初学者指南scanf
-4赞 Rai Singh 12/1/2021 #6
/*Take char input using scanf after int input using scanf just use fflush(stdin) function  after int input */
#include<stdio.h>
#include<conio.h>
void main()
{
  int x;
  char y;
  clrscr();
  printf(" enter an int ");
  scanf("%d",&x);
  fflush(stdin);
  printf("\n Now enter a char");
  scanf("%c",&y);
  printf("\n X=%d and Y=%c",x,y);
  getch();
}

评论

2赞 Jonathan Leffler 4/13/2022
请注意有关使用 fflush(stdin) 的注意事项。
1赞 Andreas Wenzel 6/6/2022
conio.h不是 ISO C 的一部分,而是特定于平台的标头。这些功能也是特定于平台的。请注意,该问题未标记任何特定平台,因此该问题不适用于任何特定平台。因此,如果您决定提供特定于平台的答案,您至少应该清楚地将其标记为此类答案,并指定您的答案适用于哪个平台。clrscrgetch
0赞 wangloo 9/8/2022 #7

两种解决方法。一种是巧妙地捕捉多余的空格。

getchar(); // (1) add `getchar()`
scanf("%c", &c1);

scanf(" %c", &c1); // (2) add whitespace before %c

另一种是用 to 代替。注意:是不安全的,看看为什么得到是危险的fgets()scanf()gets()

相比之下,我更愿意推荐第二种方式。因为它更具可性和可维护性。例如,在第一种方式中,您必须在添加/移动一些 .scanf()