Поток ostream имеет точно такие же состояния, как и поток
istream: good()
,
fail()
,
eof()
и
bad()
. Однако в таких программах, которые мы описываем в этой книге, ошибки при выводе встречаются намного реже, чем при вводе, поэтому мы редко их проверяем. Если вероятность того, что устройство вывода недоступно, переполнено или сломано, является значительной, то в программе следует предусмотреть проверку состояния потока вывода после каждой операции вывода, так как мы сделали выше по отношению к операции ввода.
10.7. Считывание отдельного значения
Итак, мы знаем, как считать последовательность значений, завершающихся признаком конца файла или завершения ввода. Впоследствии мы рассмотрим еще несколько примеров, а сейчас обсудим все еще популярную идею о том, чтобы несколько раз запрашивать значение, пока не будет введен его приемлемый вариант. Это позволит нам проверить несколько распространенных проектных решений. Мы обсудим эти альтернативы на примерах нескольких решений простой проблемы — как получить от пользователя приемлемое значение. Начнем с очевидного, но скучного и запутанного варианта под названием “сначала попытайся”, а затем станем его постепенно совершенствовать. Наше основное предположение заключается в том, что мы имеем дело с интерактивным вводом, в ходе которого человек набирает на клавиатуре входные данные и читает сообщения, поступающие от программы. Давайте предложим пользователю ввести целое число от 1 до 10 (включительно).
cout << "Пожалуйста, введите целое число от 1 до 10:\n";
int n = 0;
while (cin>>n) { // читаем
if (1<=n && n<=10) break; // проверяем диапазон
cout << "Извините " << n
<< " выходит за пределы интервала [1:10]; попробуйте еще \n";
}
Этот код довольно уродлив, но отчасти работоспособен. Если вы не любите использовать оператор
break
(раздел А.6), то можете объединить считывание и проверку диапазона.
cout << "Пожалуйста, введите целое число от 1 до 10:\n";
int n = 0;
while (cin>>n && !(1<=n && n<=10)) // read and check range
cout << "Извините, "
<< n << " выходит за пределы интервала [1:10];
попробуйте еще \n";
Тем не менее эти изменения носят всего лишь “косметический” характер. Почему мы утверждаем, что этот код работоспособен только отчасти? Дело в том, что он будет работать, если пользователь аккуратно вводит целые числа. Если же пользователь небрежен и наберет букву
t
вместо цифры
6
(на большинстве клавиатур буква
t
расположена прямо под цифрой
6
), то программа выйдет из цикла, не изменив значения переменной
n
, поэтому это число окажется за пределами допустимого диапазона. Такой код нельзя назвать качественным. Шутник (или усердный испытатель) также может ввести с клавиатуры признак конца файла (нажав комбинацию клавиш <Ctrl+Z> в системе Windows или <Ctrl+D> в системе Unix). И снова программа выйдет из цикла со значением
n
, лежащим за пределами допустимого диапазона. Иначе говоря, для того чтобы обеспечить надежный ввод, мы должны решить три проблемы.
1. Что делать, если пользователь вводит число, находящееся за пределами допустимого диапазона?
2. Что делать, если пользователь не вводит никакого числа (признак конца файла)?
3. Что делать, если пользователь вводит неправильные данные (в данном случае не целое число)?
Что же делать во всех этих ситуациях? При создании программ часто возникает вопрос: чего мы на самом деле хотим? В данном случае для каждой из трех ошибок у нас есть три альтернативы.
1. Решить проблему в коде при вводе данных.
2. Сгенерировать исключение, чтобы кто-то другой решил проблему (возможно, прекратив выполнение программы).
3. Игнорировать проблему.
Между прочим, эти три альтернативы являются очень распространенными при обработке ошибок. Таким образом, это хороший пример рассуждений об ошибках.
Заманчиво сказать, что третья альтернатива, т.е. игнорировать проблему, ни в коем случае не является приемлемой, но это было бы преувеличением. Если я пишу простую программу для своего собственного использования, то могу делать все, что захочу, даже забыть о проверке ошибок, которые могут привести к ужасным результатам. Однако если я пишу программу, которую буду использовать через несколько часов после ее создания, то было бы глупо оставлять такие ошибки. Если же я планирую передать свою программу другим людям, то не стану оставлять такие дыры в системе проверки ошибок. Пожалуйста, обратите внимание на то, что местоимение “я” здесь использовано намеренно; местоимение “мы” могло бы ввести в заблуждение. Мы не считаем третью альтернативу приемлемой, даже если в проекте участвуют только два человека.
Выбор между первой и второй альтернативами является настоящим; иначе говоря, в программе могут быть веские причины выбрать любой из них. Сначала отметим, что в большинстве программ нет локального и элегантного способа обработать ситуацию, когда пользователь не вводит данные, сидя за клавиатурой: после того, как поток ввода был закрыт, нет большого смысла предлагать пользователю ввести число. Мы могли бы заново открыть поток
cin
(используя функцию
cin.clear()
), но пользователь вряд ли закрыл этот поток непреднамеренно (как можно случайно нажать комбинацию клавиш <Ctrl+Z>?). Если программа ждет ввода целого числа и обнаруживает конец файла, то часть программы, пытающаяся прочитать это число, должна прекратить свои попытки и надеяться, что какая-то другая часть программы справится с этой проблемой; иначе говоря, наш код, требующий ввода от пользователя, должен сгенерировать исключение. Это значит, что выбор происходит не между локальным генерированием исключений и решением проблемы, а между задачами, которые следует решить локально (если они возникают).
10.7.1. Разделение задачи на управляемые части
Попробуем решить проблемы, связанные с выходом за пределы допустимого диапазона при вводе и при вводе данных неправильного типа.
cout << "Пожалуйста, введите целое число от 1 до 10:\n";