Обработка некорректного пользовательского ввода в C++ / Ravesli

Как организовать защиту от некорректного ввода в программах на C и C++ простым и действенным способом

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

Прежде чем разбираться с обработкой некорректного ввода через std::cin и оператор >>, давайте сначала рассмотрим их принцип работы.

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

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

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

   Если во входном буфере есть данные, то эти данные используются для извлечения.

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

   Оператор >> извлекает столько данных из входного буфера в переменную, сколько позволяет размер самой переменной (игнорируя любые пробелы, табы или n).

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

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

Если пользователь введет 7d, то 7 будет извлечено, преобразовано в целое число и присвоено переменной a. А dn останется во входном буфере дожидаться следующего извлечения.

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

Если бы пользователь ввел z, то извлечение не выполнилось бы, так как z не может быть извлечено в целочисленную переменную.

Элемент управления MaskedTextBox

Если требуется, чтобы пользователи вводили данные в четко определенном формате, например номер телефона или артикул, это можно сделать быстро и с минимальным количеством кода с помощью элемента управления MaskedTextBox. Маска — это строка, состоящая из символов языка маскирования, которые указывают, какие символы можно вводить в каждой заданной позиции в текстовом поле. Элемент управления отображает набор запросов пользователю. Если пользователь вводит неправильное значение, например, вводит букву, когда требуется цифра, элемент управления автоматически отклоняет введенные данные.

Язык маскирования, используемый MaskedTextBox, является очень гибким. Он позволяет указать обязательные символы, необязательные символы, литералы, такие как дефисы и скобки, символы валют и разделители дат. Этот элемент управления также хорошо работает при привязке к источнику данных. Событие Format в привязке данных можно использовать для переформатирования входящих данных в соответствии с маской, а событие Parse можно использовать для переформатирования исходящих данных в соответствии со спецификациями поля данных.

Реализация защиты от дурака на языке 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

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

Ввод целых значений без знака

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

unsigned num;

scanf(“%u”, &num);

выполняют считывание целого числа без знака и присваивают его переменной num.

Чтение одиночных символов с помощью scanf()

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

При чтении одиночного символа символы разделителей читаются так же, как и любой другой символ, хотя при чтении данных других типов разделители интерпретируются как разделители полей. Например, при вводе с входного потока “x y” фрагмент кода

scanf(“%c%c%c”, &a, &b, &c);

помещает символ x в a, пробел — в b, а символ y — в c.

Для чтения из входного потока строки можно использовать функцию scanf() со спецификатором преобразования %s. Использование спецификатора преобразования %s заставляет scanf() читать символы до тех пор, пока не встретится какой-либо разделитель. Читаемые символы помещаются в символьный массив, на который указывает соответствующий аргумент, а после введенных символов еще добавляется символ конца строки (‘0’). Что касается scanf(), то таким разделителем может быть пробел, разделитель строк, табуляция, вертикальная табуляция или подача страницы. В отличие от gets(), которая читает строку, пока не будет нажата клавиша <ENTER>, scanf() читает строку до тех пор, пока не встретится первый разделитель. Это означает, что scanf() нельзя использовать для чтения строки “это испытание”, потому что после пробела процесс чтения прекратится. Чтобы увидеть, как действует спецификатор %s, попробуйте при выполнении этой программы ввести строку “привет всем”:

#include <stdio.h>

int main(void)

{

char str[80];

printf(“Введите строку: “);

scanf(“%s”, str);

printf(“Вот Ваша строка: %s”, str);

return 0;

}

Программа выведет только часть строки, то есть слово привет.

Для ввода какого-либо адреса памяти используйте спецификатор преобразования %p. Этот спецификатор преобразования заставляет функцию scanf() читать адрес в том формате, который определен архитектурой центрального процессора. Например, следующая программа вначале вводит адрес, а затем отображает то, что находится в памяти по этому адресу:

#include <stdio.h>

int main(void)

{

char *p;

printf(“Введите адрес: “);

scanf(“%p”, &p);

printf(“По адресу %p находится %cn”, p, *p);

return 0;

}

Спецификатор %n указывает, что scanf() должна поместить количество символов, считанных (до того момента, когда встретился %n) из входного потока в целую переменную, указанную соответствующим аргументом.

Основные типы некорректного пользовательского ввода

Мы выделили 4 типа:

   Извлечение выполняется успешно, но значения бесполезны для программы (например, вместо математического оператора введен символ k).

   Извлечение выполняется успешно, но пользователь вводит лишний текст (например, *q hello вместо одного символа математического оператора).

   Извлечение не выполняется (например, вместо числового значения ввели символ q).

   Извлечение выполняется успешно, но пользователь ввел слишком большое числовое значение.

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

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

См. также

  • Использование событий клавиатуры (Windows Forms .NET)
  • Control.Validating
  • Form.FormClosing
  • System.Windows.Forms.FormClosingEventArgs
Рейтинг
( 1 оценка, среднее 5 из 5 )
Загрузка ...