C бесконечный цикл при вводе символов вместо int - PullRequest
0 голосов
/ 30 октября 2018

У меня есть программа на 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));
  }
}

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Если бы я был вами, в этой конкретной погоне я бы вместо таблицы «число» проверил таблицу ascii. Проверка того, что вводит пользователь, это буквально число от 1 до 9 в таблице ascii. Буквально решаем вашу проблему в этом случае.

#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'));
  }
}
0 голосов
/ 30 октября 2018

Каждый раз, когда вы принимаете пользовательский ввод, вы должны учитывать каждый символ, который остается во входном буфере (stdin здесь). Это особенно верно при приеме ввода с scanf (или семейством) из-за того, что scanf обрабатывает input или , соответствующие сбоям. В любом случае больше не читаются никакие символы , и любые некорректные символы остаются во входном буфере непрочитано - просто ожидаю, что вас снова укусят при следующей попытке чтения (обычно это приводит к бесконечный цикл, если вы принимаете входные данные внутри цикла)

Кроме того, вы должны понимать, как каждый спецификатор преобразования обрабатывает начальный пробел и используется ли начальный пробел указателем (например, числовой спецификатор преобразования и "%s") а какие нет (все остальные, особенно "%c" и "%[...]").

Это одна из основных причин, по которой для получения пользовательского ввода рекомендуется использовать линейно-ориентированную функцию , такую ​​как fgets (с буфером соответствующего размера) или POSIX getline. И прочитайте и включите завершающий '\n' в заполненный буфер. Это полностью использует строку, устраняя любую неопределенность, какие дополнительные символы остаются во входном буфере непрочитанными Затем вы можете передать буфер в sscanf для анализа. Это позволяет независимо проверять как (1) чтение («я получил ввод?»), Так и (2) анализ информации из строки («содержит ли она информацию, которая мне нужна?»).

scanf можно использовать, если используется правильно. Это означает, что вы несете ответственность за проверку возврата из scanf каждый раз . Вы должны справиться с тремя условиями

  1. (return == EOF) пользователь отменил ввод, сгенерировав руководство EOF, нажав Ctrl + d (или в windows Ctrl + z , но см. CTRL + Z не генерирует EOF в Windows 10 (ранние версии) );
  2. (return < expected No. of conversions) a соответствие или вход произошел сбой. Для сбоя match вы должны учитывать каждый символ, который останется в вашем входном буфере. (сканирование вперед во входном буфере с чтением и отбрасыванием символов до тех пор, пока не будет найдено '\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); 
}

( примечание об использовании вспомогательной функции empty_stdin() - и что даже после успешного чтения вы все равно должны empty_stdin() убедиться, что входной буфер пуст и подготовлен для следующего пользователя вход - что бы это ни было)

Пример использования / вывода

$ ./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-байтовый слишком короткий ....

...