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


84

我有以下程序:

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字符?

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

  1. 使用 fflush()

  2. 编写这样的函数:

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

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

Answers:


89

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

您在第二行看到的行为是正确的,但这并不是正确的解释。使用文本模式流,平台使用什么行尾都没有关系(回车符(0x0D)+换行符(0x0A),裸CR还是LF)。C运行时库将为您解决这些问题:您的程序将仅看到'\n'换行符。

如果您键入一个字符并按Enter键,那么该输入字符将由第1行读取,然后'\n'由第2行读取。请参阅我正在scanf %c读取Y / N响应,但稍后将跳过输入。从comp.lang.c常见问题解答中获取。

至于建议的解决方案,请参阅(同样来自comp.lang.c FAQ):

基本上说,唯一可移植的方法是:

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

你的getchar() != '\n'循环工作,因为一旦你打电话getchar(),返回的字符已经已经从输入流中删除。

另外,我有义务阻止您scanf完全使用:为什么每个人都说不使用scanf?我应该怎么用呢?


1
这将挂起,等待用户按Enter。如果您只想清除标准输入,请直接使用termios。 stackoverflow.com/a/23885008/544099
ishmael

@ishmael您的评论没有任何意义,因为不需要按Enter即可清除stdin。首先需要获得输入。(而且这是在C语言中读取输入的唯一标准方法。如果您希望能够立即读取键,则必须使用非标准库。)
jamesdlin

@jamesdlin在Linux上,getchar()将等待用户按Enter。只有这样,它们才能脱离while循环。据我所知,termios是一个标准库。
伊士玛尔

@ishmael不,你在两个方面都错了。这个问题是关于如何读取输入之后以及在标准C语言中从stdin清除其余输入的情况,提供该输入首先需要输入一行并按Enter。 输入该行getchar()将读取并返回这些字符;用户无需再按Enter键。此外,尽管termios在Linux上可能很常见,但它不是C标准库的一部分
jamesdlin

scanf(" %c", &char_var)仅通过在%c说明符之前添加空格来工作并忽略换行符的原因是什么?
卡特琳娜瑟尔布

44

您也可以这样进行:

fseek(stdin,0,SEEK_END);

哇...顺便说一句,标准说fseek适用于stdin吗?
2014年

3
我不知道,我认为它没有提到它,但是stdin是FILE *,而fseek接受FILE *参数。我对其进行了测试,它可以在Mac OS X上运行,但不能在Linux上运行。
拉米·阿·祖瓦里

请解释。如果作者写出这种方法的含义,那将是一个很好的答案。有什么问题吗?
EvAlex

7
@EvAlex:这有很多问题。如果它在您的系统上有效,那就太好了;如果不是,那么就不足为奇了,因为当标准输入是交互式设备(或管道,套接字或FIFO等不可搜索的设备)时,没有任何东西可以保证它能正常工作。失败)。
乔纳森·莱夫勒

为什么应该是SEEK_END?我们可以改用SEEK_SET吗?FP stdin的行为如何?
Rajesh

11

清除已经尝试部分阅读的行尾的一种便携式方法是:

int c;

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

这将读取并丢弃字符,直到获得字符为止,\n这标志着文件结束。它还会检查EOF输入流在行尾之前关闭的情况。的类型c必须是int(或更大)才能容纳该值EOF

没有一种便携式方法来确定当前行之后是否还有其他行(如果没有,getchar则将阻塞输入)。


为什么while((c = getchar())!= EOF); 不起作用?这是无尽的循环。无法了解EOF在stdin中的位置。
Rajesh

@Rajesh这将一直清除,直到输入流关闭为止,如果以后再有更多输入,则不会出现
MM

@Rajesh是的,您是正确的。如果在调用stdin时没有任何要冲洗的东西,则由于陷入无限循环而将阻塞(挂起)。
杰森·伊诺克斯

11

这些行:

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

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

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

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


注意: 由于文件结尾,while ( getchar() != '\n' );应该getchar()返回一个无限循环EOF
chux-恢复莫妮卡2015年

要检查EOF,int ch; while ((ch = getchar()) != '\n' && ch != EOF);也可以使用
-Dmitri

8

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

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


6

你可以试试

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

%* c接受并忽略换行符的位置

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

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

while循环后不要忘记分号


2
1)"%*c"扫描任何字符(并且不保存它),无论是换行符还是其他字符。代码依赖于第二个字符是换行符。2)while((getchar())!='\n');是一个无限循环,应由于文件结尾而getchar()返回EOF
chux-恢复莫妮卡2015年

1
第二种方法取决于换行符,空字符,EOF等条件(在换行符之上)
kapil 2015年

1

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

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。循环之后,您可以考虑stdin保持干净/清晰y并保持' \n'或EOFEOF当不存在换行符并且缓冲区已耗尽时(如果输入在[ENTER]按下之前输入将使缓冲区溢出),返回。 -您可以有效地使用while((ch=getchar())!='\n'&&ch!=EOF);来补足stdin输入缓冲区中的每个字符。
veganaiZe

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中声明的功能,并且仅在Windows上作为标准功能可用。可以在类似Unix的计算机上模拟它们,但是这样做需要一定注意。
乔纳森·莱夫勒

0

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


2
如果将stdin重定向到文件,则将完全破坏程序。对于交互式输入,不能保证做任何事情。
melpomene

0

我很惊讶没有人提到此:

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

-1

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

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

当stdin上没有要刷新的内容(如以下众所周知的行)时,不会陷入无限循环中:

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

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

从同事那里偷走了:)


在Linux上不起作用。直接使用termios。 stackoverflow.com/a/23885008/544099
ishmael

2
您能否详细说明为什么众所周知的线是可能的无限循环?
Cacahuete Frito

freopen存在的全部原因是您无法将其分配给stdin。这是一个宏。
melpomene

1
ishmael:Cyber​​ Command中我们所有的计算机都是Linux,并且可以在它们上正常运行。我在Debian和Fedora上都进行了测试。
詹森·埃诺克斯

您所谓的“无限循环”是等待输入的程序。那不是同一回事。
jamesdlin

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.