如何在fgets循环中正确清空stdin

10

我曾多次在这里看到使用while((c = getchar()) != '\n' && c != EOF)清除stdin的示例,并尝试在通过fgets获取输入的循环中使用它。我需要刷新,因为循环会接收上一个输入的\n字符并再次运行。

现在发生的情况是我需要按两次回车键。为什么会这样,并且我该如何解决?

#define BUFFER_LIMIT 50
do
{
  int c;
  while ((c = getchar()) != '\n' && c != EOF);

  printf("console> ");
  fgets(input_buffer, BUFFER_LIMIT-1, stdin);

  if(do_something(input_buffer))
    break;
} while(strncmp(input_buffer, "quit", 4) != 0);

找到了解决我的问题的方法,而不需要刷新stdin,仍然欢迎回答! - BigBadWolf
4个回答

11

在C语言中,关于“清空输入缓冲区”的概念存在很多混乱。这种混淆几乎完全来自于流行但有缺陷的scanf函数的一个奇怪事实:它通常不会读取完整的输入行,并且通常会在转换其输入后在输入流中保留换行符\n。例如,如果您编写一个程序:

printf("Type a number:\n");
scanf("%d", &n);
如果用户输入"123"并按下回车键,则数字123将被存储在变量n中,但是对应于回车键的字符"\n"将留在输入流中。如果你的程序接下来要做的事情是调用fgets或getchar,想象一下你将开始读取用户输入的下一行,那么你的程序将立即读取剩余的换行符。看起来好像用户多输入了一行空白,或其他什么东西。
这个问题非常普遍。大量的初学者C程序员都会遇到这个问题。简而言之,有三种推荐的解决方法:
1.在调用像scanf这样保留输入缓冲区中换行符的函数之后,在调用期望从新行开始的函数如getchar或fgets之前,使用小循环while((c = getchar()) != '\n' && c != EOF)读取和丢弃scanf留在输入缓冲区中的换行符。 2.完全不使用scanf,或者如果你使用了,就不要尝试将它与调用getchar或fgets混合使用。 3.(普遍但严重有问题的)调用fflush(stdin)刷新不需要的输入。
第三种解决方案的问题已经被广泛讨论,所以我不会再多说那些问题或那个解决方案。推荐的“便携式”替代方法是第一种,但显然它只在至少有一个不需要的换行符在等待刷新时才有效。如果没有,解决方案1将错误地读取并丢弃所需输入的一行。
因此,你不能在程序的任何地方都使用解决方案1。你不能随意地在你的程序中添加它;你不能在可能使用fflush(stdin)的任何地方都使用它。通常情况下,如前所述,只有在调用scanf之后,在调用getchar或fgets之前才需要它。
在问题的代码片段中,可能根本不需要使用解决方案1。fgets函数完全能够干净地读取单独的输入行。不一定需要额外的输入清除或换行符丢弃。(然而,如果在do_something()下面调用了scanf,可能需要额外的换行符处理。)

#1 可以通过编写实际想要执行的操作来轻松解决。您甚至可以通过在转换字符串的末尾添加 %*1[\n] 或类似内容,使 scanf 函数消耗掉换行符。 - R.. GitHub STOP HELPING ICE
在我看来,那个 scanf 技巧完全是自我毁灭的。scanf 只有一个优点,那就是对于初学者在他们的前几个程序中表面上易于使用和理解。但 %*1[\n] 显然与“易于使用和理解”相反! - Steve Summit
我很高兴我的旧问题仍然受到关注!如果您有时间,也许您可以编辑您的答案,并附上一个代码片段,展示如何正确地完成#1? - BigBadWolf

8
所以现在我必须按两次回车键。为什么会这样,我该如何解决?
嗯,这就是你的代码做的事情 - 它首先逐个字符读取,直到找到换行符。然后它调用fgets()函数,该函数将...好吧,读取直到找到换行符(可能是逐个字符,但也可能以其他方式)。
你可以尝试使用fflush(stdin),但不能保证它会做你想要的事情(它只对输出缓冲区提供保证,而不是输入缓冲区)。
此外,你可以尝试setbuf(stdin, NULL),它应该禁用标准输入的缓冲,所以不需要刷新任何内容。我在不同的系统上尝试过几次,都能正常工作,但该函数的文档并不完全清楚。

谢谢。不过fflush(stdin)并不是很可移植,对吧?而setbuf(stdin, NULL)似乎是个好的解决方案!你能想到任何可能出错的地方吗?(除了它不是100%清晰) - BigBadWolf
1
@BigBadWolf 如果你不写可移植代码,那么尝试fflush(stdin)是值得一试的,它可能适用于你的平台。至于setbuf(stdin, NULL),我认为没有什么问题,但是由于其文档不清晰,有些实现可能不会按照你想要的方式执行。总的来说,我会尝试两种方法,希望其中一种能够奏效。 :) - srdjan.veljkovic
非常感谢!我学习C语言的主要原因是为了工程方面,因此可移植性和文档具有很高的优先级。但是,在任何嵌入式系统上,我可能不需要输入循环 :) - BigBadWolf
晚来的评论:fflush(stdin)当然不是清除输入的好方法,也不是通常推荐的方法,而且搞乱了setbuf更糟糕。对于原始问题的正确答案是,原始问题本身就有缺陷:在仅调用fgets的循环中,没有必要刷新任何内容。问题中的代码可能是在将不必要且无效的fflush(stdin)调用替换为“更便携”的显式getchar循环时产生的。 - Steve Summit

1
我的简单解决方法是创建一个缓冲字符串char clear[2],并且每当我使用scanf时,通过使用gets(clear)将换行符写入其中来清空输入缓冲区。

不要使用 gets,它已经不再是标准的一部分了。 - David Ranieri
那么使用 fgets 做同样的事情会比较合适吗? - gr33nd00r
是的,fgets是正确的函数。 - David Ranieri

0

使用@gr33nd00r的答案结合fgets()函数。

#define BUFFER_LIMIT 50
do
{
  char input_buffer[BUFFER_LIMIT], clear[2];

  printf("console> ");
  fgets(input_buffer, BUFFER_LIMIT-1, stdin);
  while (strchr(input_buffer, '\n') == NULL && clear[0] != '\n')
      fgets(clear, 2, stdin);
  clear[0] = 0;

  if(do_something(input_buffer))
    break;
} while(strncmp(input_buffer, "quit", 4) != 0);

在 while 循环中(第一个 fgets() 之后),我首先检查输入是否没有换行符(这意味着输入超过了输入长度)。然后我逐个字符地删除,直到它达到一个新行。

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