【问题标题】:scanf() leaves the newline character in the bufferscanf() 将换行符留在缓冲区中
【发布时间】:2025-12-27 08:30:09
【问题描述】:

我有以下程序:

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 书中看到的,作者说scanf() 在缓冲区中留下了一个换行符,因此,程序不会在第 4 行停止让用户输入数据,而是存储换行符在 c2 中并移动到第 5 行。

是吗?

但是,这是否只发生在 char 数据类型上?因为我没有看到int数据类型有这个问题,如第1、2、3行。是吗?

【问题讨论】:

  • 有时建议fflush(stdin) 可以在调用scanf() 之前用于单个字符。请阅读Using fflush(stdin),了解该方法的优缺点和替代方法(或多或少在 Windows 上有效,而在大多数其他地方无效)。
  • 您能告诉我们您指的是哪本书吗?

标签: c scanf


【解决方案1】:

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

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

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


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

因此,如果您想从上一行跳过换行符,但仍需要在固定字符串 like this question 上进行文字匹配,您可能希望在此处使用 " order = %d"

【讨论】:

  • %c%n%[] 是不使用前导空格的 3 个指定期望。
  • @chux 那么在其他情况下,scanf 会清除缓冲区中之前的所有空格,或者因为它们输入而忽略它们但它们仍然存在?
  • @SurajJain 是的,
  • 请参阅Trailing blank in scanf() format stringscanf() asking twice for input while I expect it to ask only once,了解格式字符串中尾随空格的讨论。它们是个坏主意——如果您期望人类交互并且对程序交互不利。
  • 这个答案很好地解释了scanf()读取然后格式化,而不仅仅是根据格式读取。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 如果用户没有输入任何其他内容(例如尾随空格),则此方法有效。但它不如扫描到下一个换行符的循环:int c; while ((c = getchar()) != EOF &amp;&amp; c != '\n') ;(不在注释中时写成三行)。这通常就足够了;它不是万无一失的(你必须记住,傻瓜很聪明,很擅长搞砸事情)。
    • @JonathanLeffler 你的意思是写了两行吗? int c; while ((c = getchar()) != EOF &amp;&amp; c != '\n') ;
    • @john:可以是一、二、三行或更多行——编译器不在乎。留给我自己的设备,它将是三行:声明、循环控制和循环体。表示空循环体的分号将单独在一行上,以强调它是故意的(如 K&R 所建议的那样)。
    【解决方案4】:

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

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

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

    不过,最后我还是要第二个the FAQ's last option

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

    【讨论】:

    • 这种技术的问题在于,如果用户键入a 然后是空格然后是换行符,被抑制的字符转换会读取空格并仍然留下换行符。如果用户在您期望a 时键入supercalifragilisticexpialidocious,那么您需要处理很多额外的字符。您也永远无法判断尾随被抑制的转化是否成功——它们不计入来自scanf() 的返回中。
    【解决方案5】:

    为了回应我在another answer about C++ 中发布的内容:我建议将scanf() 扔掉,永远不要使用它,而是使用fgets()sscanf()

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

    因此,您永远不能一次获取一个字符,或者几个单个字符,而只能获取一整行。但这不是例如scanf("%d") 处理,而不是只处理数字,并停在那里,将其余部分缓存在 C 库中,以供将来的 stdio 函数使用。如果你的程序有例如

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

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

    这不是用户通常期望的!

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

    scanf("%d") 无法真正做到这一点。如果用户只是按 Enter 键,则什么也不会发生。因为scanf()还在等号码。终端继续发送该行,但您的程序看不到它,因为scanf() 吃掉了它。您没有机会对用户的错误做出反应。

    这也不是很有用。

    因此,我建议使用fgets()getline() 一次读取整行输入。这与终端给出的完全匹配,并且总是在用户输入一行后给予您的程序控制权。你对输入行做什么取决于你,如果你想要一个数字,你可以使用atoi()strtol(),甚至sscanf(buf, "%d", &amp;a)来解析数字。 sscanf()scanf() 没有相同的不匹配,因为它读取的缓冲区大小有限,当它结束时,它就结束了——函数不能等待更多。

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


    所以,不要使用我上面的内容,而是使用如下内容:

    printf("Enter a number: ");
    
    fgets(buf, bufsize, stdin);
    sscanf(buf, "%d", &a);
    

    或者,实际上,也检查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() 会一直读到换行符,所以你不必自己做。

    【讨论】:

    • fgets()/getline() 然后sscanf()strtol() 等,也比直接使用scanf() 有另一个巨大的优势。 程序遇到意外输入时,输入流不会处于未知状态,不可能在不丢失数据的情况下恢复。
    【解决方案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();
    }
    

    【讨论】: