【问题标题】:How to clear input buffer in C?如何清除C中的输入缓冲区?
【发布时间】:2011-12-15 10:35:04
【问题描述】:

我有以下程序:

int main(int argc, char *argv[])
{
  char ch1, ch2;
  printf("Input the first character:"); // Line 1
  scanf("%c", &ch1); 
  printf("Input the second character:"); // Line 2
  ch2 = getchar();

  printf("ch1=%c, ASCII code = %d\n", ch1, ch1);
  printf("ch2=%c, ASCII code = %d\n", ch2, ch2);

  system("PAUSE");  
  return 0;
}

正如上述代码的作者所解释的: 该程序将无法正常运行,因为在第 1 行,当用户按 Enter 时,它会在输入缓冲区 2 中留下字符:Enter key (ASCII code 13)\n (ASCII code 10)。因此,在第 2 行,它将读取\n,并且不会等待用户输入字符。

好的,我知道了。但我的第一个问题是:为什么第二个getchar() (ch2 = getchar();) 不读取Enter key (13),而不是\n 字符?

接下来,作者提出了2种解决此类问题的方法:

  1. 使用fflush()

  2. 这样写一个函数:

    void
    clear (void)
    {    
      while ( getchar() != '\n' );
    }
    

这段代码确实有效。但我无法解释自己是如何工作的?因为在while语句中,我们使用getchar() != '\n',意思是读取除'\n'以外的任何单个字符?如果是这样,在输入缓冲区中仍然保留'\n' 字符?

【问题讨论】:

    标签: c buffer


    【解决方案1】:

    程序将无法正常运行,因为在第 1 行,当用户按下 Enter 键时,它会在输入缓冲区中留下第 2 个字符:Enter 键(ASCII 码 13)和 \n(ASCII 码 10)。因此,在第 2 行,它将读取 \n 而不会等待用户输入字符。

    您在第 2 行看到的行为是正确的,但这并不是完全正确的解释。对于文本模式流,您的平台使用什么行尾(无论是回车符(0x0D)+换行符(0x0A)、纯 CR 还是纯 LF)都无关紧要。 C 运行时库会为您解决这个问题:您的程序只会看到 '\n' 换行符。

    如果您键入一个字符并按下回车键,则该输入字符将在第 1 行读取,然后'\n' 将在第 2 行读取。请参阅 comp.lang.c 常见问题解答中的I'm using scanf %c to read a Y/N response, but later input gets skipped.

    关于建议的解决方案,请参阅(再次来自 comp.lang.c 常见问题解答):

    这基本上表明唯一可移植的方法是:

    int c;
    while ((c = getchar()) != '\n' && c != EOF) { }
    

    您的getchar() != '\n' 循环有效,因为一旦您调用getchar(),返回的字符已经从输入流中删除。

    另外,我有义务阻止你完全使用scanfWhy does everyone say not to use scanf? What should I use instead?

    【讨论】:

    • 这将挂起,等待用户按 Enter。如果你想简单地清除标准输入,直接使用 termios 。 stackoverflow.com/a/23885008/544099
    • @ishmael 您的评论没有意义,因为 clear 标准输入不需要按 Enter;首先需要获得输入。 (这是在 C 中读取输入的唯一标准方法。如果您希望能够立即读取键,那么您将不得不使用非标准库。)
    • @jamesdlin 在 Linux 上,getchar() 将等待用户按 Enter。只有这样他们才会跳出while循环。据我所知,termios 是一个标准库。
    • @ishmael 不,你在这两个方面都错了。这个问题是关于如何从标准输入 读取输入后清除剩余的输入,在标准 C 中,如果输入 first 需要输入一行并按 Enter 键。 输入该行后,getchar() 将读取并返回这些字符;用户无需再次按 Enter 键。此外,虽然 termios 可能在 Linux 上很常见,但它不是 C 标准库的一部分。
    • scanf(" %c", &char_var) 在 %c 说明符前添加一个空格而忽略换行符的原因是什么?
    【解决方案2】:

    你也可以这样做:

    fseek(stdin,0,SEEK_END);
    

    【讨论】:

    • 哇...顺便说一句,标准是否说fseek 可用于stdin
    • 我不知道,我认为它没有提到它,但stdin是 FILE* 并且 fseek 接受 FILE* 参数。我对其进行了测试,它可以在 Mac OS X 上运行,但不能在 Linux 上运行。
    • 请解释一下。如果作者能写出这种方法的含义,那将是一个很好的答案。有什么问题吗?
    • @EvAlex:这有很多问题。如果它适用于您的系统,那就太好了;如果不是,那也就不足为奇了,因为当标准输入是一个交互式设备(或像管道、套接字或 FIFO 等不可搜索的设备,仅举几例其他方式时,它可以失败)。
    • 为什么应该是 SEEK_END?我们可以改用 SEEK_SET 吗? FP 标准输入的行为如何?
    【解决方案3】:

    一种可移植的方式来清除您已经尝试部分阅读的行尾:

    int c;
    
    while ( (c = getchar()) != '\n' && c != EOF ) { }
    

    这会读取并丢弃字符,直到它得到\n,它表示文件结束。它还会检查EOF,以防输入流在行尾之前关闭。 c 的类型必须为 int(或更大)才能保持值 EOF

    没有可移植的方法来确定当前行之后是否还有其他行(如果没有,则getchar 将阻止输入)。

    【讨论】:

    • 为什么是 while((c=getchar())!=EOF);不工作?这是无限循环。无法理解 EOF 在标准输入中的位置。
    • @Rajesh 这将在输入流关闭之前清除,如果稍后有更多输入将不会清除
    • @Rajesh 是的,你是对的。如果在调用它时 stdin 上没有要刷新的内容,则会因为陷入无限循环而阻塞(挂起)。
    • @M.M EOF 的值是-1 那么为什么char 不好,因为最终我们只取字符。
    【解决方案4】:

    线条:

    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF)
        ;
    

    不只读取换行符之前的字符 ('\n')。它读取流中的所有字符(并丢弃它们)直到并包括下一个换行符(或遇到 EOF)。为了使测试为真,它必须首先读取换行符;所以当循环停止时,换行符是最后一个读取的字符,但它已经被读取了。

    至于为什么它读取换行符而不是回车符,那是因为系统已将回车符转换为换行符。当按下 enter 时,表示行结束......但流包含换行符,因为这是系统的正常行尾标记。这可能取决于平台。

    另外,在输入流上使用fflush() 并不适用于所有平台;例如,它通常不适用于 Linux。

    【讨论】:

    • 注意:while ( getchar() != '\n' ); 是一个无限循环,由于文件结束,getchar() 返回EOF
    • 要检查EOF,也可以使用int ch; while ((ch = getchar()) != '\n' && ch != EOF);
    【解决方案5】:

    但我无法解释自己是如何工作的?因为在while语句中,我们使用getchar() != '\n',这意味着读取除'\n'之外的任何单个字符??如果是这样,在输入缓冲区中仍然保留'\n' 字符??? 我是不是误会了什么?

    您可能没有意识到比较发生在 getchar() 从输入缓冲区中删除字符之后。因此,当您到达'\n' 时,它会被消耗,然后然后您会跳出循环。

    【讨论】:

      【解决方案6】:

      你可以试试

      scanf("%c%*c", &ch1);
      

      %*c 接受并忽略换行符

      另一种方法 而不是调用未定义行为的 fflush(stdin) 您可以编写

      while((getchar())!='\n');
      

      不要忘记while循环后的分号

      【讨论】:

      • 1) "%*c" 扫描任何字符(并且不保存),无论是换行符还是其他字符。代码依赖于第二个字符是一个新行。 2) while((getchar())!='\n'); 是一个无限循环,由于文件结束,getchar() 返回 EOF
      • 第二种方法取决于换行符,空字符,EOF等条件(上面是换行符)
      【解决方案7】:

      试试这个:

      stdin->_IO_read_ptr = stdin->_IO_read_end;

      【讨论】:

      【解决方案8】:

      我很惊讶没有人提到这个:

      scanf("%*[^\n]");
      

      【讨论】:

        【解决方案9】:

        我在尝试实施解决方案时遇到问题

        while ((c = getchar()) != '\n' && c != EOF) { }
        

        我为可能遇到相同问题的任何人发布了一些调整“代码 B”。

        问题是程序让我捕捉到 '\n' 字符,独立于输入字符,这是给我问题的代码。

        代码 A

        int y;
        
        printf("\nGive any alphabetic character in lowercase: ");
        while( (y = getchar()) != '\n' && y != EOF){
           continue;
        }
        printf("\n%c\n", toupper(y));
        

        调整是在 while 循环中的条件被评估之前“捕获”(n-1)个字符,这里是代码:

        代码 B

        int y, x;
        
        printf("\nGive any alphabetic character in lowercase: ");
        while( (y = getchar()) != '\n' && y != EOF){
           x = y;
        }
        printf("\n%c\n", toupper(x));
        

        可能的解释是,要中断while循环,它必须将值'\n'分配给变量y,因此它将是最后一个分配的值。

        如果我在解释中遗漏了代码 A 或代码 B,请告诉我,我对 c 几乎是新手。

        希望对某人有所帮助

        【讨论】:

        • 您应该在while 循环内处理您的“预期”字符。在循环之后,您可以认为stdinclean/clear 并且y 将保持'\n' 或EOFEOF 在不存在换行符且缓冲区已用尽时返回(如果在按下 [ENTER] 之前输入会溢出缓冲区)。 -- 您有效地使用while((ch=getchar())!='\n'&&ch!=EOF); 来咀嚼stdin 输入缓冲区中的每个字符。
        【解决方案10】:
        unsigned char a=0;
        if(kbhit()){
            a=getch();
            while(kbhit())
                getch();
        }
        cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;
        

        -或-

        使用也许使用_getch_nolock() ..???

        【讨论】:

        • 问题是关于 C,而不是 C++。
        • 请注意,kbhit()getch() 是在 &lt;conio.h&gt; 中声明的函数,并且仅在 Windows 上作为标准提供。它们可以在类 Unix 机器上模拟,但这样做需要小心。
        【解决方案11】:

        尚未提及的另一个解决方案是使用: 倒带(标准输入);

        【讨论】:

        • 如果标准输入被重定向到一个文件,那将完全破坏程序。对于交互式输入,它不能保证做任何事情。
        【解决方案12】:

        简而言之。放线...

        while ((c = getchar()) != '\n') ;
        

        ...在读取输入的行之前是唯一有保证的方法。 它仅使用核心 C 功能(根据 K&R,“C 语言的传统核心”),保证在所有情况下都可以与所有编译器一起使用。

        实际上,您可以选择添加一个提示,要求用户按 ENTER 继续(或者,可选地,按 Ctrl-D 或任何其他按钮来完成或执行其他代码):

        printf("\nPress ENTER to continue, press CTRL-D when finished\n");    
        while ((c = getchar()) != '\n') {
                if (c == EOF) {
                    printf("\nEnd of program, exiting now...\n");
                    return 0;
                }
                ...
            }
        

        还是有问题。用户可以多次按 ENTER。这可以通过在输入行中添加输入检查来解决:

        while ((ch2 = getchar()) < 'a' || ch1 > 'Z') ;
        

        以上两种方法结合理论上应该是防弹的。 在所有其他方面,@jamesdlin 的回答是最全面的。

        【讨论】:

          【解决方案13】:

          快速解决方案是添加第二个 scanf 以便它强制 ch2 暂时吃回车。不做任何检查,所以它假定用户会玩得很好。没有完全清除输入缓冲区,但它工作得很好。

          int main(int argc, char *argv[])
          {
            char ch1, ch2;
            printf("Input the first character:"); // Line 1
            scanf("%c", &ch1); 
            scanf("%c", &ch2); // This eats '\n' and allows you to enter your input
            printf("Input the second character:"); // Line 2
            ch2 = getchar();
          
            printf("ch1=%c, ASCII code = %d\n", ch1, ch1);
            printf("ch2=%c, ASCII code = %d\n", ch2, ch2);
          
            system("PAUSE");  
            return 0;
          }
          

          【讨论】:

            【解决方案14】:

            scanf 是一个奇怪的功能,电影WarGames 中有一句经典台词与此相关:“唯一获胜的动作就是不玩”。

            如果你发现自己需要“刷新输入”,你已经输了。制胜法宝不是拼命寻找某种神奇的方法来刷新令人讨厌的输入:相反,您需要做的是以更好的方式进行输入,这不涉及留下未读的输入输入流,并且它会导致问题,因此您必须尝试刷新它。

            基本上有三种情况:

            1. 您正在使用scanf 读取输入,并且它将用户的换行符留在输入中,并且稍后调用getcharfgets 会错误地读取该杂散换行符。 (这是您最初询问的情况。)

            2. 您正在使用scanf 读取输入,并且它在输入上留下了用户的换行符,并且稍后对scanf("%c") 的调用会错误地读取该流浪换行符。

            3. 您正在使用 scanf 读取数字输入,而用户正在输入非数字输入,并且非数字输入留在输入端,这意味着下一个 scanf 调用也失败了。

            在所有这三种情况下,似乎正确的做法是“刷新”有问题的输入。你可以尝试,但最好的情况是麻烦,最坏的情况是不可能的。最后,我认为尝试刷新输入是错误的方法,并且有更好的方法,具体取决于您担心的情况:

            在情况 1 中,更好的解决方案是,不要将对 scanf 的调用与其他输入函数混在一起。要么使用scanf 完成所有输入,要么使用getchar 和/或fgets 完成所有输入。要使用scanf 完成所有输入,您可以使用scanf("%c") 替换对getchar 的调用——但请参阅第2 点。理论上您可以使用scanf("%[^\n]*c") 替换对fgets 的调用,尽管这会带来各种进一步的问题我不推荐它。要使用fgets 完成所有输入,即使您想要/需要一些scanf 的解析,您可以使用fgets 读取行,然后在事后使用sscanf 解析它们。

            在情况 2 中,更好的解决方案是,永远不要使用scanf("%c")。请改用scanf(" %c")。神奇的额外空间让一切变得不同。 (对于额外空间的作用以及它为什么有帮助,有很长的解释,但这超出了这个答案的范围。)

            而在第 3 种情况下,恐怕根本就没有好的解决方案。 scanf 有很多问题,其中一个问题是它的错误处理很糟糕。如果你想编写一个简单的程序来读取数字输入,并且如果你可以假设用户总是会在提示时输入正确的数字输入,那么scanf("%d") 可能是足够的 - 勉强足够 -输入法。但是,如果您的目标是提示用户输入一些数字,并检查用户是否确实输入了数字,如果没有,则打印错误消息并要求用户再试一次,那么我相信对于所有意图和目的你无法基于scanf实现这个目标。你可以试试,但这就像给蠕动的婴儿穿连体衣一样:在双腿和一只手臂进入后,当你试图让第二只手臂进入时,一条腿会扭出。尝试使用scanf 来读取和验证可能不正确的数字输入实在是太过分了。以其他方式来做这件事要容易得多。

            您会注意到我列出的所有三个案例都有一个主题:它们都以“您正在使用scanf...读取输入”开头。这里有一个不可避免的结论。请参阅另一个问题:What can I use for input conversion instead of scanf?

            现在,我意识到我还没有回答你实际提出的问题。当人们问“我该怎么做 X?”时,当所有的答案都是“你不应该做 X”时,这真的很令人沮丧。如果您真的非常想刷新输入,那么除了人们在这里给您的其他答案之外,另外两个充满相关答案的好问题是:

            【讨论】:

              【解决方案15】:

              我编写了一个函数(也对其进行了测试),它从标准输入获取输入并丢弃额外的输入(字符)。此函数称为 get_input_from_stdin_and_discard_extra_characters(char *str, int size),它读取最大“size - 1”个字符并在末尾附加一个 '\0'。

              代码如下:

              
              /* read at most size - 1 characters. */
              char *get_input_from_stdin_and_discard_extra_characters(char *str, int size)
              {
              
                  char c = 0;
                  int num_chars_to_read = size - 1;
                  int i = 0;
              
                  if (!str)
                      return NULL;
              
                  if (num_chars_to_read <= 0)
                      return NULL;
              
                  for (i = 0; i < num_chars_to_read; i++) {
              
                      c = getchar();
              
                      if ((c == '\n') || (c == EOF)) {
                          str[i] = 0;
                          return str;
                      }
              
                      str[i] = c;
              
                  } // end of for loop
              
                  str[i] = 0;
              
                  // discard rest of input
                  while ((c = getchar()) && (c != '\n') && (c != EOF));
              
                  return str;
              
              } // end of get_input_from_stdin_and_discard_extra_characters
              
              

              【讨论】:

                【解决方案16】:

                简短、可移植并在 stdio.h 中声明

                stdin = freopen(NULL,"r",stdin);
                

                当标准输入上没有任何内容可以刷新时,不会陷入无限循环,如下面众所周知的行:

                while ((c = getchar()) != '\n' && c != EOF) { }
                

                有点贵,所以不要在需要反复清除缓冲区的程序中使用它。

                从同事那里偷的 :)

                【讨论】:

                • 不适用于 Linux。而是直接使用 termios。 stackoverflow.com/a/23885008/544099
                • 您能否详细说明为什么众所周知的线路可能是无限循环?
                • freopen 存在的全部原因是您无法将其分配给stdin。这是一个宏。
                • ishmael :我们在 Cyber​​ Command 中的所有计算机都是 Linux 并且在它们上运行良好。我在 Debian 和 Fedora 上都对其进行了测试。
                • 所谓的“无限循环”就是等待输入的程序。那不是一回事。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2013-05-17
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多