如何清除 C 语言中的输入缓冲区?

How can I clear an input buffer in C?

提问人:ipkiss 提问时间:10/26/2011 最后编辑:digito_evoipkiss 更新时间:9/12/2023 访问量:486146

问:

我有以下程序:

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 行,当用户按下时,它将在输入缓冲区中留下 2 个字符:键(ASCII 代码 13)和(ASCII 代码 10)。因此,在第 2 行中,它将读取 并且不会等待用户输入字符。EnterEnter\n\n

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

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

  1. fflush()

  2. 编写如下函数:

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

这段代码实际上有效。但我无法解释它是如何工作的。因为在 while 语句中,我们使用 ,这意味着读取除 ?如果是这样,字符是否仍保留在输入缓冲区中?getchar() != '\n''\n''\n'

C IO 缓冲区 stdin 刷新

评论

0赞 Gabriel Staples 4/10/2022
Смотритетакже: 我无法冲洗 stdin。如何在 C 中刷新 stdin?
1赞 ShadowRanger 11/17/2022
需要明确的是,“Enter 键”不是一个字符。您所说的“Enter 键”是回车符 ()。当您按 Enter 键时,根据您的系统,它可能会发出( Mac)、(Windows) 或 (几乎所有其他操作系统,包括 OSX/macOS)。答案已经涵盖了 C 运行时如何将 Windows 折叠为仅针对文本模式流。\r\r\r\n\n\r\n\n

答:

10赞 zwol 10/26/2011 #1

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

您可能没有意识到的是,比较是在从输入缓冲区中删除字符之后发生的。所以当你到达 时,它被消耗掉了,然后你就跳出了这个循环。getchar()'\n'

13赞 Dmitri 10/26/2011 #2

这些行:

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

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

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

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

评论

0赞 chux - Reinstate Monica 9/10/2015
注意:由于文件结束,应该返回无限循环。while ( getchar() != '\n' );getchar()EOF
0赞 Dmitri 9/11/2015
要检查EOF,也可以使用int ch; while ((ch = getchar()) != '\n' && ch != EOF);
127赞 jamesdlin 10/26/2011 #3

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

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

如果键入一个字符并按 Enter,则该输入字符将按第 1 行读取,然后由第 2 行读取。请参阅我正在使用 scanf %c 读取 Y/N 响应,但后来的输入被跳过。 来自 comp.lang.c 常见问题解答。'\n'

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

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

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

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

另外,我觉得有义务劝阻你不要完全使用:为什么每个人都说不要使用 scanf?我应该用什么来代替?scanf

评论

1赞 ishmael 2/25/2019
这将挂起,等待用户按 Enter。如果只想清除 stdin,请直接使用 termios。stackoverflow.com/a/23885008/544099
1赞 jamesdlin 2/25/2019
@ishmael 您的评论没有意义,因为不需要按 Enter 键即可清除 stdin;首先需要获取输入。(这是在 C 语言中读取输入的唯一标准方法。如果您希望能够立即读取密钥,则必须使用非标准库。
2赞 ishmael 2/26/2019
@jamesdlin 在 Linux 上,getchar() 将等待用户按 Enter。只有这样,他们才会跳出while循环。据我所知,termios 是一个标准库。
2赞 jamesdlin 2/26/2019
@ishmael 不,你在这两个方面都错了。这个问题是关于如何在读取输入从 stdin 中清除剩余的输入,在标准 C 中,前提是输入首先需要键入一行并按 Enter。 输入该行,将读取并返回这些字符;用户无需再按 Enter 键。此外,虽然 termios 在 Linux 上可能很常见,但它不是 C 标准库的一部分。getchar()
0赞 Cătălina Sîrbu 9/30/2020
仅仅通过添加空格 befor %c 说明符来工作和忽略换行符的原因是什么?scanf(" %c", &char_var)
53赞 Ramy Al Zuhouri 3/17/2012 #4

您也可以通过以下方式进行操作:

fseek(stdin,0,SEEK_END);

评论

0赞 ikh 5/22/2014
哇。。。顺便说一句,标准说是否可用于?fseekstdin
4赞 Ramy Al Zuhouri 5/22/2014
我不知道,我认为它没有提到它,但 stdin 是 FILE* 并且 fseek 接受 FILE* 参数。我测试了它,它适用于 Mac OS X,但不适用于 Linux。
0赞 EvAlex 10/28/2015
请解释。如果作者能写出这种方法的含义,那将是一个很好的答案。有什么问题吗?
12赞 Jonathan Leffler 9/15/2016
@EvAlex:这有很多问题。如果它适用于您的系统,那就太好了;如果不是,那么也就不足为奇了,因为当标准输入是交互式设备(或不可搜索的设备,如管道、插座或先进先出等)时,没有什么能保证它会起作用,仅举几例其他可能失败的方式。
0赞 Rajesh 3/3/2017
为什么要SEEK_END?我们可以用SEEK_SET来代替吗?FP stdin 表现如何?
0赞 Michael Grieswald 8/19/2014 #5
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-或-

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

评论

0赞 Spikatrix 10/12/2015
问题是关于C,而不是C++。
1赞 Jonathan Leffler 9/15/2016
请注意,和 是在 Windows 中声明的函数,并且仅在 Windows 上作为标准提供。它们可以在类 Unix 机器上模拟,但这样做需要一些小心。kbhit()getch()<conio.h>
19赞 M.M 9/28/2014 #6

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

int c;

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

这将读取并丢弃字符,直到它得到文件结束的信号。它还会检查输入流是否在行尾之前关闭。的类型必须为(或更大)才能保存值。\nEOFcintEOF

没有可移植的方法可以找出当前行之后是否还有更多行(如果没有,则将阻止输入)。getchar

评论

0赞 Rajesh 3/3/2017
为什么 while((c=getchar())!=EOF);不起作用?这是无限循环。无法理解EOF在stdin中的位置。
1赞 M.M 3/4/2017
@Rajesh 在输入流关闭之前,这将清除,如果以后有更多输入,则不会
0赞 Jason Enochs 7/14/2017
@Rajesh 是的,你是对的。如果 stdin 上没有任何东西可以刷新,当它被调用时,它将阻塞(挂起),因为它被捕获在一个无限循环中。
2赞 Paul_Pedant 3/19/2022
@AbhishekMane 但是一个字节是 8 位,而 256 需要 9 位。如果将 getchar() 中的结果分配给 char,则无法处理任何 8 位值以及一个额外的标志值。此外,具有 .C 有 char、signed char、unsigned char,char 可能在 0..255 或 -128..127 中,具体取决于体系结构。 非常明确:“fgetc() 从流中读取下一个字符,并将其作为无符号字符强制转换为 int 或文件末尾的 EOF 或错误。所以 (int) -1 或 (int) 0..255。将其塞入 char 变量会丢弃基本信息。stdio.h#define EOF (-1)man getchar
1赞 Abhishek Mane 3/22/2022
@Paul_Pedant明白了。谢谢。 不是性格。 returns 要处理此问题,唯一的返回类型是 。所以一定是对的吗?EOFgetchar()char(0,1,2,...255) + EOFEOFgetchar(_)intCint
6赞 kapil 4/12/2015 #7

你可以试试

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

其中 %*c 接受并忽略换行符。

还有一种方法

您可以编写

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

不要忘记 while 循环后面的分号。

评论

4赞 chux - Reinstate Monica 9/10/2015
1)扫描任何字符(并且不保存它),无论是换行符还是其他字符。代码依赖于第二个字符是换行符。2)是一个无限循环,由于文件结束而应该返回。"%*c"while((getchar())!='\n');getchar()EOF
1赞 kapil 9/11/2015
第二种方法取决于换行符、空字符、EOF等条件(上面是换行符)
1赞 antadlp 4/20/2017 #8

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

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 几乎没有新手。

希望它对某人有所帮助

评论

0赞 veganaiZe 7/29/2017
您应该在循环中处理您的“预期”字符。循环后,您可以认为是干净/清晰的,并将按住 '' 或 . 当不存在换行符并且缓冲区已耗尽时返回(如果输入在按下之前会溢出缓冲区)。-- 你有效地使用了while((ch=getchar())!='\n'&&ch!=EOF); 来咀嚼 stdin 输入缓冲区中的每个字符。whilestdiny\nEOFEOF[ENTER]
-1赞 Jason Enochs 7/14/2017 #9

简短、便携并在 stdio.h 中声明

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

当 stdin 上没有任何东西可以刷新时,不会像以下众所周知的行一样刷新,从而不会挂在无限循环中:

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

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

从同事那里偷来的:)

评论

0赞 ishmael 2/25/2019
在 Linux 上不起作用。请直接使用 termios。stackoverflow.com/a/23885008/544099
2赞 alx - recommends codidact 4/3/2019
您能详细说明为什么众所周知的线可能是一个无限循环吗?
1赞 melpomene 7/29/2019
存在的全部原因是您无法可移植地分配给 .这是一个宏。freopenstdin
1赞 Jason Enochs 7/30/2019
伊斯梅尔 :我们在 Cyber Command 中的所有计算机都是 Linux,它可以在它们上运行良好。我在 Debian 和 Fedora 上都测试了它。
0赞 jamesdlin 9/30/2020
你所说的“无限循环”是等待输入的程序。那不是一回事。
-1赞 James Blue 3/26/2018 #10

另一个尚未提到的解决方案是使用: 倒带(stdin);

评论

3赞 melpomene 7/29/2019
如果将 stdin 重定向到文件,这将完全破坏程序。对于交互式输入,它不能保证做任何事情。
0赞 Inquisitions 7/26/2020 #11

试试这个:

stdin->_IO_read_ptr = stdin->_IO_read_end;

评论

6赞 Marcello B. 7/26/2020
请添加有关此解决方案如何工作以及它如何成为解决问题的有用方法的更多详细信息 stackoverflow.com/help/how-to-answer
4赞 nowox 11/2/2020 #12

我很惊讶没有人提到这一点:

scanf("%*[^\n]");
0赞 Dmitry Somov 7/21/2021 #13

简而言之。把线...

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的答案是最全面的。

0赞 Matt 10/15/2021 #14

快速解决方案是添加第二个扫描,以便它迫使 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;
}
8赞 Steve Summit 10/15/2021 #15

scanf是一个奇怪的功能,电影《战争游戏》中有一句经典台词是相关的:“唯一的制胜之举就是不玩”。

如果你发现自己需要“刷新输入”,你就已经输了。制胜之举不是拼命寻找某种神奇的方法来刷新荨麻输入:相反,你需要做的是以某种不同的(更好的)方式进行输入,这不涉及在输入流上留下未读的输入,并让它坐在那里并引起问题,因此你必须尝试刷新它。

基本上有三种情况:

  1. 您正在使用 读取输入,并且它将用户的换行符保留在输入流上,并且该杂散换行符被稍后对 或 的调用错误地读取。(这是您最初询问的情况。scanfgetcharfgets

  2. 您正在使用 读取输入,并且它将用户的换行符保留在输入流上,并且该杂散换行符被稍后调用 错误地读取。scanfscanf("%c")

  3. 您正在使用 读取数字输入,并且用户正在键入非数字文本,并且非数字文本留在输入流中,这意味着下一次调用也会失败。scanfscanf

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

在情况 1 中,更好的解决方案是,不要将对 scanf 的调用与其他输入函数混合使用。要么用 做所有输入,要么用 和/或做所有输入。要用 进行所有输入,可以将对 的调用替换为 — 但请参阅第 2 点。从理论上讲,您可以将调用替换为 ,尽管这有各种进一步的问题,我不建议这样做。即使您想要/需要一些 的解析,也要完成所有输入,您可以使用读取行,然后在事后使用 .scanfgetcharfgetsscanfgetcharscanf("%c")fgetsscanf("%[^\n]%*c")fgetsscanffgetssscanf

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

在案例 3 中,恐怕根本没有好的解决方案。 有很多问题,它的众多问题之一就是它的错误处理很糟糕。如果你想编写一个读取数字输入的简单程序,并且如果你可以假设用户在出现提示时总是会输入正确的数字输入,那么可以是一个足够的——勉强足够的——输入法。但也许你的目标是做得更好。也许您想提示用户输入一些数字输入,并检查用户是否确实输入了数字输入,如果没有,请打印错误消息并要求用户重试。在这种情况下,我相信无论出于何种意图和目的,您都无法基于 scanf 实现此目标。你可以尝试,但这就像给一个蠕动的婴儿穿上连体衣:在把两条腿和一只胳膊伸进去后,当你试图把第二只胳膊伸进去时,一条腿会蠕动出来。尝试使用 读取和验证可能不正确的数字输入实在是太费力了。以其他方式做到这一点要容易得多。scanfscanf("%d")scanf

你会注意到一个贯穿我列出的所有三个案例的主题:它们都以“您正在使用...”阅读输入开始。这里有一个不可避免的结论。请参阅另一个问题:我可以使用什么来代替 scanf 进行输入转换?scanf

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

0赞 user16083509 10/30/2021 #16

我编写了一个函数(并对其进行了测试),该函数从 stdin 获取输入并丢弃额外的输入(字符)。此函数称为 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

2赞 Gabriel Staples 4/10/2022 #17

如何在 C 语言中刷新或清除输入缓冲区?stdin

cppreference.com 社区 wiki 上的 fflush() 引用指出(强调后加):

对于输入流(以及输入最后一个操作的更新流),行为是未定义的。

所以,不要在 上使用 。fflush()stdin

如果你的“刷新”目标是删除缓冲区中的所有字符,那么最好的方法是手动使用 getchar()getc(stdin)(同样的事情),或者如果使用 POSIX 或 Linux,则使用 read()(使用 stdin 作为第一个参数)。stdinstdin

这里这里投票最多的答案都是这样做的:

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

我认为更清晰(更易读)的方法是这样做。当然,我的评论使我的方法看起来比实际要长得多:

/// Clear the stdin input stream by reading and discarding all incoming chars up
/// to and including the Enter key's newline ('\n') char. Once we hit the
/// newline char, stop calling `getc()`, as calls to `getc()` beyond that will
/// block again, waiting for more user input.
/// - I copied this function
///   from "eRCaGuy_hello_world/c/read_stdin_getc_until_only_enter_key.c".
void clear_stdin()
{
    // keep reading 1 more char as long as the end of the stream, indicated by
    // `EOF` (end of file), and the end of the line, indicated by the newline
    // char inserted into the stream when you pressed Enter, have NOT been
    // reached
    while (true)
    {
        int c = getc(stdin);
        if (c == EOF || c == '\n')
        {
            break;
        }
    }
}

例如,我在这里的两个文件中使用了这个函数。请参阅以下文件中的上下文,了解何时清除可能最有用:stdin

  1. array_2d_fill_from_user_input_scanf_and_getc.c
  2. read_stdin_getc_until_only_enter_key.c

自我注意:我最初在这里发布了这个答案,但后来删除了这个答案,将这个答案作为我唯一的答案。

0赞 Tommy Andersen 11/18/2022 #18

为了完整起见,在你的情况下,如果你真的想使用,有很多理由不这样做,我会在格式说明符前面添加一个空格,告诉忽略字符前面的所有空格:scanfscanf

scanf(" %c", &ch1);

在此处查看更多详细信息:https://en.cppreference.com/w/c/io/fscanf#Notes

0赞 Owl 8/26/2023 #19

首先,您应该使用 int 类型的变量而不是 char 来读取输入输出流(至少在 Linux 中)。

其次,在 Linux 中第一次读取后,stdin 中只剩下 '\n'(在 Windows 中有所不同)。因此,您可以在示例中首次读取后使用额外的变量来清除缓冲区。例如,您的代码可以如下所示:

int cr;
cr = fgetc(stdin);
0赞 Matheus Garcia 9/12/2023 #20

以下操作在 Linux 上执行。

fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
while(getchar() != EOF)
  ;