C cin int error

#include; int main() { int a = 1; int b = 2; std::cin >> a >> b; std::cout << a << "+" << b << "=" << a+b << std::en...
#include<iostream>;

int main()
{
    int a = 1;
    int b = 2;
    std::cin >> a >> b;
    std::cout << a << "+" << b << "=" << a+b << std::endl;
    return 0;
}

when I enter 3 4 as input,the output will be 3+4=7,well,it’s strange;
But when I enter a b,the output is 0+0=0(Why it is 0 and 0?);
The most confusing,a 4,it will be 0+0=0(Why not ‘0+4=4’?????);
Then i write another prog.

#include<iostream>;

int main()
{
    int a = 1;
    int b = 2;
    std::cin >> a;
    std::cin.clear();
    std::cin >> b;
    std::cout << a << "+" << b << "=" << a+b << std::endl;
    return 0;
}

When i enter a 4,why is it still 0+0=0?Shouldn’t it be 0+4=4?

Thanks to all the warm-hearted!!

I write prog3,to test what will happen when i don’t write int a=1;int b=2;

2

#include <iostream>
using namespace std;
int main()
{  
    int a,b;
    cin >> a  ;
    cin >> b;
    cout<< a << "+"<< b <<"="<< a+b << endl;
    return 0;
}

When a bagain,it outputs 0+-1218170892=-1218170892(Why isn’t 0+0=0??)

Добавлено 31 мая 2021 в 22:08

Большинство программ, имеющих какой-либо пользовательский интерфейс, должны обрабатывать вводимые пользователем данные. В программах, которые мы писали, мы использовали std::cin, чтобы попросить пользователя ввести текст. Поскольку ввод текста имеет произвольную форму (пользователь может вводить что угодно), пользователю очень легко ввести данные, которые не ожидаются.

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

В этом уроке мы подробно рассмотрим способы, которыми пользователь может вводить недопустимые текстовые данные через std::cin, и покажем вам несколько разных способов обработки таких случаев.

std::cin, буферы и извлечение

Чтобы обсудить, как std::cin и operator>> могут давать сбой, сначала полезно немного узнать, как они работают.

Когда мы используем operator>> для получения пользовательского ввода и помещения его в переменную, это называется «извлечением». Соответственно, в этом контексте оператор >> называется оператором извлечения.

Когда пользователь вводит данные в ответ на операцию извлечения, эти данные помещаются в буфер внутри std::cin. Буфер (также называемый буфером данных) – это просто часть памяти, отведенная для временного хранения данных, пока они перемещаются из одного места в другое. В этом случае буфер используется для хранения пользовательских входных данных, пока они ожидают извлечения в переменные.

При использовании оператора извлечения происходит следующая процедура:

  • Если во входном буфере уже есть данные, то для извлечения используются они.
  • Если входной буфер не содержит данных, пользователя просят ввести данные для извлечения (так бывает в большинстве случаев). Когда пользователь нажимает Enter, во входной буфер помещается символ ‘n’.
  • operator>> извлекает столько данных из входного буфера, сколько может, в переменную (игнорируя любые начальные пробельные символы, такие как пробелы, табуляции или ‘n’).
  • Любые данные, которые не могут быть извлечены, остаются во входном буфере для следующего извлечения.

Извлечение завершается успешно, если из входного буфера извлечен хотя бы один символ. Любые неизвлеченные входные данные остаются во входном буфере для дальнейшего извлечения. Например:

int x{};
std::cin >> x;

Если пользователь вводит «5a», 5 будет извлечено, преобразовано в целое число и присвоено переменной x. А «an» останется во входном потоке для следующего извлечения.

Извлечение не выполняется, если входные данные не соответствуют типу переменной, в которую они извлекаются. Например:

int x{};
std::cin >> x;

Если бы пользователь ввел ‘b’, извлечение не удалось бы, потому что ‘b’ не может быть извлечено в переменную типа int.

Проверка ввода

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

Есть три основных способа проверки ввода:

  • встроенный (по мере печати пользователя):
    • прежде всего, не позволить пользователю вводить недопустимые данные;
  • пост-запись (после печати пользователя):
    • позволить пользователю ввести в строку всё, что он хочет, затем проверить правильность строки и, если она корректна, преобразовать строку в окончательный формат переменной;
    • позволить пользователю вводить всё, что он хочет, позволить std::cin и operator>> попытаться извлечь данные и обработать случаи ошибок.

Некоторые графические пользовательские интерфейсы и расширенные текстовые интерфейсы позволяют проверять входные данные, когда пользователь их вводит (символ за символом). В общем случае, программист предоставляет функцию проверки, которая принимает входные данные, введенные пользователем, и возвращает true, если входные данные корректны, и false в противном случае. Эта функция вызывается каждый раз, когда пользователь нажимает клавишу. Если функция проверки возвращает истину, клавиша, которую только что нажал пользователь, принимается. Если функция проверки возвращает false, введенный пользователем символ отбрасывается (и не отображается на экране). Используя этот метод, вы можете гарантировать, что любые входные данные, вводимые пользователем, гарантированно будут корректными, потому что любые недопустимые нажатия клавиш обнаруживаются и немедленно отбрасываются. Но, к сожалению, std::cin не поддерживает этот стиль проверки.

Поскольку строки не имеют никаких ограничений на ввод символов, извлечение гарантированно завершится успешно (хотя помните, что std::cin прекращает извлечение на первом неведущем пробельном символе). После того, как строка введена, программа может проанализировать эту строку, чтобы узнать, корректна она или нет. Однако анализ строк и преобразование вводимых строк в другие типы (например, числа) может быть сложной задачей, поэтому это делается только в редких случаях.

Чаще всего мы позволяем std::cin и оператору извлечения выполнять эту тяжелую работу. В этом методе мы позволяем пользователю вводить всё, что он хочет, заставляем std::cin и operator>> попытаться извлечь данные и справиться с последствиями, если это не удастся. Это самый простой способ, о котором мы поговорим ниже.

Пример программы

Рассмотрим следующую программу-калькулятор, в которой нет обработки ошибок:

#include <iostream>
 
double getDouble()
{
    std::cout << "Enter a double value: ";
    double x{};
    std::cin >> x;
    return x;
}
 
char getOperator()
{
    std::cout << "Enter one of the following: +, -, *, or /: ";
    char op{};
    std::cin >> op;
    return op;
}
 
void printResult(double x, char operation, double y)
{
    switch (operation)
    {
    case '+':
        std::cout << x << " + " << y << " is " << x + y << 'n';
        break;
    case '-':
        std::cout << x << " - " << y << " is " << x - y << 'n';
        break;
    case '*':
        std::cout << x << " * " << y << " is " << x * y << 'n';
        break;
    case '/':
        std::cout << x << " / " << y << " is " << x / y << 'n';
        break;
    }
}
 
int main()
{
    double x{ getDouble() };
    char operation{ getOperator() };
    double y{ getDouble() };
 
    printResult(x, operation, y);
 
    return 0;
}

Эта простая программа просит пользователя ввести два числа и математический оператор.

Enter a double value: 5
Enter one of the following: +, -, *, or /: *
Enter a double value: 7
5 * 7 is 35

Теперь подумайте, где неверный ввод пользователя может нарушить работу этой программы.

Сначала мы просим пользователя ввести несколько чисел. Что, если он введет что-то, отличающееся от числа (например, ‘q’)? В этом случае извлечение не удастся.

Во-вторых, мы просим пользователя ввести один из четырех возможных символов. Что, если он введет символ, отличный от ожидаемых? Мы сможем извлечь входные данные, но пока не обрабатываем то, что происходит после.

В-третьих, что, если мы попросим пользователя ввести символ, а он введет строку типа «*q hello». Хотя мы можем извлечь нужный нам символ ‘*’, в буфере останутся дополнительные входные данные, которые могут вызвать проблемы в будущем.

Типы недопустимых входных текстовых данных

Обычно мы можем разделить ошибки ввода текста на четыре типа:

  1. извлечение входных данных выполняется успешно, но входные данные не имеют смысла для программы (например, ввод ‘k’ в качестве математического оператора);
  2. извлечение входных данных выполняется успешно, но пользователь вводит дополнительные данные (например, вводя «*q hello» в качестве математического оператора);
  3. ошибка извлечения входных данных (например, попытка ввести ‘q’ при запросе ввода числа);
  4. извлечение входных данных выполнено успешно, но пользователь выходит за пределы значения числа.

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

Давайте разберемся в каждом из этих случаев и в том, как их обрабатывать с помощью std::cin.

Случай ошибки 1: извлечение успешно, но входные данные не имеют смысла

Это самый простой случай. Рассмотрим следующий вариант выполнения приведенной выше программы:

Enter a double value: 5
Enter one of the following: +, -, *, or /: k
Enter a double value: 7

В этом случае мы попросили пользователя ввести один из четырех символов, но вместо этого он ввел ‘k’. ‘k’ – допустимый символ, поэтому std::cin успешно извлекает его в переменную op, и она возвращается в main. Но наша программа не ожидала этого, поэтому она не обрабатывает этот случай правильно (и, таким образом, ничего не выводит).

Решение здесь простое: выполните проверку ввода. Обычно она состоит из 3 шагов:

  1. убедитесь, что пользовательский ввод соответствует вашим ожиданиям;
  2. если да, верните значение вызывающей функции;
  3. если нет, сообщите пользователю, что что-то пошло не так, и попросите его повторить попытку.

Вот обновленная функция getOperator(), которая выполняет проверку ввода.

char getOperator()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter one of the following: +, -, *, or /: ";
        char operation{};
        std::cin >> operation;
 
        // Проверяем, ввел ли пользователь подходящие данные
        switch (operation)
        {
        case '+':
        case '-':
        case '*':
        case '/':
          return operation; // возвращаем символ вызывающей функции
        default: // в противном случае сообщаем пользователю, что пошло не так
            std::cout << "Oops, that input is invalid.  Please try again.n";
        }
    } // и попробуем еще раз
}

Как видите, мы используем цикл while для бесконечного цикла до тех пор, пока пользователь не предоставит допустимые данные. Если он этого не делает, мы просим его повторить попытку, пока он не даст нам правильные данные, не закроет программу или не уничтожит свой компьютер.

Случай ошибки 2: извлечение успешно, но с посторонними входными данными

Рассмотрим следующий вариант выполнения приведенной выше программы:

Enter a double value: 5*7

Как думаете, что будет дальше?

Enter a double value: 5*7
Enter one of the following: +, -, *, or /: Enter a double value: 5 * 7 is 35

Программа выводит правильный ответ, но форматирование испорчено. Давайте подробнее разберемся, почему.

Когда пользователь вводит «5*7» в качестве вводных данных, эти данные попадают в буфер. Затем оператор >> извлекает 5 в переменную x, оставляя в буфере «*7n». Затем программа напечатает «Enter one of the following: +, -, *, or /:». Однако когда был вызван оператор извлечения, он видит символы «*7n», ожидающие извлечения в буфере, поэтому он использует их вместо того, чтобы запрашивать у пользователя дополнительные данные. Следовательно, он извлекает символ ‘*’, оставляя в буфере «7n».

После запроса пользователя ввести другое значение double, из буфера извлекается 7 без ожидания ввода пользователя. Поскольку у пользователя не было возможности ввести дополнительные данные и нажать Enter (добавляя символ новой строки), все запросы в выводе идут вместе в одной строке, даже если вывод правильный.

Хотя программа работает, выполнение запутано. Было бы лучше, если бы любые введенные посторонние символы просто игнорировались. К счастью, символы игнорировать легко:

// очищаем до 100 символов из буфера или пока не будет удален символ 'n'
std::cin.ignore(100, 'n');

Этот вызов удалит до 100 символов, но если пользователь ввел более 100 символов, мы снова получим беспорядочный вывод. Чтобы игнорировать все символы до следующего символа ‘n’, мы можем передать std::numeric_limits<std::streamsize>::max() в std::cin.ignore(). std::numeric_limits<std::streamsize>::max() возвращает наибольшее значение, которое может быть сохранено в переменной типа std::streamsize. Передача этого значения в std::cin.ignore() приводит к отключению проверки счетчика.

Чтобы игнорировать всё, вплоть до следующего символа ‘n’, мы вызываем

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');

Поскольку эта строка довольно длинная для того, что она делает, будет удобнее обернуть ее в функцию, которую можно вызвать вместо std::cin.ignore().

#include <limits> // для std::numeric_limits
 
void ignoreLine()
{
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
}

Поскольку последний введенный пользователем символ должен быть ‘n’, мы можем указать std::cin игнорировать символы в буфере, пока не найдет символ новой строки (который также будет удален).

Давайте обновим нашу функцию getDouble(), чтобы игнорировать любой посторонний ввод:

double getDouble()
{
    std::cout << "Enter a double value: ";
    double x{};
    std::cin >> x;
    ignoreLine();
    return x;
}

Теперь наша программа будет работать, как ожидалось, даже если мы введем «5*7» при первом запросе ввода – 5 будет извлечено, а остальные символы из входного буфера будут удалены. Поскольку входной буфер теперь пуст, при следующем выполнении операции извлечения данные у пользователя будут запрашиваться правильно!

Случай ошибки 3: сбой при извлечении

Теперь рассмотрим следующий вариант выполнения нашей программы калькулятора:

Enter a double value: a

Неудивительно, что программа работает не так, как ожидалось, но интересно, как она дает сбой:

Enter a double value: a
Enter one of the following: +, -, *, or /: Enter a double value: 

и программа внезапно завершается.

Это очень похоже на случай ввода посторонних символов, но немного отличается. Давайте посмотрим подробнее.

Когда пользователь вводит ‘a’, этот символ помещается в буфер. Затем оператор >> пытается извлечь ‘a’ в переменную x, которая имеет тип double. Поскольку ‘a’ нельзя преобразовать в double, оператор >> не может выполнить извлечение. В этот момент происходят две вещи: ‘a’ остается в буфере, а std::cin переходит в «режим отказа».

Находясь в «режиме отказа», будущие запросы на извлечение входных данных будут автоматически завершаться ошибкой. Таким образом, в нашей программе калькулятора вывод запросов всё еще печатается, но любые запросы на дальнейшее извлечение игнорируются. Программа просто выполняется до конца, а затем завершается (без вывода результата потому, что мы не прочитали допустимую математическую операцию).

К счастью, мы можем определить, завершилось ли извлечение сбоем, и исправить это:

if (std::cin.fail()) // предыдущее извлечение не удалось?
{
    // да, давайте разберемся с ошибкой
    std::cin.clear(); // возвращаем нас в "нормальный" режим работы
    ignoreLine();     // и удаляем неверные входные данные
}

Вот и всё!

Давайте, интегрируем это в нашу функцию getDouble():

double getDouble()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter a double value: ";
        double x{};
        std::cin >> x;
 
        if (std::cin.fail()) // предыдущее извлечение не удалось?
        {
            // да, давайте разберемся с ошибкой
            std::cin.clear(); // возвращаем нас в "нормальный" режим работы
            ignoreLine();     // и удаляем неверные входные данные
        }
        else // иначе наше извлечение прошло успешно
        {
            ignoreLine();
            return x; // поэтому возвращаем извлеченное нами значение
        }
    }
}

Примечание. До C++11 неудачное извлечение не приводило к изменению извлекаемой переменной. Это означает, что если переменная была неинициализирована, она останется неинициализированной в случае неудачного извлечения. Однако, начиная с C++11, неудачное извлечение из-за недопустимого ввода приведет к тому, что переменная будет инициализирована нулем. Инициализация нулем означает, что для переменной установлено значение 0, 0.0, «» или любое другое значение, в которое 0 преобразуется для этого типа.

Случай ошибки 4: извлечение успешно, но пользователь выходит за пределы значения числа

Рассмотрим следующий простой пример:

#include <cstdint>
#include <iostream>
 
int main()
{
    std::int16_t x{}; // x - 16 бит, может быть от -32768 до 32767
    std::cout << "Enter a number between -32768 and 32767: ";
    std::cin >> x;
 
    std::int16_t y{}; // y - 16 бит, может быть от -32768 до 32767
    std::cout << "Enter another number between -32768 and 32767: ";
    std::cin >> y;
 
    std::cout << "The sum is: " << x + y << 'n';
    return 0;
}

Что произойдет, если пользователь введет слишком большое число (например, 40000)?

Enter a number between -32768 and 32767: 40000
Enter another number between -32768 and 32767: The sum is: 32767

В приведенном выше случае std::cin немедленно переходит в «режим отказа», но также присваивает переменной ближайшее значение в диапазоне. Следовательно, x остается с присвоенным значением 32767. Дополнительные входные данные пропускаются, оставляя y с инициализированным значением 0. Мы можем обрабатывать этот вид ошибки так же, как и неудачное извлечение.

Примечание. До C++11 неудачное извлечение не приводило к изменению извлекаемой переменной. Это означает, что если переменная была неинициализирована, в случае неудачного извлечения она останется неинициализированной. Однако, начиная с C++11, неудачное извлечение вне диапазона приведет к тому, что переменной будет присвоено ближайшее значение в диапазоне.

Собираем всё вместе

Вот наш пример калькулятора с полной проверкой ошибок:

#include <iostream>
#include <limits>
 
void ignoreLine()
{
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
}
 
double getDouble()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter a double value: ";
        double x{};
        std::cin >> x;
 
        // Проверяем на неудачное извлечение
        if (std::cin.fail()) // предыдущее извлечение не удалось?
        {
            // да, давайте разберемся с ошибкой
            std::cin.clear(); // возвращаем нас в "нормальный" режим работы
            ignoreLine();     // и удаляем неверные входные данные
            std::cout << "Oops, that input is invalid.  Please try again.n";
        }
        else
        {
            ignoreLine(); // удаляем любые посторонние входные данные
 
            // пользователь не может ввести бессмысленное значение double,
            // поэтому нам не нужно беспокоиться о его проверке
            return x;
        }
    }
}
 
char getOperator()
{
    while (true) // Цикл, пока пользователь не введет допустимые данные
    {
        std::cout << "Enter one of the following: +, -, *, or /: ";
        char operation{};
        std::cin >> operation;
        ignoreLine();
 
        // Проверяем, ввел ли пользователь осмысленные данные
        switch (operation)
        {
        case '+':
        case '-':
        case '*':
        case '/':
            return operation; // возвращаем символ вызывающей функции
        default: // в противном случае сообщаем пользователю, что пошло не так
            std::cout << "Oops, that input is invalid.  Please try again.n";
        }
    } // и попробуем еще раз
}
 
void printResult(double x, char operation, double y)
{
    switch (operation)
    {
    case '+':
        std::cout << x << " + " << y << " is " << x + y << 'n';
        break;
    case '-':
        std::cout << x << " - " << y << " is " << x - y << 'n';
        break;
    case '*':
        std::cout << x << " * " << y << " is " << x * y << 'n';
        break;
    case '/':
        std::cout << x << " / " << y << " is " << x / y << 'n';
        break;
    default: // Надежность означает также обработку неожиданных параметров,
             // даже если getOperator() гарантирует, что op в этой
             // конкретной программе корректен 
        std::cerr << "Something went wrong: printResult() got an invalid operator.n";
    }
}
 
int main()
{
    double x{ getDouble() };
    char operation{ getOperator() };
    double y{ getDouble() };
 
    printResult(x, operation, y);
 
    return 0;
}

Заключение

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

  • Могло ли извлечение закончиться неудачей?
  • Может ли пользователь ввести больше, чем ожидалось?
  • Может ли пользователь ввести бессмысленные входные данные?
  • Может ли пользователь переполнить входные данные?

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

Следующий код очистит любые посторонние входные данные:

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');

Следующий код будет проверять и исправлять неудачные извлечения или переполнение:

if (std::cin.fail()) // предыдущее извлечение не удалось или закончилось переполнением?
{
    // да, давайте разберемся с ошибкой
    std::cin.clear(); // возвращаем нас в "нормальный" режим работы
    ignoreLine();     // и удаляем неверные входные данные
}

Наконец, используйте циклы, чтобы попросить пользователя повторно ввести данные, если исходные входные данные были недопустимыми.

Примечание автора


Проверка ввода важна и полезна, но она также делает примеры более сложными и трудными для понимания. Соответственно, на будущих уроках мы, как правило, не будем проводить никакой проверки вводимых данных, если они не имеют отношения к чему-то, чему мы пытаемся научить.

Теги

C++ / CppLearnCppstd::cinДля начинающихОбнаружение ошибокОбработка ошибокОбучениеПрограммирование

Содержание

  1. 7.16 – std::cin и обработка недопустимого ввода
  2. std::cin , буферы и извлечение
  3. Проверка ввода
  4. Пример программы
  5. Типы недопустимых входных текстовых данных
  6. Случай ошибки 1: извлечение успешно, но входные данные не имеют смысла
  7. Случай ошибки 2: извлечение успешно, но с посторонними входными данными
  8. Случай ошибки 3: сбой при извлечении
  9. Случай ошибки 4: извлечение успешно, но пользователь выходит за пределы значения числа
  10. Собираем всё вместе
  11. Заключение

7.16 – std::cin и обработка недопустимого ввода

Большинство программ, имеющих какой-либо пользовательский интерфейс, должны обрабатывать вводимые пользователем данные. В программах, которые мы писали, мы использовали std::cin , чтобы попросить пользователя ввести текст. Поскольку ввод текста имеет произвольную форму (пользователь может вводить что угодно), пользователю очень легко ввести данные, которые не ожидаются.

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

В этом уроке мы подробно рассмотрим способы, которыми пользователь может вводить недопустимые текстовые данные через std::cin , и покажем вам несколько разных способов обработки таких случаев.

std::cin , буферы и извлечение

Чтобы обсудить, как std::cin и operator>> могут давать сбой, сначала полезно немного узнать, как они работают.

Когда мы используем operator>> для получения пользовательского ввода и помещения его в переменную, это называется «извлечением». Соответственно, в этом контексте оператор >> называется оператором извлечения.

Когда пользователь вводит данные в ответ на операцию извлечения, эти данные помещаются в буфер внутри std::cin . Буфер (также называемый буфером данных) – это просто часть памяти, отведенная для временного хранения данных, пока они перемещаются из одного места в другое. В этом случае буфер используется для хранения пользовательских входных данных, пока они ожидают извлечения в переменные.

При использовании оператора извлечения происходит следующая процедура:

  • Если во входном буфере уже есть данные, то для извлечения используются они.
  • Если входной буфер не содержит данных, пользователя просят ввести данные для извлечения (так бывает в большинстве случаев). Когда пользователь нажимает Enter , во входной буфер помещается символ ‘n’ .
  • operator>> извлекает столько данных из входного буфера, сколько может, в переменную (игнорируя любые начальные пробельные символы, такие как пробелы, табуляции или ‘n’ ).
  • Любые данные, которые не могут быть извлечены, остаются во входном буфере для следующего извлечения.

Извлечение завершается успешно, если из входного буфера извлечен хотя бы один символ. Любые неизвлеченные входные данные остаются во входном буфере для дальнейшего извлечения. Например:

Если пользователь вводит «5a» , 5 будет извлечено, преобразовано в целое число и присвоено переменной x . А «an» останется во входном потоке для следующего извлечения.

Извлечение не выполняется, если входные данные не соответствуют типу переменной, в которую они извлекаются. Например:

Если бы пользователь ввел ‘b’ , извлечение не удалось бы, потому что ‘b’ не может быть извлечено в переменную типа int .

Проверка ввода

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

Есть три основных способа проверки ввода:

  • встроенный (по мере печати пользователя):
    • прежде всего, не позволить пользователю вводить недопустимые данные;
  • пост-запись (после печати пользователя):
    • позволить пользователю ввести в строку всё, что он хочет, затем проверить правильность строки и, если она корректна, преобразовать строку в окончательный формат переменной;
    • позволить пользователю вводить всё, что он хочет, позволить std::cin и operator>> попытаться извлечь данные и обработать случаи ошибок.

Некоторые графические пользовательские интерфейсы и расширенные текстовые интерфейсы позволяют проверять входные данные, когда пользователь их вводит (символ за символом). В общем случае, программист предоставляет функцию проверки, которая принимает входные данные, введенные пользователем, и возвращает true , если входные данные корректны, и false в противном случае. Эта функция вызывается каждый раз, когда пользователь нажимает клавишу. Если функция проверки возвращает истину, клавиша, которую только что нажал пользователь, принимается. Если функция проверки возвращает false , введенный пользователем символ отбрасывается (и не отображается на экране). Используя этот метод, вы можете гарантировать, что любые входные данные, вводимые пользователем, гарантированно будут корректными, потому что любые недопустимые нажатия клавиш обнаруживаются и немедленно отбрасываются. Но, к сожалению, std::cin не поддерживает этот стиль проверки.

Поскольку строки не имеют никаких ограничений на ввод символов, извлечение гарантированно завершится успешно (хотя помните, что std::cin прекращает извлечение на первом неведущем пробельном символе). После того, как строка введена, программа может проанализировать эту строку, чтобы узнать, корректна она или нет. Однако анализ строк и преобразование вводимых строк в другие типы (например, числа) может быть сложной задачей, поэтому это делается только в редких случаях.

Чаще всего мы позволяем std::cin и оператору извлечения выполнять эту тяжелую работу. В этом методе мы позволяем пользователю вводить всё, что он хочет, заставляем std::cin и operator>> попытаться извлечь данные и справиться с последствиями, если это не удастся. Это самый простой способ, о котором мы поговорим ниже.

Пример программы

Рассмотрим следующую программу-калькулятор, в которой нет обработки ошибок:

Эта простая программа просит пользователя ввести два числа и математический оператор.

Теперь подумайте, где неверный ввод пользователя может нарушить работу этой программы.

Сначала мы просим пользователя ввести несколько чисел. Что, если он введет что-то, отличающееся от числа (например, ‘q’ )? В этом случае извлечение не удастся.

Во-вторых, мы просим пользователя ввести один из четырех возможных символов. Что, если он введет символ, отличный от ожидаемых? Мы сможем извлечь входные данные, но пока не обрабатываем то, что происходит после.

В-третьих, что, если мы попросим пользователя ввести символ, а он введет строку типа «*q hello» . Хотя мы можем извлечь нужный нам символ ‘*’ , в буфере останутся дополнительные входные данные, которые могут вызвать проблемы в будущем.

Типы недопустимых входных текстовых данных

Обычно мы можем разделить ошибки ввода текста на четыре типа:

  1. извлечение входных данных выполняется успешно, но входные данные не имеют смысла для программы (например, ввод ‘k’ в качестве математического оператора);
  2. извлечение входных данных выполняется успешно, но пользователь вводит дополнительные данные (например, вводя «*q hello» в качестве математического оператора);
  3. ошибка извлечения входных данных (например, попытка ввести ‘q’ при запросе ввода числа);
  4. извлечение входных данных выполнено успешно, но пользователь выходит за пределы значения числа.

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

Давайте разберемся в каждом из этих случаев и в том, как их обрабатывать с помощью std::cin .

Случай ошибки 1: извлечение успешно, но входные данные не имеют смысла

Это самый простой случай. Рассмотрим следующий вариант выполнения приведенной выше программы:

В этом случае мы попросили пользователя ввести один из четырех символов, но вместо этого он ввел ‘k’ . ‘k’ – допустимый символ, поэтому std::cin успешно извлекает его в переменную op , и она возвращается в main . Но наша программа не ожидала этого, поэтому она не обрабатывает этот случай правильно (и, таким образом, ничего не выводит).

Решение здесь простое: выполните проверку ввода. Обычно она состоит из 3 шагов:

  1. убедитесь, что пользовательский ввод соответствует вашим ожиданиям;
  2. если да, верните значение вызывающей функции;
  3. если нет, сообщите пользователю, что что-то пошло не так, и попросите его повторить попытку.

Вот обновленная функция getOperator() , которая выполняет проверку ввода.

Как видите, мы используем цикл while для бесконечного цикла до тех пор, пока пользователь не предоставит допустимые данные. Если он этого не делает, мы просим его повторить попытку, пока он не даст нам правильные данные, не закроет программу или не уничтожит свой компьютер.

Случай ошибки 2: извлечение успешно, но с посторонними входными данными

Рассмотрим следующий вариант выполнения приведенной выше программы:

Как думаете, что будет дальше?

Программа выводит правильный ответ, но форматирование испорчено. Давайте подробнее разберемся, почему.

Когда пользователь вводит «5*7» в качестве вводных данных, эти данные попадают в буфер. Затем оператор >> извлекает 5 в переменную x , оставляя в буфере «*7n» . Затем программа напечатает «Enter one of the following: +, -, *, or /:» . Однако когда был вызван оператор извлечения, он видит символы «*7n» , ожидающие извлечения в буфере, поэтому он использует их вместо того, чтобы запрашивать у пользователя дополнительные данные. Следовательно, он извлекает символ ‘*’ , оставляя в буфере «7n» .

После запроса пользователя ввести другое значение double , из буфера извлекается 7 без ожидания ввода пользователя. Поскольку у пользователя не было возможности ввести дополнительные данные и нажать Enter (добавляя символ новой строки), все запросы в выводе идут вместе в одной строке, даже если вывод правильный.

Хотя программа работает, выполнение запутано. Было бы лучше, если бы любые введенные посторонние символы просто игнорировались. К счастью, символы игнорировать легко:

Этот вызов удалит до 100 символов, но если пользователь ввел более 100 символов, мы снова получим беспорядочный вывод. Чтобы игнорировать все символы до следующего символа ‘n’ , мы можем передать std::numeric_limits ::max() в std::cin.ignore() . std::numeric_limits ::max() возвращает наибольшее значение, которое может быть сохранено в переменной типа std::streamsize . Передача этого значения в std::cin.ignore() приводит к отключению проверки счетчика.

Чтобы игнорировать всё, вплоть до следующего символа ‘n’ , мы вызываем

Поскольку эта строка довольно длинная для того, что она делает, будет удобнее обернуть ее в функцию, которую можно вызвать вместо std::cin.ignore() .

Поскольку последний введенный пользователем символ должен быть ‘n’ , мы можем указать std::cin игнорировать символы в буфере, пока не найдет символ новой строки (который также будет удален).

Давайте обновим нашу функцию getDouble() , чтобы игнорировать любой посторонний ввод:

Теперь наша программа будет работать, как ожидалось, даже если мы введем «5*7» при первом запросе ввода – 5 будет извлечено, а остальные символы из входного буфера будут удалены. Поскольку входной буфер теперь пуст, при следующем выполнении операции извлечения данные у пользователя будут запрашиваться правильно!

Случай ошибки 3: сбой при извлечении

Теперь рассмотрим следующий вариант выполнения нашей программы калькулятора:

Неудивительно, что программа работает не так, как ожидалось, но интересно, как она дает сбой:

и программа внезапно завершается.

Это очень похоже на случай ввода посторонних символов, но немного отличается. Давайте посмотрим подробнее.

Когда пользователь вводит ‘a’ , этот символ помещается в буфер. Затем оператор >> пытается извлечь ‘a’ в переменную x , которая имеет тип double . Поскольку ‘a’ нельзя преобразовать в double , оператор >> не может выполнить извлечение. В этот момент происходят две вещи: ‘a’ остается в буфере, а std::cin переходит в «режим отказа».

Находясь в «режиме отказа», будущие запросы на извлечение входных данных будут автоматически завершаться ошибкой. Таким образом, в нашей программе калькулятора вывод запросов всё еще печатается, но любые запросы на дальнейшее извлечение игнорируются. Программа просто выполняется до конца, а затем завершается (без вывода результата потому, что мы не прочитали допустимую математическую операцию).

К счастью, мы можем определить, завершилось ли извлечение сбоем, и исправить это:

Давайте, интегрируем это в нашу функцию getDouble() :

Примечание. До C++11 неудачное извлечение не приводило к изменению извлекаемой переменной. Это означает, что если переменная была неинициализирована, она останется неинициализированной в случае неудачного извлечения. Однако, начиная с C++11, неудачное извлечение из-за недопустимого ввода приведет к тому, что переменная будет инициализирована нулем. Инициализация нулем означает, что для переменной установлено значение 0, 0.0, «» или любое другое значение, в которое 0 преобразуется для этого типа.

Случай ошибки 4: извлечение успешно, но пользователь выходит за пределы значения числа

Рассмотрим следующий простой пример:

Что произойдет, если пользователь введет слишком большое число (например, 40000)?

В приведенном выше случае std::cin немедленно переходит в «режим отказа», но также присваивает переменной ближайшее значение в диапазоне. Следовательно, x остается с присвоенным значением 32767. Дополнительные входные данные пропускаются, оставляя y с инициализированным значением 0. Мы можем обрабатывать этот вид ошибки так же, как и неудачное извлечение.

Примечание. До C++11 неудачное извлечение не приводило к изменению извлекаемой переменной. Это означает, что если переменная была неинициализирована, в случае неудачного извлечения она останется неинициализированной. Однако, начиная с C++11, неудачное извлечение вне диапазона приведет к тому, что переменной будет присвоено ближайшее значение в диапазоне.

Собираем всё вместе

Вот наш пример калькулятора с полной проверкой ошибок:

Заключение

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

  • Могло ли извлечение закончиться неудачей?
  • Может ли пользователь ввести больше, чем ожидалось?
  • Может ли пользователь ввести бессмысленные входные данные?
  • Может ли пользователь переполнить входные данные?

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

Следующий код очистит любые посторонние входные данные:

Следующий код будет проверять и исправлять неудачные извлечения или переполнение:

Наконец, используйте циклы, чтобы попросить пользователя повторно ввести данные, если исходные входные данные были недопустимыми.

Примечание автора

Проверка ввода важна и полезна, но она также делает примеры более сложными и трудными для понимания. Соответственно, на будущих уроках мы, как правило, не будем проводить никакой проверки вводимых данных, если они не имеют отношения к чему-то, чему мы пытаемся научить.

Источник

Most programs that have a user interface of some kind need to handle user input. In the programs that you have been writing, you have been using std::cin to ask the user to enter text input. Because text input is so free-form (the user can enter anything), it’s very easy for the user to enter input that is not expected.

As you write programs, you should always consider how users will (unintentionally or otherwise) misuse your programs. A well-written program will anticipate how users will misuse it, and either handle those cases gracefully or prevent them from happening in the first place (if possible). A program that handles error cases well is said to be robust.

In this lesson, we’ll take a look specifically at ways the user can enter invalid text input via std::cin, and show you some different ways to handle those cases.

std::cin, buffers, and extraction

In order to discuss how std::cin and operator>> can fail, it first helps to know a little bit about how they work.

When we use operator>> to get user input and put it into a variable, this is called an “extraction”. The >> operator is accordingly called the extraction operator when used in this context.

When the user enters input in response to an extraction operation, that data is placed in a buffer inside of std::cin. A buffer (also called a data buffer) is simply a piece of memory set aside for storing data temporarily while it’s moved from one place to another. In this case, the buffer is used to hold user input while it’s waiting to be extracted to variables.

When the extraction operator is used, the following procedure happens:

  • If there is data already in the input buffer, that data is used for extraction.
  • If the input buffer contains no data, the user is asked to input data for extraction (this is the case most of the time). When the user hits enter, a ‘n’ character will be placed in the input buffer.
  • operator>> extracts as much data from the input buffer as it can into the variable (ignoring any leading whitespace characters, such as spaces, tabs, or ‘n’).
  • Any data that can not be extracted is left in the input buffer for the next extraction.

Extraction succeeds if at least one character is extracted from the input buffer. Any unextracted input is left in the input buffer for future extractions. For example:

int x{};
std::cin >> x;

If the user enters “5a”, 5 will be extracted, converted to an integer, and assigned to variable x. “an” will be left in the input buffer for the next extraction.

Extraction fails if the input data does not match the type of the variable being extracted to. For example:

int x{};
std::cin >> x;

If the user were to enter ‘b’, extraction would fail because ‘b’ can not be extracted to an integer variable.

Validating input

The process of checking whether user input conforms to what the program is expecting is called input validation.

There are three basic ways to do input validation:

Inline (as the user types):

  1. Prevent the user from typing invalid input in the first place.

Post-entry (after the user types):

  1. Let the user enter whatever they want into a string, then validate whether the string is correct, and if so, convert the string to the final variable format.
  2. Let the user enter whatever they want, let std::cin and operator>> try to extract it, and handle the error cases.

Some graphical user interfaces and advanced text interfaces will let you validate input as the user enters it (character by character). Generally speaking, the programmer provides a validation function that accepts the input the user has entered so far, and returns true if the input is valid, and false otherwise. This function is called every time the user presses a key. If the validation function returns true, the key the user just pressed is accepted. If the validation function returns false, the character the user just input is discarded (and not shown on the screen). Using this method, you can ensure that any input the user enters is guaranteed to be valid, because any invalid keystrokes are discovered and discarded immediately. Unfortunately, std::cin does not support this style of validation.

Since strings do not have any restrictions on what characters can be entered, extraction is guaranteed to succeed (though remember that std::cin stops extracting at the first non-leading whitespace character). Once a string is entered, the program can then parse the string to see if it is valid or not. However, parsing strings and converting string input to other types (e.g. numbers) can be challenging, so this is only done in rare cases.

Most often, we let std::cin and the extraction operator do the hard work. Under this method, we let the user enter whatever they want, have std::cin and operator>> try to extract it, and deal with the fallout if it fails. This is the easiest method, and the one we’ll talk more about below.

A sample program

Consider the following calculator program that has no error handling:

#include <iostream>
 
double getDouble()
{
    std::cout << "Enter a double value: ";
    double x{};
    std::cin >> x;
    return x;
}
 
char getOperator()
{
    std::cout << "Enter one of the following: +, -, *, or /: ";
    char op{};
    std::cin >> op;
    return op;
}
 
void printResult(double x, char operation, double y)
{
    switch (operation)
    {
    case '+':
        std::cout << x << " + " << y << " is " << x + y << 'n';
        break;
    case '-':
        std::cout << x << " - " << y << " is " << x - y << 'n';
        break;
    case '*':
        std::cout << x << " * " << y << " is " << x * y << 'n';
        break;
    case '/':
        std::cout << x << " / " << y << " is " << x / y << 'n';
        break;
    }
}
 
int main()
{
    double x{ getDouble() };
    char operation{ getOperator() };
    double y{ getDouble() };
 
    printResult(x, operation, y);
 
    return 0;
}

This simple program asks the user to enter two numbers and a mathematical operator.

Enter a double value: 5
Enter one of the following: +, -, *, or /: *
Enter a double value: 7
5 * 7 is 35

Now, consider where invalid user input might break this program.

First, we ask the user to enter some numbers. What if they enter something other than a number (e.g. ‘q’)? In this case, extraction will fail.

Second, we ask the user to enter one of four possible symbols. What if they enter a character other than one of the symbols we’re expecting? We’ll be able to extract the input, but we don’t currently handle what happens afterward.

Third, what if we ask the user to enter a symbol and they enter a string like “q hello”. Although we can extract the ‘‘ character we need, there’s additional input left in the buffer that could cause problems down the road.

Types of invalid text input

We can generally separate input text errors into four types:

  • Input extraction succeeds but the input is meaningless to the program (e.g. entering ‘k’ as your mathematical operator).
  • Input extraction succeeds but the user enters additional input (e.g. entering ‘*q hello’ as your mathematical operator).
  • Input extraction fails (e.g. trying to enter ‘q’ into a numeric input).
  • Input extraction succeeds but the user overflows a numeric value.

Thus, to make our programs robust, whenever we ask the user for input, we ideally should determine whether each of the above can possibly occur, and if so, write code to handle those cases.

Let’s dig into each of these cases, and how to handle them using std::cin.

Error case 1: Extraction succeeds but input is meaningless

This is the simplest case. Consider the following execution of the above program:

Enter a double value: 5
Enter one of the following: +, -, *, or /: k
Enter a double value: 7

In this case, we asked the user to enter one of four symbols, but they entered ‘k’ instead. ‘k’ is a valid character, so std::cin happily extracts it to variable op, and this gets returned to main. But our program wasn’t expecting this to happen, so it doesn’t properly deal with this case (and thus never outputs anything).

The solution here is simple: do input validation. This usually consists of 3 steps:

  1. Check whether the user’s input was what you were expecting.
  2. If so, return the value to the caller.
  3. If not, tell the user something went wrong and have them try again.

Here’s an updated getOperator() function that does input validation.

char getOperator()
{
    while (true) // Loop until user enters a valid input
    {
        std::cout << "Enter one of the following: +, -, *, or /: ";
        char operation{};
        std::cin >> operation;

        // Check whether the user entered meaningful input
        switch (operation)
        {
        case '+':
        case '-':
        case '*':
        case '/':
            return operation; // return it to the caller
        default: // otherwise tell the user what went wrong
            std::cerr << "Oops, that input is invalid.  Please try again.n";
        }
    } // and try again
}

As you can see, we’re using a while loop to continuously loop until the user provides valid input. If they don’t, we ask them to try again until they either give us valid input, shutdown the program, or destroy their computer.

Error case 2: Extraction succeeds but with extraneous input

Consider the following execution of the above program:

Enter a double value: 5*7

What do you think happens next?

Enter a double value: 5*7
Enter one of the following: +, -, *, or /: Enter a double value: 5 * 7 is 35

The program prints the right answer, but the formatting is all messed up. Let’s take a closer look at why.

When the user enters 5*7 as input, that input goes into the buffer. Then operator>> extracts the 5 to variable x, leaving *7n in the buffer. Next, the program prints “Enter one of the following: +, -, *, or /:”. However, when the extraction operator was called, it sees *7n waiting in the buffer to be extracted, so it uses that instead of asking the user for more input. Consequently, it extracts the ‘*’ character, leaving 7n in the buffer.

After asking the user to enter another double value, the 7 in the buffer gets extracted without asking the user. Since the user never had an opportunity to enter additional data and hit enter (causing a newline), the output prompts all run together on the same line.

Although the above program works, the execution is messy. It would be better if any extraneous characters entered were simply ignored. Fortunately, it’s easy to ignore characters:

std::cin.ignore(100, 'n');  // clear up to 100 characters out of the buffer, or until a 'n' character is removed

This call would remove up to 100 characters, but if the user entered more than 100 characters we’ll get messy output again. To ignore all characters up to the next ‘n’, we can pass std::numeric_limits<std::streamsize>::max() to std::cin.ignore(). std::numeric_limits<std::streamsize>::max() returns the largest value that can be stored in a variable of type std::streamsize. Passing this value to std::cin.ignore() causes it to disable the count check.

To ignore everything up to and including the next ‘n’ character, we call

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');

Because this line is quite long for what it does, it’s handy to wrap it in a function which can be called in place of std::cin.ignore().

#include <limits> // for std::numeric_limits

void ignoreLine()
{
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
}

Since the last character the user entered must be a ‘n’, we can tell std::cin to ignore buffered characters until it finds a newline character (which is removed as well).

Let’s update our getDouble() function to ignore any extraneous input:

double getDouble()
{
    std::cout << "Enter a double value: ";
    double x{};
    std::cin >> x;
    ignoreLine();
    return x;
}

Now our program will work as expected, even if we enter “5*7” for the first input — the 5 will be extracted, and the rest of the characters will be removed from the input buffer. Since the input buffer is now empty, the user will be properly asked for input the next time an extraction operation is performed!

Author’s note

Some lessons still pass 32767 to std::cin.ignore(). This is a magic number with no special meaning to std::cin.ignore() and should be avoided. If you see such an occurrence, feel free to point it out.

Error case 3: Extraction fails

Now consider the following execution of our updated calculator program:

Enter a double value: a

You shouldn’t be surprised that the program doesn’t perform as expected, but how it fails is interesting:

Enter a double value: a
Enter one of the following: +, -, *, or /: Oops, that input is invalid.  Please try again.
Enter one of the following: +, -, *, or /: Oops, that input is invalid.  Please try again.
Enter one of the following: +, -, *, or /: Oops, that input is invalid.  Please try again.

and that last line keeps printing until the program is closed.

This looks pretty similar to the extraneous input case, but it’s a little different. Let’s take a closer look.

When the user enters ‘a’, that character is placed in the buffer. Then operator>> tries to extract ‘a’ to variable x, which is of type double. Since ‘a’ can’t be converted to a double, operator>> can’t do the extraction. Two things happen at this point: ‘a’ is left in the buffer, and std::cin goes into “failure mode”.

Once in “failure mode”, future requests for input extraction will silently fail. Thus in our calculator program, the output prompts still print, but any requests for further extraction are ignored. This means that instead waiting for us to enter an operation, the input prompt is skipped, and we get stuck in an infinite loop because there is no way to reach one of the valid cases.

Fortunately, we can detect whether an extraction has failed:

if (std::cin.fail()) // has a previous extraction failed?
{
    // yep, so let's handle the failure
    std::cin.clear(); // put us back in 'normal' operation mode
    ignoreLine(); // and remove the bad input
}

Because std::cin has a Boolean conversion indicating whether the last input succeeded, it’s more idiomatic to write the above as following:

if (!std::cin) // has a previous extraction failed?
{
    // yep, so let's handle the failure
    std::cin.clear(); // put us back in 'normal' operation mode
    ignoreLine(); // and remove the bad input
}

Let’s integrate that into our getDouble() function:

double getDouble()
{
    while (true) // Loop until user enters a valid input
    {
        std::cout << "Enter a double value: ";
        double x{};
        std::cin >> x;

        if (!std::cin) // has a previous extraction failed?
        {
            // yep, so let's handle the failure
            std::cin.clear(); // put us back in 'normal' operation mode
            ignoreLine(); // and remove the bad input
        }
        else // else our extraction succeeded
        {
            ignoreLine();
            return x; // so return the value we extracted
        }
    }
}

A failed extraction due to invalid input will cause the variable to be zero-initialized. Zero initialization means the variable is set to 0, 0.0, “”, or whatever value 0 converts to for that type.

Error case 4: Extraction succeeds but the user overflows a numeric value

Consider the following simple example:

#include <cstdint>
#include <iostream>

int main()
{
    std::int16_t x{}; // x is 16 bits, holds from -32768 to 32767
    std::cout << "Enter a number between -32768 and 32767: ";
    std::cin >> x;

    std::int16_t y{}; // y is 16 bits, holds from -32768 to 32767
    std::cout << "Enter another number between -32768 and 32767: ";
    std::cin >> y;

    std::cout << "The sum is: " << x + y << 'n';
    return 0;
}

What happens if the user enters a number that is too large (e.g. 40000)?

Enter a number between -32768 and 32767: 40000
Enter another number between -32768 and 32767: The sum is: 32767

In the above case, std::cin goes immediately into “failure mode”, but also assigns the closest in-range value to the variable. Consequently, x is left with the assigned value of 32767. Additional inputs are skipped, leaving y with the initialized value of 0. We can handle this kind of error in the same way as a failed extraction.

A failed extraction due to invalid input will cause the variable to be zero-initialized. Zero initialization means the variable is set to 0, 0.0, “”, or whatever value 0 converts to for that type.

Putting it all together

Here’s our example calculator, updated with a few additional bits of error checking:

#include <iostream>
#include <limits>

void ignoreLine()
{
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
}

double getDouble()
{
    while (true) // Loop until user enters a valid input
    {
        std::cout << "Enter a double value: ";
        double x{};
        std::cin >> x;

        // Check for failed extraction
        if (!std::cin) // has a previous extraction failed?
        {
            // yep, so let's handle the failure
            std::cin.clear(); // put us back in 'normal' operation mode
            ignoreLine(); // and remove the bad input
            std::cerr << "Oops, that input is invalid.  Please try again.n";
        }
        else
        {
            ignoreLine(); // remove any extraneous input
            return x;
        }
    }
}

char getOperator()
{
    while (true) // Loop until user enters a valid input
    {
        std::cout << "Enter one of the following: +, -, *, or /: ";
        char operation{};
        std::cin >> operation;
        ignoreLine(); // // remove any extraneous input

        // Check whether the user entered meaningful input
        switch (operation)
        {
        case '+':
        case '-':
        case '*':
        case '/':
            return operation; // return it to the caller
        default: // otherwise tell the user what went wrong
            std::cerr << "Oops, that input is invalid.  Please try again.n";
        }
    } // and try again
}
 
void printResult(double x, char operation, double y)
{
    switch (operation)
    {
    case '+':
        std::cout << x << " + " << y << " is " << x + y << 'n';
        break;
    case '-':
        std::cout << x << " - " << y << " is " << x - y << 'n';
        break;
    case '*':
        std::cout << x << " * " << y << " is " << x * y << 'n';
        break;
    case '/':
        std::cout << x << " / " << y << " is " << x / y << 'n';
        break;
    default: // Being robust means handling unexpected parameters as well, even though getOperator() guarantees operation is valid in this particular program
        std::cerr << "Something went wrong: printResult() got an invalid operator.n";
    }
}
 
int main()
{
    double x{ getDouble() };
    char operation{ getOperator() };
    double y{ getDouble() };
 
    printResult(x, operation, y);
 
    return 0;
}

Conclusion

As you write your programs, consider how users will misuse your program, especially around text input. For each point of text input, consider:

  • Could extraction fail?
  • Could the user enter more input than expected?
  • Could the user enter meaningless input?
  • Could the user overflow an input?

You can use if statements and boolean logic to test whether input is expected and meaningful.

The following code will clear any extraneous input:

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n');

The following code will test for and fix failed extractions or overflow:

if (!std::cin) // has a previous extraction failed or overflowed?
{
    // yep, so let's handle the failure
    std::cin.clear(); // put us back in 'normal' operation mode
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); // and remove the bad input
}

Finally, use loops to ask the user to re-enter input if the original input was invalid.

Author’s note

Input validation is important and useful, but it also tends to make examples more complicated and harder to follow. Accordingly, in future lessons, we will generally not do any kind of input validation unless it’s relevant to something we’re trying to teach.

cin wrong input

hi guys I am trying to figure out a way to check for the correct input for example if I type steve in for id it will not work as id is an int,

I thought my code would work but it clearly doesn’t

what I thought was happening while input equals false we get trapped in a while loop so we enter steve in so i expect cin.bad() to be set to true so the first if statement shouldn’t run yet it seems to for some reason

and I expected the second if statement to run I used cin.clear to clear the state of the stream then ignore to ignore the input

but for some reason the code never gets this far,

what am I doing wrong and clearly I’m mistaken

thanks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

   vector<info> names;
   int id;
   string name,department;
   int choice;

   bool input1 = false;
        while(!input1){
        cout << "enter your ID" << endl;
        cin >> id;
        if(!cin.bad()){

           input1 = true;
        }
        if(cin.bad()){

            cin.clear();
            cin.ignore();

        }

std::basic_ios::bad is used when there is an unrecoverable error with the stream, std::basic_ios::fail can be used to do what you want.

See the example here:
http://en.cppreference.com/w/cpp/io/basic_ios/fail

so whats the difference?

isn’t reading a string to an int an unrecoverable error?

ps do you know any good sources I can study the flags of the iostream,I searched online and couldn’t find any good sources

thanks

isn’t reading a string to an int an unrecoverable error?

Not really. It is possible to recover from this error by clearing the stream error flags and completely cleaning out the input buffer.

Your code is only removing one character from the input buffer, you need to tell ignore() how many characters to remove(usually using numeric limits max) as the numeric value and the end of line character as the optional second parameter.

By the way you can check the status of all the flags by just using the stream name if(std::cin).

> sources I can study the flags of the iostream

https://stdcxx.apache.org/doc/stdlibug/29-1.html
https://stdcxx.apache.org/doc/stdlibug/29-2.html

It is canonical to check for the success or failure of attempted input by using the returned (reference to the) stream as the condition in if/loop statements.

See: http://en.cppreference.com/w/cpp/io/basic_ios/operator_bool

This operator makes it possible to use streams and functions that return references to streams as loop conditions, resulting in the idiomatic C++ input loops such as
while(stream >> value) {…} or while(getline(stream, string)){…}.
Such loops execute the loop’s body only if the input operation succeeded.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

int get_int()
{
    int value ;
    if( std::cin >> value ) return value ; // success: an int was read

    // badly formed input: failed to read an int
    std::cout << "please enter en integer: " ; // inform the user
    std::cin.clear() ; // clear the failed state of the stream
    std::cin.ignore( 1000000, 'n' ) ; // extract and discard the bad input
    return get_int() ; // try again
}

int get_int( int minv, int maxv )
{
    int value = get_int() ;
    if( value >= minv && value <= maxv ) return value ;

    // out of range
    std::cout << "please enter a value in [" << minv << ", " << maxv << "]: " ;
    return get_int(minv,maxv) ;
}

int main()
{
    std::cout << "id [0,999]?: " ;
    const int id = get_int(0,999) ;
    std::cout << "id is " << id << 'n' ;
}

thanks guys

I tried ignoring a large number followed by a newline as the params to ignore but it doesn’t seem to work the way I expected it pretty much just goes to a new line and waits for input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


while(!input1){

            cout << "enter id" << endl;
            cin >> id;

            if(cin.fail()){

                cin.clear();
                cin.ignore();
            }else{

                 input1 = true;

            }
        }

also another snippet of code to explain my question below

another question relating to this is what would cause a stream to go into an unrecoverable error or the cin.bad() to be true?

and for some reason when I print out the failbit in both cases it prints 4,why 4? and I thought that it would be 0 if the read is successful yet it still prints 4

thanks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

while(!input1){
        cout << "enter your ID" << endl;
        if(cin >> id){

            cout << cin.failbit << endl;
            input1 = true;
        }else{
           cout << cin.failbit << endl;
           cin.clear();
           cin.ignore(10000, 'n');
        }
        }

thnaks JL

Topic archived. No new replies allowed.


Recommended Answers

switch(...) { }
// The switch ends
while( expr ) ; // Do nothing
// Forever...

Jump to Post

Here is a shell that shows one popular student way to start a menu / choice / switch type program.

Note the use of functions to facilitate your logic flow and code developement testing steps.

Note the use of getline for input so that the WHOLE line is input.

       

Jump to Post

Sorry forgot you were using int. By changing difficulty to char and modifying the switch block to check for char cin doesn’t enter an error state and the loop acts normally:

    switch (difficulty)
    {
    case '0':
        cout << "Thank you for playing.n" << endl;
        pass1=true;
        break; …

Jump to Post

All 9 Replies

Member Avatar

9 Years Ago

switch(...) { }
// The switch ends
while( expr ) ; // Do nothing
// Forever...

Member Avatar

9 Years Ago

Not sure exactly what was causing the problem. But caling it again in the default case is redundant. Encasing the switch block in a while loop and only calling cin once per loop will solve the problem. Something like this:

#include <iostream>
using namespace std;
int main()

{
    int difficulty;
    bool pass1 = false;
    cout << "Welcome to 'CAN YOU GUESS MY NUMBER'n" <<endl;
    while(!pass1)
    {
        cout << "Choose your difficulty: n" <<endl;
        cout << "1: Easy" <<endl;
        cout << "2: Normal" <<endl;
        cout << "3: Hardn" <<endl;
        cin >> difficulty;
        switch (difficulty)
        {
        case 1:
            cout << "You will play the game in easyn" << endl;
            pass1 = true;
            break;
        case 2:
            cout << "You will play the game in Normal difficultyn" << endl;
            pass1 = true;
            break;
        case 3:
            cout << "You will play the game in Hardn" << endl;
            pass1 = true;
            break;
        default:
            cout << "nYou have made an invalid choice." << endl;

        }
    }
    return 0;
}

Notice how using the Code option in the editor allows all your code to be displayed as code.

Edited

9 Years Ago
by tinstaafl

Member Avatar


David W

131



Practically a Posting Shark


9 Years Ago

Here is a shell that shows one popular student way to start a menu / choice / switch type program.

Note the use of functions to facilitate your logic flow and code developement testing steps.

Note the use of getline for input so that the WHOLE line is input.

#include <iostream>
#include <string>

using namespace std;


void lev1( )
{
    cout << "nnLevel 'Easy' is being developed ...n";
}
void lev2( )
{
    cout << "nnLevel 'Normal' is being developed ...n";
}
void lev3( )
{
    cout << "nnLevel 'Hard' is being developed ...n";
}

int showMenuGetChoice()
{
    cout << "nWelcome to 'CAN YOU GUESS MY NUMBER?'nn"
         << "Choose your difficulty: n"
         << "1: Easyn"
         << "2: Normaln"
         << "3: Hardn"
         << "0: Quitn"
         << "Enter your choice 0..3 : " << flush;
    string line;
    getline( cin, line );
    if( line.size( ) != 0 ) return line[0];
    // else ...
    return 0;
}

void pauseForEnter()
{
    cout << "nPress 'Enter' to continue ... " << flush;
    string line;
    getline( cin, line );
}


int main()
{
    bool done = false;
    do
    {
        switch( showMenuGetChoice() )
        {
            case '1' : lev1(); break;
            case '2' : lev2(); break;
            case '3' : lev3(); break;
            case '0' : done = true; break;
            default: cout << "nnYou have made an invalid choice.n";
        }
    }
    while( !done );

    pauseForEnter();

}

Member Avatar

9 Years Ago

@tinstaafl: your method is a lot more organize and logic is clearer, but I still get an infinite loop when the input for ‘difficulty ‘ is not an integer. I’m trying how to figure out how to ignore or make that last input clear or ignore the ‘difficult’ input when given a value so the loop doesn’t become infite.

#include <iostream>
using namespace std;
int main()

    //Difficulty Loop
    //infinite loop error.
{
    int difficulty;
    bool pass1 = false;
    cout << "Welcome to 'CAN YOU GUESS MY NUMBER'n" <<endl;
    while(!pass1)
    {
        cout << "Choose your difficulty: n" <<endl;

        cout << "1: Easy" <<endl;
        cout << "2: Normal" <<endl;
        cout << "3: Hard" <<endl;
        cout << "0: Quitn" <<endl;

        cout << "Enter your choice: 0-3:n"<< endl;

        cin >> difficulty;
        switch (difficulty)
        {
        case 0: 
            cout << "Thank you for playing.n" << endl;
            pass1=true;
            break;
        case 1:
            cout << "You will play the game in easyn" << endl;
            pass1 = true;
            break;
        case 2:
            cout << "You will play the game in Normal difficultyn" << endl;
            pass1 = true;
            break;
        case 3:
            cout << "You will play the game in Hardn" << endl;
            pass1 = true;
            break;
        default:
            cout << "nYou have made an invalid choice." << endl;
            pass1=false;
            break;
        }
    }
    return 0;
}

Member Avatar

9 Years Ago

default:
cout << "nYou have made an invalid choice." << endl;
return 0;

Member Avatar

deceptikon

1,790



Code Sniper



Administrator



Featured Poster


9 Years Ago

Consider this simple example of clearing the input line on an invalid conversion:

#include <ios>
#include <iostream>
#include <limits>

using namespace std;

int main()
{
    int num;

    while (true)
    {
        cout << "Enter a number: ";

        if (cin >> num)
        {
            break;
        }

        cerr << "Invalid inputn";

        // Clear the stream state so we can read again
        cin.clear();

        // Extract everything remaining on the line
        cin.ignore(numeric_limits<streamsize>::max(), 'n');
    }

    cout << "You entered " << num << 'n';
}

When a conversion by the >> operator fails, it puts cin into an error state where it won’t read anymore data. Also, the bad data is left on the stream. So you need to clear the error state and then discard the bad data before trying again.

Member Avatar

9 Years Ago

Sorry forgot you were using int. By changing difficulty to char and modifying the switch block to check for char cin doesn’t enter an error state and the loop acts normally:

    switch (difficulty)
    {
    case '0':
        cout << "Thank you for playing.n" << endl;
        pass1=true;
        break;        
    case '1':
        cout << "You will play the game in easyn" << endl;
        pass1 = true;
        break;
    case '2':
        cout << "You will play the game in Normal difficultyn" << endl;
        pass1 = true;
        break;
    case '3':
        cout << "You will play the game in Hardn" << endl;
        pass1 = true;
        break;
    default:
        cout << "nYou have made an invalid choice." << endl;
    }

Edited

9 Years Ago
by tinstaafl

Member Avatar

9 Years Ago

He said,

but I still get an infinite loop when the input for ‘difficulty ‘ is not an integer.

Meaning he intends for the loop to end when difficulty is not an integer.

Member Avatar


David W

131



Practically a Posting Shark


9 Years Ago

You seem to be experiencing a very common ‘beginner design problem’ … due to a lack of understanding about how cin (stream input) works … especially when numeric input is expected … and what then happens when cin enters a ‘failed’ state.

cin skips over all leading ws (white space) char’s and tests the first non-ws char to see if it is of the type expected.

For example:

If an int (type) was being expected, and the 1st non-ws char is a + or — sign or a digit in the range 0..9 … all is still ok … and cin will keep taking in valid digits, from the cin stream, until it reaches a non-valid-digit-including-a-ws ( ws includes ‘ ‘, ‘t’, ‘n’ )

If the integer taken in was within the size limits for an int, no error flags are set … all is ‘good’

Other-wise … a cin error flag is set and cin enters into a ‘failed state’ … and stays in that failed state and will not accept any more input until ‘cleared’ via cin.clear() … so any char’s that were entered after the failed char and before the ‘Enter’ key was pressed will still be there in the cin stream

This following little demo may give you some more ideas about ways to deal with cin (single number) input.

// cinErrorDemo.cpp //

#include <iostream>


using namespace std;


char getCharReply( const std::string& msg )
{
    std::cout << msg << std::flush;
    std::string reply;
    getline( std::cin, reply );
    if( reply.size() ) return reply[0];
    // else ...
    return 0;
}

bool more()
{
    if( tolower( getCharReply( "nMore (y/n) ? " )) == 'n' ) return false;
    // else ...
    return true;
}


// prompt and take in and return a valid int
// default prompt is ""
int takeInValidInt( const std::string& prompt = "" ) 
{
    int val;
    while( true )
    {
        std::cout << prompt << std::flush;
        if( std::cin >> val )
        {
            if( std::cin.get() == 'n' )
                break; // break out of while loop right NOW
            // else
            std::cout << "nThere were extra char's entered ...n";
        }
        else
        {
            if( !std::cin.good() ) std::cout << "good is not true ...n";
            if( std::cin.fail() ) std::cout << "fail is true ...n";

            std::cin.clear(); // clear flags ...
        }
        std::cin.sync(); // 'flush' cin stream...
        std::cout << "Try again ...";
    }
    return val;
}



int main()
{
    do
    {
        int myInt = takeInValidInt( "Enter an integer: " );
        cout << "You entered " << myInt << "n";
    }
    while( more() );
}

Note that taking in ‘line input’ via C++ getline and C++ string …
then parsing that line (that string …perhaps using stringstream) …
can also be useful to avoid input loops crashing on bad input …
and also avoids the problem of ‘dangling left over chararacters’

Note that char input will NOT crash because of bad input as per this updated example:

// getline.cpp //

#include <iostream>
#include <string>

using namespace std;


void lev1()
{
    cout << "nn'Easy' is being developed ...n";
}
void lev2()
{
    cout << "nnNormal' is being developed ...n";
}
void lev3()
{
    cout << "nn'Hard' is being developed ...n";
}

int showMenuGetChoice()
{
    cout << "nWelcome to 'CAN YOU GUESS MY NUMBER?'nn"
         << "Choose your difficulty: n"
         << "1: (E)asyn"
         << "2: (N)ormaln"
         << "3: (H)ardn"
         << "0: (Q)uitn"
         << "Enter your choice 0..3 or e..q: " << flush;
    string line;
    getline( cin, line );
    if( line.size() != 0 ) return line[0];
    // else ...
    return 0;
}

void pauseForEnter()
{
    cout << "nPress 'Enter' to continue ... " << flush;
    string line;
    getline( cin, line );
}


int main()
{
    bool done = false;
    do
    {
        switch( showMenuGetChoice() )
        {
            case '1' : case 'e' : case 'E' : lev1(); break;
            case '2' : case 'n' : case 'N' : lev2(); break;
            case '3' : case 'h' : case 'H' : lev3(); break;
            case '0' : case 'q' : case 'Q' : done = true; break;
            default: cout << "nnInvalid choice ... try again.n";
        }
    }
    while( !done );

    pauseForEnter();
}

Note:

some times you want cin to take in several numbers on a line …

or from a file, right up to the end of that file …

so this above single number input code, to take in a single valid number from a user via keyboard, would not apply there.


Reply to this topic

Be a part of the DaniWeb community

We’re a friendly, industry-focused community of developers, IT pros, digital marketers,
and technology enthusiasts meeting, networking, learning, and sharing knowledge.

Здравствуйте, дорогие читатели!

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

В работе над этой статьей, я использовал Microsoft Visual Studio 2008 Standard с установленным SP1.

Итак, приступим.

Для наших экспериментов, возьмем очень простой код:

#include <iostream>
#include <conio.h>

using namespace std;

int main()

       int a, b; 
       // Вводим первое число 
       cout << «Enter «A»: «
       cin >> a; 
       cout << «You have entered: « << a << endl; 
 

       // Вводим второе число 
       cout << «Enter «B»: «
      
cin >> b; 
       cout << «You have entered: « << b << endl << endl; 
 

       // Do something with a and b 
 
       cout << «Thanks a lot. Press any key for exit.»
<< endl; 
       while(!_kbhit());
}  

Код очень простой, но я все же поясню, что тут происходит.
Мы объявили две переменные типа Integer и назвали их «a» и «
b», соответственно. Далее, мы два раза просим пользователя ввести число, после чего выводим его на экран. А последняя строчка нашей главной функции программы, просто ждет нажатия любой клавиши от пользователя.

Для начала, посмотрим как выглядит корректная работа программы.

image

А теперь, давайте посмотрим, что произойдет, если мы введем вместо первого числа, какой-нибудь символ.

image

Как вы видите, программа естественно сработала не правильно.

Проверка ввода

Итак, давайте сделаем проверку ввода. Для этого слегка изменим исходный код нашей программы.
Мы добавим следующий код, после строчки «
cin >> a;» :

if (cin.fail())

{

       cout << «Incorrect input.» << endl;

       return 1;

}

Таким образом, мы сразу же после ввода смотрим, была ли ошибка во время чтения из входного потока. И если это так, то выводим текст об ошибке и выходим из программы.

Казалось бы, что все, но нет. Давайте разберемся, почему:

   1. Программа показывает нам число «-858993460», когда пользователь вводит текст

   2  Что хранится во входном потоке.

   3.  Почему пользователю не предоставилась возможность ввести второе число и как это исправить.

Почему -858993460?

Ответ на этот вопрос прост. В Visual Studio, этим значением, инициализируются неинициализированные локальные переменные. Это если компилировать программу в Debug-режиме. А в Release сборке, числа будут случайными.

Как это происходит? Когда пользователь вводит текст, то естественно, строчка “cin >> a;” выполняется с ошибкой, потому, что ожидается число. Естественно, в переменную a ничего не записывается и происходит ее неявная инициализация.

Для того, чтобы в переменной, при не правильном вводе хранилось какое-то число(отличное от
-858993460), то можно либо в блоке, где мы проверяем входной поток на ошибки, добавить явную инициализацию ( “a = -1;” ), либо инициализировать переменную при объявлении. Таким образом в переменной уже будет храниться число и оно не измениться при некорректном вводе.

Что храниться во входном потоке?

Ну, а это самый просто вопрос. Что пользователь вводил, то и храниться.

Как заставить работать второй пользовательский ввод?

Для начала объясню почему не удается считать данные от пользователя второй раз. Этого не удается сделать, потому как в буфере уже содержаться данные и биты состояния входного потока установлены в состояние ошибки. Для того, чтобы заставить сработать второй пользовательский ввод, слегка модифицируем наш код проверки. После модификации, он будет выглядеть следующим образом:

if (cin.fail())

{

       cout << «Incorrect input.» << endl;

       cin.clear();

       while(cin.get()!=‘n’);

} else {

       cout << «You have entered: « << a << endl;

}

Посмотрим, что мы изменили. Мы добавили вызов cin.clear(). С помощью этой функции, мы восстановили флаги состояния потока, в состояние по умолчанию. Тем самым разрешили читать его содержимое. А с помощью цикла while, мы вытаскиваем из входного буфера по одному символу, пока не дойдем до символа перевода строки. Этим способом, мы очищаем входной буфер.
Почему цикл, а не просто cin.get()? Да потому, что пользователь может ввести не один символ, а сразу несколько.

И, вуаля! Второй пользовательский ввод начал работать!

image

Вот собственно и все! Надеюсь, что изложил доступно. Пишите, если будут какие-то вопросы, пожелания.

Спасибо за внимание! Надеюсь статья будет Вам полезна! До скорых встреч!

Понравилась статья? Поделить с друзьями:
  • C builder error detected lme200
  • C 4521 ошибка коника
  • C 32 11 ошибка камера сони
  • C 3102 ошибка konica minolta
  • C 1221 ошибка опель