Странное поведение при чтении в int из STDIN - PullRequest
5 голосов
/ 20 марта 2012

Предположим, у нас есть меню, которое предоставляет пользователю некоторые опции:

Welcome:
1) Do something
2) Do something else
3) Do something cool
4) Quit

Пользователь может нажать 1 - 4, а затем клавишу ввода.Программа выполняет эту операцию, а затем представляет меню обратно пользователю.Недопустимый параметр должен просто снова отобразить меню.

У меня есть следующий метод main():

int main()
{
    while (true)
        switch (menu())
        {
            case 1:
                doSomething();
                break;
            case 2:
                doSomethingElse();
                break;
            case 3:
                doSomethingCool();
                break;
            case 4:
                return 0;
            default:
                continue;
        }
}

и следующий menu():

int menu()
{
  cout << "Welcome:" << endl
       << "1: Do something" << endl
       << "2: Do something else" << endl
       << "3: Do something cool" << endl
       << "4: Quit" << endl;

  int result = 0;
  scanf("%d", &result);
  return result;
}

Ввод числовых типов прекрасно работает.Ввод 1 - 4 заставляет программу выполнить желаемое действие, после чего снова отображается меню.Ввод числа вне этого диапазона, такого как -1 или 12., снова отобразит меню, как и ожидалось.

Однако, введя что-то вроде 'q', вы просто будете бесконечно отображать меню снова и снова, даже не останавливаясьчтобы получить пользовательский ввод.

Я не понимаю, как это могло произойти.Ясно, что menu() вызывается, так как меню отображается снова и снова, однако scanf() является частью menu(), поэтому я не понимаю, как программа попадает в это состояние ошибки, когда пользователю не предлагаетсяих ввод.

Первоначально у меня был cin >> result, который сделал то же самое.

Редактировать: Похоже, связанный вопрос ,однако исходный исходный код исчез из pastebin, и один из ответов ссылается на статью , которая, очевидно, когда-то объясняла, почему это происходит, но теперь является мертвой ссылкой.Может быть, кто-то может ответить, почему это происходит, а не ссылки?:)

Редактировать: Используя этот пример , вот как я решил проблему:

int getNumericalInput()
{
    string input = "";
    int result;

    while (true)
    {
        getline(cin, input);

        stringstream sStr(input);
        if (sStr >> result)
            return result;

        cout << "Invalid Input. Try again: ";
    }
}

и я просто заменил

int result = 0;
scanf("%d", &result);

с

int result = getNumericalInput();

Ответы [ 3 ]

6 голосов
/ 20 марта 2012

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

Есть два основных способа избежать этого.Я предпочитаю прочитать строку данных, затем преобразовать их в число и предпринять соответствующие действия.Обычно вы используете std::getline для чтения всех данных до новой строки, а затем пытаетесь преобразовать их.Так как он будет читать любые ожидающие данные, вы никогда не будете «зависать» во входных данных.

Альтернативой является (особенно, если преобразование не удается) использовать std::ignore для чтения данных из входных данных.(обычно) до следующей новой строки.

4 голосов
/ 20 марта 2012

1) Скажите это себе 1000 раз или пока вы не заснете:


Я никогда не буду использовать функции ввода-вывода без проверки возвращаемого значения.


2) Повторите вышеуказанное 50 раз.

3) Перечитайте код: Проверяете ли вы результат scanf?Что происходит, когда scanf не может преобразовать ввод в желаемый формат?Как бы вы узнали о такой информации, если бы не знали?(На ум приходят четыре буквы.)

Я также хотел бы спросить, почему вы используете scanf вместо более подходящей операции iostreams, но это будет страдать от точно такой же проблемы.

1 голос
/ 20 марта 2012

Вам необходимо проверить, успешно ли выполнено чтение. Подсказка: это не так. Всегда проверяйте после чтения, что вы успешно прочитали ввод:

if (std::cin >> result) { ... }
if (scanf("%d", result) == 1) { ... }

В C ++ сбойное состояние является липким и сохраняется до тех пор, пока не станет clear() ed. Пока поток находится в состоянии сбоя, он не будет ничего полезного. В любом случае вы хотите ignore() плохой персонаж или fgetc() его. Обратите внимание, что сбой может быть вызван достижением конца потока, в этом случае устанавливается eof() или возвращается EOF для iostream или stdio соответственно.

...