Контроль ошибок при вводе с

Jino_

2 / 2 / 1

Регистрация: 26.06.2010

Сообщений: 37

1

Контроль ошибок при вводе

07.10.2010, 00:56. Показов 5078. Ответов 14

Метки нет (Все метки)


Студворк — интернет-сервис помощи студентам

есть, например, код

C++
1
2
3
4
5
6
7
8
#include <iostream>
int main()
{
int i;
cin >> i;
 
return 0;
}

как проконтролировать, чтобы пользователь вводил в поток именно цифры, а не другие символы?



0



ForEveR

В астрале

Эксперт С++

8048 / 4805 / 655

Регистрация: 24.06.2010

Сообщений: 10,562

07.10.2010, 00:59

2

Jino_,

C++
1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
int i;
cin >> i;
if(cin.fail())
   std::cerr<<"Only digits!n"; 
return 0;
}

Или.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <iterator>
 
int main()
{
    int i=0;
    std::istream_iterator<int>  InIt(std::cin);
    std::istream_iterator<int>StopIt;
    if(InIt!=StopIt)
    std::cout<<*InIt<<'n';
    else
        std::cout<<"Only integer!n";
    return 0;
}



1



Jino_

2 / 2 / 1

Регистрация: 26.06.2010

Сообщений: 37

08.10.2010, 16:24

 [ТС]

3

а как сделать вызов функции, в том случае если пользователь ввел не число?

C++
1
2
3
4
5
6
7
8
9
10
11
int i;
void func()
{
    cout << "Input: ";
    cin >> i;
    if(cin.fail()) {
    func();
    }
 
 
}

такой вариант зацикливается



0



gooseim

Эксперт С++

516 / 421 / 92

Регистрация: 23.09.2010

Сообщений: 1,165

08.10.2010, 16:38

4

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <limits>
 
using namespace std;
 
int i;
void func()
{
    cout << "Input: ";
    cin >> i;
    if(cin.fail()) {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), 'n');
        func();
    }
 
 
}
 
int main()
{
func();
return 0;
}

Только надо иметь ввиду, что строку 123qwer cin воспримет как 123 и ошибки не будет.



0



Jino_

2 / 2 / 1

Регистрация: 26.06.2010

Сообщений: 37

09.10.2010, 12:28

 [ТС]

5

Цитата
Сообщение от gooseim
Посмотреть сообщение

C++
1
2
3
4
5
    if(cin.fail()) {
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), 'n');
        func();
    }

объясни тут пожалуйста, все методы cin

Цитата
Сообщение от gooseim
Посмотреть сообщение

Только надо иметь ввиду, что строку 123qwer cin воспримет как 123 и ошибки не будет.

а как это исправить?



0



В астрале

Эксперт С++

8048 / 4805 / 655

Регистрация: 24.06.2010

Сообщений: 10,562

09.10.2010, 15:13

6

Jino_, Итератор использовать например.



0



robert19

29 / 29 / 7

Регистрация: 26.03.2010

Сообщений: 305

09.10.2010, 23:36

7

Я так использую (пример):

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
        
    while(true){
        cout<<"n1. Продолжить";
        cout<<"n2. Выходn";
        cout<<"n Ввод: ";
        int x;
        cin>>x;
        if (x != 1 && x != 2 ){
            cin.clear();
            fflush(stdin);
            continue;
                       }
            }



0



В астрале

Эксперт С++

8048 / 4805 / 655

Регистрация: 24.06.2010

Сообщений: 10,562

10.10.2010, 00:08

8

robert19, Ну тут то разговор про тип значения)



0



robert19

29 / 29 / 7

Регистрация: 26.03.2010

Сообщений: 305

10.10.2010, 00:22

9

Может так тогда))))))

C++
1
2
3
4
5
6
7
8
9
10
11
12
while(true){
           cout<<"n1. Продолжить";
           cout<<"n2. Выходn";
           cout<<"n Ввод: ";
           int x;
           cin>>x;
           if (x < '0' || x > '9'){
               cin.clear();
               fflush(stdin);
               continue;
           }
}



0



ForEveR

В астрале

Эксперт С++

8048 / 4805 / 655

Регистрация: 24.06.2010

Сообщений: 10,562

10.10.2010, 00:43

10

robert19, Ну введите более 9 и посмотрим.

Добавлено через 11 минут

C++
1
if (x < '0' && x > '9')

Тогда уж так. Но тоже нехорошо.

Чем итераторы то плохи?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <iterator>
 
using namespace std;
 
int main()
{
    std::istream_iterator<int> InIt(std::cin);
    std::istream_iterator<int> StopIt;
    
    while(InIt!=StopIt)
    {
        std::cout<<*InIt<<'n';
        InIt++;
    }
    return 0;
}



0



Jino_

2 / 2 / 1

Регистрация: 26.06.2010

Сообщений: 37

10.10.2010, 20:20

 [ТС]

11

Цитата
Сообщение от Lavroff
Посмотреть сообщение

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <iterator>
 
using namespace std;
 
int main()
{
    std::istream_iterator<int> InIt(std::cin);
    std::istream_iterator<int> StopIt;
    
    while(InIt!=StopIt)
    {
        std::cout<<*InIt<<'n';
        InIt++;
    }
    return 0;
}

объясни, пожалуйста этот код.
не особо понимаю



0



В астрале

Эксперт С++

8048 / 4805 / 655

Регистрация: 24.06.2010

Сообщений: 10,562

10.10.2010, 20:36

12

Jino_, istream_iterator — итератор ввода
Первый — итератор ввода для типа int, который инициализируется потоком ввода cin
Второй — итератор ввода для типа int, пустой, используется как условие выхода
До тех пор пока первый итератор не равен второму (если мы введем что-то кроме int они будут равны)
Печатаем то, что ввели (если это цифра, иначе цикл прерывается)
Переводим итератор на ввод следующего числа.

Работает так.
Сразу предлагается ввести число. Ввели. Если правда число — печать числа. Предложение следующего ввода. И так до тех пор пока не ввели не число. Как только не число — завершается цикл и программа соответственно



1



Jino_

2 / 2 / 1

Регистрация: 26.06.2010

Сообщений: 37

17.10.2010, 18:10

 [ТС]

13

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

такой вариант (отличающийся только условием выхода из цикла) зацикливается

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <iterator>
 
using namespace std;
 
int main()
{
    istream_iterator<int> InIt(std::cin);
    istream_iterator<int> StopIt;
    
    while(InIt==StopIt)
    {
        cout<<*InIt<<'n';
        InIt++;
    }
    return 0;
}



0



Mr.X

Эксперт С++

3222 / 1749 / 435

Регистрация: 03.05.2010

Сообщений: 3,867

17.10.2010, 20:31

14

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
////////////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <sstream>
#include <string>
////////////////////////////////////////////////////////////////////////////////////////
typedef std::string  T_str;
////////////////////////////////////////////////////////////////////////////////////////
int main()
{
    std::locale::global(std::locale(""));
    T_str  s;    
    int    i;
    bool   res = false;
    do
    {
        std::cout << "Введите целое число i: ";
        std::cin >> s;      
        std::istringstream  ssin(s);
        res = ssin >> i;
    }while(!res);
    std::cout << "i = "
              << i
              << std::endl;    
}



1



2 / 2 / 1

Регистрация: 26.06.2010

Сообщений: 37

18.10.2010, 19:44

 [ТС]

15

вот это круто

теперь объясни, пожалуйста как оно работает?



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Для начинающихОбнаружение ошибокОбработка ошибокОбучениеПрограммирование

#Руководства

  • 4 июн 2020

  • 12

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

 vlada_maestro / shutterstock

Евгений Кучерявый

Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.

Это четвёртая часть из серии статей «Глубокое погружение в C++». В прошлый раз мы познакомились с ветвлением и условными конструкциями. Сейчас поговорим о том, как сделать программу более полезной за счёт ввода данных и обработки исключений.

Если программа работает с изначально заданными данными, то она полезна только один раз, потому что результат будет всегда один и тот же. Гораздо эффективнее она становится, когда можно использовать разные данные:

  • числа для вычисления;
  • команды пользователя;
  • изображения;
  • аудио и видео;
  • текст и так далее.

Как получить и обработать данные через консоль? Давайте пробовать.

В самом начале кода каждой программы мы подключаем библиотеку iostream Input/Output Stream (поток ввода/вывода). Именно в ней находится команда cout, что позволяет выводить данные на экран консоли. В ней же есть команда cin, которая, наоборот, запрашивает пользовательский ввод.

Давайте напишем простую программу, которая спрашивает имя пользователя.

#include <iostream>

int main()
{
    std::string name;

    std::cout << "Enter your name: ";

    std::cin >> name;

//С помощью одной команды cout можно вывести несколько значений
    std::cout << "Hello, " << name << "! n";
}

Сначала мы объявили переменную name строкового типа, а потом сказали пользователю, что именно нужно ввести, и в конце выдали сообщение «Hello, %name%!». Это выглядит так:

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

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

int a, b, c;

std::cout << "Enter a: ";
std::cin >> a;

std::cout << "Enter b: ";
std::cin >> b;

c = a + b;

std::cout << a << " + " << b << " = " << c;

Получается простейший калькулятор:

Это работает с символами, числами с плавающей запятой и другими данными.

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

Допустим, нам нужно, чтобы пользователь ввёл свой возраст. Мы ожидаем число вроде 8, 15 или 21, но кто-то может ввести эти числа прописью, например «двадцать один». Для программы эти варианты будут неожиданными, потому что она уже подготовила переменную типа int строка в неё никак не влезет.

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

В C++ самый простой способ конвертировать строку в число — использовать функцию stoi () или аналогичную:

std::string ageInput;
int age;

std::cout << "How old are you: ";
std::cin >> ageInput;

//В круглых скобках функции указывается значение, которое нужно конвертировать
age = stoi(ageInput);

std::cout << "You are " << age << " years old. n";

Вот пример корректной и некорректной конвертации:

Как видно на скриншоте, в первом случае всё прошло успешно, но в следующих случаях программа выдала ошибку. Давайте рассмотрим последнюю:

Разберём некоторые моменты подробнее:

  • Исключение (exception) — это что-то вроде ошибки, но не совсем. Разные части программы могут «выбрасывать» их при определённых условиях, чтобы сказать: что-то идёт не так; в данном случае — out of range (выход из диапазона). Это исключение было выброшено, потому что введённое число больше, чем может поместиться в переменную типа int.
  • Дамп памяти (memory dump) — это содержимое рабочей памяти одного процесса, ядра или всей операционной системы. Дамп можно посмотреть, чтобы понять, каким было состояние программы и почему она аварийно завершила работу.

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

Для этого нам пригодится конструкция try-catch.

Вот код программы, которая проверяет корректность введённых данных:

std::string ageInput;
int age = 0;

std::cout << "How old are you: ";
std::cin >> ageInput;

//Названия исключений можно посмотреть в сообщениях об ошибке
try
{
    age = stoi(ageInput);
}
catch(std::invalid_argument) 
{
    //Говорим, что можно вводить только числа
    std::cout << "Only numbers are allowed! n";
}
catch(std::out_of_range)
{
    //Говорим, что число слишком большое
    std::cout << "You can't be that old! n";
}
catch(...)
{
    //Если будет выброшено какое-то исключение, которое не обработано выше, то говорим, что возникла неизвестная ошибка
    std::cout << "Unknown error! n";
}

if(age > 0)
{
    std::cout << "You are " << age << " years old. n";
}
else
{
    std::cout << "Try again! n";
}

Теперь можно запустить её и попробовать ввести любые значения — программа определит, какое сообщение вывести, и благополучно завершится.

С помощью пользовательского ввода можно по-настоящему раскрыть возможности условных конструкций. Например, конструкция switch позволяет создать простое консольное меню.

int action = 0;

//Выводим меню
std::cout << "===Calculator++==n1 - Additionn2 - Subtractionn3 - Divisionn4 - Multiplicationn5 - Quadratic equationnAction: ";

//Допускаем, что ввод будет верным
std::cin >> action;

switch(action)
{
    case 1:
   	 //Сложение
   	 break;

    case 2:
   	 //Вычитание
   	 break;

    case 3:
   	 //Деление
   	 break;

    case 4:
   	 //Умножение
   	 break;

    case 5:
   	 //Квадратные уравнения
	std::cout << 
   	 break;
}

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

Конструкция if пригодится для решения квадратных уравнений:

double a, b, c, d, x1, x2;

std::cout << "ax^2 + bx + c = 0n";

std::cout << "a = ";
std::cin >> a;

std::cout << "b = ";
std::cin >> b;

std::cout << "c = ";
std::cin >> c;

d = (b * b) - 4 * a * c;

std::cout << "d = " << b << "^2 - 4 * " << a << " * " << c << "n";
std::cout << "d = " << d << "n";

if(d > 0)
{
    //Для работы функции sqrt() нужно подключить библиотеку cmath
    x1 = (-b + sqrt(d)) / (2 * a);
    x2 = (-b - sqrt(d)) / (2 * a);

    std::cout << "x1, x2 = (-" << b << " +- sqrt(" << d << ")) / (2 * " << a <<")n";
    std::cout << "x1 = " << x1 << "nx2 = " << x2 << "n";
}
else if(d == 0)
{
    x1 = -(b / (2 * a));
    std::cout << "x1, x2 = -(" << b << " / (2 * " << a << ")n";
    std::cout << "x1, x2 = " << x1 << "n";
}
else if(d < 0)
{
    std::cout << "No roots! n";
}

Такие программы сильно упрощают жизнь не только учёным и инженерам, но и школьникам:

Мы научились запрашивать данные у пользователя, проверять, правильного ли они типа, и впервые воспользовались функцией. В следующей статье научимся писать собственные функции.

Научитесь: Профессия Разработчик на C++ с нуля
Узнать больше

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

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

В работе над этой статьей, я использовал 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

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

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

1 Августа 2017

Время чтения: 3 минуты

Превью к статье об организации защиты от дурака в программах

Большинство программ должны взаимодействовать с пользователем посредством ввода определённых данных, будь то ФИО, рост, вес для внесния в базу данных или геометрические размеры какого-то объекта, для которого нужно что-то рассчитать. Все эти данные вводит пользователь — человек, а значит в ответ может придти всё что угодно. Что выдаст программа, если вместо требуемого ей возраста пользователь напишет его словом? Скорее всего программа аварийно завершится или зависнет, но только не в том случае, если в ней предусмотрена «защита от дурака».

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

Реализация защиты от дурака на языке C

Чтобы реализовать хорошую защиту от дурака для ввода различных числовых (int, double…) данных, необходимо считывать не сами числа, а всю вводимую строку и уже потом анализировать ввод. В языке C есть очень хорошая функция sscanf(const char *, const char *, args), которая работает аналогично функции scanf(const char *, args), возвращая количество успешно считанных аргументов, только чтение данных происходит не из стандартного потока ввода, а из переданной ей первым аргументом строки.

Рассмотрим несколько примеров функций, которые реализуют проверку на дурака, используя функцию sscanf.

Ввод целого числа с проверкой на некорректный ввод

int get_integer(const char *msg) {
    char answer[256]; // строка для чтения
    int n; // итоговое целое число

    printf("%s", msg); // выводим приглашение ко вводу
    fgets(answer, sizeof(answer), stdin); // считываем строку

	// пока не будет считано целое число
    while (sscanf(answer, "%d", &n) != 1) {
        printf("Incorrect input. Try again: "); // выводим сообщение об ошибке
        fgets(answer, sizeof(answer), stdin); // и заново считываем строку
    }

    return n; // возвращаем корректное целое число
}

Чтобы считать целое число, алгоритм считывает всю строку и затем пытаеся получить из неё целое число. В том случае, если это не удалось, функция выводим сообщение об ошибке с предложением повторить ввод до тех пор, пока не будет введено корректное значение целого числа.

Ввод вещественного числа с проверкой на некорректный ввод

double get_double(const char *msg) {
    char answer[256];  // строка для чтения
    double x; // итоговое вещественное число

    printf("%s", msg); // выводим приглашение ко вводу
    fgets(answer, sizeof(answer), stdin); // считываем строку

	// пока не будет считано вещественное число
    while (sscanf(answer, "%lf", &x) != 1) {
        printf("Incorrect input. Try again: "); // выводим сообщение об ошибке
        fgets(answer, sizeof(answer), stdin); // и заново считываем строку
    }

    return x; // воозвращаем корректное вещественное число
}

Ввод точки на координатной плоскости (структура с двумя вещественными полями)

// описание структуры даных
typedef struct point_t {
    double x; // координата x
    double y; // координата y
} point_t;

point_t get_point(const char *msg) {
    char answer[256]; // строка для чтения
    point_t point; // итоговая точка

    printf("%s", msg); // выводим приглашение ко вводу
    fgets(answer, sizeof(answer), stdin); // считываем строку

    // пока не будут считаны обе координаты точки
    while (sscanf(answer, "(%lf,%lf)", &point.x, &point.y) != 2) {
        printf("Incorrect input. Try again: "); // выводим сообщение об ошибке
        fgets(answer, sizeof(answer), stdin); // и заново считываем строку
    }

    return point; // возвращаем корректную точку
}

Как видно из примеров, особенность возвращения функцией sscanf числа считанных аргументов позволяет контролировать корректность введённых данных в указанном формате, а считывание всей строки защищает от того, что в потоке ввода останутся символы пробела или переноса строки 'n', которые уж очень часто заставляют потратить на поиск ошибки ни один час или даже день.

Фото Кудиновой Светланы, автора этой статьи

Программист, соосновательница programforyou.ru, рукодельница, всегда готова придти на помощь и помочь во всём разобраться

Языки программирования: Python, C, C++, Pascal

Выпускница МГТУ им. Н.Э. Баумана

Возможно, вам также будет интересно:

  • Контроль ошибок оператора человека оператора
  • Контроль ошибок на сетевом уровне
  • Контроль направленный на предупреждение ошибок
  • Контроль нагрева датчика кислорода ошибка
  • Контроль качества в лаборатории ошибки

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии