【问题标题】:Do..While loop doesn't stop to wait for uinput [duplicate]做..虽然循环不会停止等待uinput [重复]
【发布时间】:2020-12-11 11:03:40
【问题描述】:

我正在实现一个带有基本输入有效性检查机制的超级简单的菜单选择程序。合法输入是 {1,2,3},因此可能的问题是超出此范围的数字或非整数。我的代码如下所示。这适用于前一个问题(即当我输入“4”时)但对于后者(当我尝试输入一个字符时),它会一遍又一遍地打印无效消息而不是等待新的输入,就像它一样除了第一次之外,每次迭代都完全跳过 scanf 行。我在这里错过了什么?

do{
      try = scanf("%d", &selection);
      if(try!=1 || selection < 1 || selection > 3){
           printf("\nInvalid input. Dear guest, please enter '1', '2', or '3'.\n\nInput:");
      }
 }while(try!=1 || selection < 1 || selection > 3);

【问题讨论】:

  • 当 scanf 失败时(try != 1),您必须在重试之前读取待处理的输入。 scanf 尝试读取一个数字,第一个非数字字符失败,并将该字符放回输入流中, 下一次迭代将再次遇到该字符。支持检查 scanf 的返回值 :-)。
  • 您确定可以使用try 作为变量名吗? (它是try ... catch 表达式的一部分)我建议你给它另一个名字。
  • @Dominique C,而不是 C++。但无论如何这可能是个坏主意,因为以后可能想迁移到 C++。
  • @Peter-ReinstateMonica:我写评论时考虑到要迁移到 C++。
  • 远离scanf。它并不真正适合交互式输入。用户键入整行,因此您希望为此做好准备。调用fgets,然后解析该行(使用sscanfstrtol 或其他任何东西)。您可能还想尝试使用 GNU readline 或类似的库来替换 fgets

标签: c loops while-loop scanf infinite-loop


【解决方案1】:

在输入一个不能作为十进制数的文本表示的一部分的字符后,比如“A”,程序可用的输入是字节序列“A”“\n”(后者是换行符),或者可能是 Windows 上的 'A' '\r' '\n' (回车后跟换行符)。

当 scanf 试图从这些字符中解析一个数字时,它已经在 'A' 处犹豫了并将其放回输入流中。 C 中的输入流保证您至少可以执行一个 @ 987654321@,即至少将一个字符放回流中,这样它将成为下一个输入操作读取的第一个字符。这个简单而巧妙的工具使处理变量格式输入变得很多:想象一下你在一些 C 源代码中解析一个表达式,并且在语法上允许整数文字或变量名作为下一个标记: 你可以先试试这个数字,如果失败了,输入仍然包含所有要处理的变量名。将第一个失败字符“记住”并使其可用于程序的其他部分的工作封装在 FILE 实现中。

这就是这里发生的事情。第一次失败的 scanf() 将“A”放回原处,以便下一次尝试再次遇到它,ad infinitum。必须从输入中删除“A”。更具体地说,应该从输入中完全删除下一个“单词”:用户可能输入了“kkjkllkjlk”,而您不希望为此出现 10 条错误消息。您可以决定是否接受“lklkj2”(并阅读 2),但丢弃整个单词更简单。

您还可以决定是否接受“1 2 3 2”作为 4 个有效的连续输入,或者是否要求数字之间有换行符;为了一般性(例如,如果输入不是来自终端),我将接受由任何类型的空格分隔的所有数字序列。在这种情况下,您只需阅读下一个空格:

#include <ctype.h>
// ...
while(!isspace(getchar())) { /* ignore */ }

这应该可以解决问题。可能会有更多的空格在后面,包括换行符等,但没关系:scanf 的符号输入转换(如 %d)跳过前导空格。

我认为让用户通过结束输入来退出程序很巧妙(通过在 Windows 控制台中按 Ctrl-z 或在 Posix 终端上按 Ctrl-d 来插入文件结尾),所以我会测试对于 scanf() 返回 EOF 的特殊情况。如果输入来自实际上可能必不可少的管道。即使在丢弃错误输入的代码中,错误输入紧跟 EOF 的边缘情况也需要检查 EOF(Posix:echo -n "a" | myprog 会挂起;-n 会抑制 echo 通常附加的常用换行符)。综上所述,我对输入循环的看法是这样的:

while(1) { // break on good input
      printf("Please enter your choice of 1, 2 or 3:\n");
      try = scanf("%d", &selection);
      if(try!=1 || selection < 1 || selection > 3){
          if(try == EOF)
          {
              return 0;
          }
           printf("\nThe input was not 1,2 or 3. Please try again.\n");
           int discard;
           {
               do{
                   discard = getchar();
                   if(discard == EOF) { return 0;} // catches EOF after bad char
               }while(!isspace(discard));
           }
      }
      else break;
}

【讨论】:

  • 哇 - 令人难以置信的答案!非常感谢细节,学到的东西比我想象的要多:)
  • @Qman Nice :-)。当我彻底解释看似简单的问题时,我每次都在学习一些东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-16
  • 2014-06-06
  • 2013-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多