Большинство программ должны взаимодействовать с пользователем посредством ввода определённых данных, будь то ФИО, рост, вес для внесния в базу данных или геометрические размеры какого-то объекта, для которого нужно что-то рассчитать. Все эти данные вводит пользователь - человек, а значит в ответ может придти всё что угодно. Что выдаст программа, если вместо требуемого ей возраста пользователь напишет его словом? Скорее всего программа аварийно завершится или зависнет, но только не в том случае, если в ней предусмотрена "защита от дурака".
Почему программа может завершиться или зависнуть? Программа попытается перевести набор символов в число, что сделать не удастся, а значит дальнейшая работа приложения не определена. Поэтому очень важно организовывать структуру программы так, чтобы при вводе неожиданных для программы данных (некорректных с точки зрения требуемого формата: нужно число, а вводится слово), приложение не "падало", а сообщало пользователю о том, что произошла ошибка и предлагало повторить ввод. Это и есть "защита от дурака".
Реализация защиты от дурака на языке 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', которые уж очень часто заставляют потратить на поиск ошибки ни один час или даже день.
