如何在C语言中清空输入缓冲区?

114

我有以下的程式:

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键(ASCII码13)和\n(ASCII码10)。因此,在第2行中,它将读取\n,而不会等待用户输入字符。

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

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

  1. 使用fflush()

  2. 编写一个像这样的函数:

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

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


另请参见:[我无法刷新标准输入。 我怎样才能在C中刷新stdin?] (https://dev59.com/O3I95IYBdhLWcg3wtwVe) - Gabriel Staples
1
要明确的是,“Enter键”不是一个字符。你所说的“Enter键”实际上是回车符(\r)。当你按下Enter键时,根据你的系统,它可能会发出\r(旧版Mac系统),\r\n(Windows)或\n(几乎所有其他操作系统,包括OSX/macOS)。已经有答案解释了C运行时如何在文本模式流中将Windows的\r\n折叠成\n - ShadowRanger
20个回答

0

我已经编写了一个函数(并进行了测试),它从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


0
快速解决方案是添加第二个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;
}

0
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-或者-

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


问题是关于 C 语言,不是 C++。 - Spikatrix
1
请注意,kbhit()getch()是在<conio.h>中声明的函数,仅在Windows上作为标准功能可用。它们可以在类Unix机器上模拟,但这样做需要一些小心。 - Jonathan Leffler

0

请尝试以下代码:

stdin->_IO_read_ptr = stdin->_IO_read_end;


6
请增加一些关于这个解决方案是如何工作的以及它是如何有效地解决问题的细节。 - Marcello B.

0

只是为了完整起见,如果您实际上想使用scanf(有很多原因不这样做),我会在格式说明符前面添加一个空格,告诉scanf忽略字符前面的所有空格:

scanf(" %c", &ch1);

更多细节请参阅此处:https://en.cppreference.com/w/c/io/fscanf#Notes


0

简而言之,将该行代码...

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
首先,在读取输入输出流时,你应该使用 int 类型的变量而不是 char 类型(至少在 Linux 中)。
其次,在 Linux 中,第一次读取后只剩下 '\n' 在 stdin 中(在 Windows 中不同)。因此,在你的示例中,你可以使用一个额外的变量来清除缓冲区。例如,你的代码可以像下面这样:
int cr;
cr = fgetc(stdin);

0
以下内容适用于Linux操作系统。
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
while(getchar() != EOF)
  ;

-1

短小、便携且在stdio.h中声明

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

当标准输入流中没有需要清空的内容时,不会像以下众所周知的行一样陷入无限循环:

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

价格有点贵,所以不要在需要重复清空缓冲区的程序中使用它。

从同事那里偷了个懒 :)


不适用于Linux。直接使用termios代替。https://dev59.com/o2Qm5IYBdhLWcg3w0Bq5#23885008 - ishmael
2
你能详细说明为什么这个广为人知的代码行可能会导致无限循环吗? - alx - recommends codidact
1
freopen 存在的主要原因是你无法可移植地分配给 stdin。它是一个宏。 - melpomene
1
ishmael:我们在网络指挥部的所有计算机都是Linux系统,而且它们运行良好。我已经在Debian和Fedora上进行了测试。 - Jason Enochs
你所谓的“无限循环”是程序在等待输入,这并不是同一件事情。 - jamesdlin
@JasonEnochs 在我们网络指挥部门,所有的电脑都是Linux系统,而且运行良好。仅仅依据“我还没有发现它失败”的标准来编写代码是非常低的。http://port70.net/~nsz/c/c11/n1570.html#note272 “freopen函数的主要用途是更改与标准文本流(stderr、stdin或stdout)相关联的文件,因为这些标识符不需要是可修改的lvalue,可以将fopen函数返回的值分配给它们。” - Andrew Henle

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

3
如果stdin被重定向到文件,那将完全破坏程序。而对于交互式输入,则无法保证其会做任何事情。 - melpomene

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