当输入 char 而不是 int 时 C 无限循环

C infinite loop when char input instead of int

我有一个 C 程序,它应该验证用户输入的是 1 到 8 之间的整数。如果输入整数,它会工作,但当输入字符时,验证循环将永远重复。你能告诉我哪里做错了吗?

#include <stdio.h>

int main(void)
{
  int i;
  int input;
  domainEntry *myDomains = buildDomainDB();

  printf("You have the choice between the"
         " following top domains: 1-EDU, 2-COM"
         ", 3-ORG, 4-GOV, 5-MIL, 6-CN, 7-COM.CN, 8.CAN\n");
  printf("Which one do you want to pick?");
  scanf(" %d", &input);

  if(input < 1 || input > 9)
  {
    do  
    {   
      printf("Invalid input. Please try again.");
      printf("You have the choice between the"
             " following top domains: 1-EDU, 2-COM"
             ", 3-ORG, 4-GOV, 5-MIL, 6-CN, 7-COM.CN, 8.CAN\n");
      printf("Which one do you want to pick?");
      scanf(" %d", &input);

    }while((input < 1) || (input > 8));
  }
}

任何时候接受用户输入时, 必须考虑输入缓冲区中保留的每个字符(此处为 stdin)。由于 scanf 处理 inputmatching 的方式,在使用 scanf(或家族)输入时尤其如此失败。当发生任何一种情况时,没有进一步的字符被读取,并且任何有问题的字符都留在输入缓冲区中未读 - 等待再次咬你您的下一次尝试阅读(如果您在循环中输入,通常会导致无限循环)

此外,您必须了解每个转换说明符如何处理前导空格以及说明符是否使用前导空格(例如numeric 转换说明符和 "%s") 而不是(所有其他,特别是 "%c""%[...]")。

这是面向行函数的主要原因之一,例如fgets(具有足够大的缓冲区)或POSIX getline 推荐用于获取用户输入。两者都读取并包含填充缓冲区中的尾随 '\n'。这完全消耗了该行,消除了输入缓冲区中未读的附加字符的任何不确定性。然后您可以将缓冲区传递给 sscanf 进行解析。这允许独立验证 (1) 读取 ("did I get input?") 和 (2) 来自行 ("did it contain the information I need?").

的信息解析

scanf可以使用,如果使用正确的话。这意味着负责检查returnscanf 每次.您必须处理 三个条件

  1. (return == EOF) 用户通过按 Ctrl+d(或 windows Ctrl+z,但看到CTRL+Z does not generate EOF in Windows 10 (early versions));
  2. (return < expected No. of conversions) 匹配输入 失败。对于 匹配 失败,您必须考虑将留在输入缓冲区中的每个字符。 (在输入缓冲区中向前扫描读取并丢弃字符,直到找到 '\n'EOF);最后
  3. (return == expected No. of conversions) 表示读取成功——然后由您检查输入是否满足任何其他条件(例如正整数、正浮点数、在所需范围内等) .

利用您的代码,您可以通过以下方式获取输入——不断循环直到收到有效输入或用户通过生成手册取消 EOF,例如

#include <stdio.h>

void empty_stdin (void) /* simple helper-function to empty stdin */
{
    int c = getchar();

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

int main(void)
{
    int input = 0,
        rtn = 0;    /* variable to save scanf return */
    // domainEntry *myDomains = buildDomainDB();

    for (;;) {  /* loop continually until valid input or EOF */
        printf ("\nSelect top level domain:\n"
                "  1-EDU\n"
                "  2-COM\n"
                "  3-ORG\n"
                "  4-GOV\n"
                "  5-MIL\n"
                "  6-CN\n"
                "  7-COM.CN\n"
                "  8.CAN\n\n"
                "choice: ");
        rtn = scanf (" %d", &input);    /* save return */

        if (rtn == EOF) {   /* user generates manual EOF */
            fputs ("(user canceled input.)\n", stderr);
            return 1;
        }
        else if (rtn == 0) {    /* matching failure */
            fputs (" error: invalid integer input.\n", stderr);
            empty_stdin();
        }
        else if (input < 1 || 8 < input) {  /* validate range */
            fputs (" error: integer out of range [1-8]\n", stderr);
            empty_stdin();
        }
        else {  /* good input */
            empty_stdin();
            break;
        }
    }

    printf ("\nvalid input: %d\n", input); 
}

(注意 helper-function empty_stdin() 的使用 - 即使在成功读取之后,您仍然应该 empty_stdin() 以确保输入缓冲区为空并为下一个用户输入做好准备——不管是什么)

示例Use/Output

$ ./bin/getintmenu

Select top level domain:
  1-EDU
  2-COM
  3-ORG
  4-GOV
  5-MIL
  6-CN
  7-COM.CN
  8.CAN

choice: edu
 error: invalid integer input.

Select top level domain:
  1-EDU
  2-COM
  3-ORG
  4-GOV
  5-MIL
  6-CN
  7-COM.CN
  8.CAN

choice: 9
 error: integer out of range [1-8]

Select top level domain:
  1-EDU
  2-COM
  3-ORG
  4-GOV
  5-MIL
  6-CN
  7-COM.CN
  8.CAN

choice: 4

valid input: 4

如果你做你的工作,你可以根据需要成功使用scanf

或者,您可以使用 fgets 让您的生活更轻松,然后测试缓冲区中的第一个字符(通过简单地取消引用指针)是否是有效的菜单选择,例如

#include <stdio.h>

#define MAXC 1024   /* read buffer max characters */

int main (void) {

    int input = 0;
    char buf[MAXC];
    // domainEntry *myDomains = buildDomainDB();

    for (;;) {  /* loop continually until valid input or EOF */
        fputs  ("\nSelect top level domain:\n"
                "  1-EDU\n"
                "  2-COM\n"
                "  3-ORG\n"
                "  4-GOV\n"
                "  5-MIL\n"
                "  6-CN\n"
                "  7-COM.CN\n"
                "  8.CAN\n\n"
                "choice: ", stdout);
        if (!fgets (buf, MAXC, stdin)) {
            fputs ("(user canceled input.)\n", stderr);
            return 1;
        }

        if (*buf < '1' || '8' < *buf) { /* check 1st char, validate range */
            fputs (" error: invalid input\n", stderr);
            continue;
        }

        input = *buf - '0';     /* convert char to integer */
        break;
    }

    printf ("\nvalid input: %d\n", input); 
}

当然,如果某个键被卡住并且用户输入的字符超过 1023 个,字符将保留在输入缓冲区中。但是,通过简单测试最后一个字符是否为 '\n',如果不是,是否读取了 MAXC - 1 个字符,您就会知道情况是否如此。您的选择,但 fgets 提供了更简单的实现。请记住——不要吝啬缓冲区大小。我宁愿有一个 10,000 字节太长而不是 1 字节太短的缓冲区....

如果我是你,在这个特定的追逐中,我会检查 ascii table 而不是 "number" 值。检查用户输入的内容是否确实是 ascii table 中从 1 到 9 的数字。从字面上看,在这种情况下解决您的问题。

#include <stdio.h>

int main(void)
{
  int i;
  char input;


  printf("You have the choice between the"
         " following top domains: 1-EDU, 2-COM"
         ", 3-ORG, 4-GOV, 5-MIL, 6-CN, 7-COM.CN, 8.CAN\n");
  printf("Which one do you want to pick?");
  scanf(" %c", &input);

  if(input < '1' || input > '9')
  {
    do  
    {   
      printf("Invalid input. Please try again.");
      printf("You have the choice between the"
             " following top domains: 1-EDU, 2-COM"
             ", 3-ORG, 4-GOV, 5-MIL, 6-CN, 7-COM.CN, 8.CAN\n");
      printf("Which one do you want to pick?");
      scanf(" %d", &input);

    }while((input < '1') || (input > '8'));
  }
}

如第一个解决方案所示,字符保留在缓冲区中,您无法访问新行以接受新值。请在下面找到我的解决方案,我基本上找到了 \n 表示再次调用 scanf 时接受新输入的新行

#include <stdio.h>

int main(void)
{
  int x;
  int result;

  while (1){
    printf("Positive Number: ");
    result = scanf("%d", &x);
    if (result == 0) {
        // find the new line to recieve new input
        while(fgetc(stdin) != '\n');
    }
    if (x>0) {
        break;
    } else {
        printf("Error, please enter a positive number.\n");
    }
 }
}