Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

Мы использовали

0
и
size
, чтобы попытаться гарантировать, что индекс
i
всегда будет находиться в допустимом диапазоне, когда мы обратимся к элементу
v[i]
. К сожалению, мы сделали ошибку. Посмотрите на цикл
for
: условие его завершения сформулировано как
i<=v.size
, в то время как правильно было бы написать
i<v.size
. В результате, прочитав пять чисел, мы попытаемся вывести шесть. Мы попытаемся обратиться к элементу
v[5]
, индекс которого ссылается за пределы вектора. Эта разновидность ошибок настолько широко известна, что даже получила несколько названий: ошибка
занижения или завышения на единицу
(off-by-obe error), ошибка диапазона (range error), так как индекс не принадлежит допустимому диапазону вектора, и ошибка пределов (bounds error), поскольку индекс выходит за пределы вектора.

Эту ошибку можно спровоцировать намного проще.

vector<int> v(5);

int x = v[5];

Однако мы сомневаемся, что вы признаете такой пример реалистичным и заслуживающим внимания. Итак, что же произойдет на самом деле, если мы сделаем ошибку диапазона? Операция доступа по индексу в классе

vector
знает размер вектора, поэтому может проверить его (и действительно, делает это; см. разделы 4.6 и 19.4). Если проверка заканчивается неудачей, то операция доступа по индексу генерирует исключение типа
out_of_range
. Итак, если бы ошибочный код, приведенный выше, являлся частью какой-то программы, перехватывающей исключения, то мы получили бы соответствующее сообщение об ошибке.

int main

try {

vector<int> v; // вектор целых чисел

int x;

while (cin>>x) v.push_back(x); // записываем значения

for (int i = 0; i<=v.size; ++i) // выводим значения

cout << "v[" << i <<"] == " << v[i] << endl;

} catch (out_of_range) {

cerr << "Ой! Ошибка диапазона \n";

return 1;

} catch (...) { // перехват всех других исключений

cerr << "Исключение: что-то не так \n";

return 2;

}

Обратите внимание на то, что ошибка диапазона на самом деле является частным случаем ошибки, связанной с аргументами, которую мы обсудили в разделе 5.5.2. Не доверяя себе, мы поручили проверку диапазона индексов вектора самой операции доступа по индексу. По очевидным причинам оператор доступа по индексу (

vector::operator[]
) сообщает об ошибке, генерируя исключение. Что еще может произойти? Оператор доступа по индексу не имеет представления о том, что бы мы хотели в этой ситуации делать. Автор класса vector даже не знает, частью какой программы может стать его код.

5.6.3. Неправильный ввод

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

Рассмотрим фрагмент кода, в котором вводится число с плавающей точкой.

double d = 0;

cin >> d;

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

cin
.

if (cin) {

// все хорошо, и мы можем считывать данные дальше

}

else {

//
последнее считывание не было выполнено,

// поэтому следует что-то сделать

}

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

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

double some_function

{

double d = 0;

cin >> d;

if (!cin)

error("невозможно считать число double в 'some_function'");

// делаем что-то полезное

}

Строку, переданную функции

error
, можно вывести на печать для облегчения отладки или для передачи сообщения пользователю. Как написать функцию
error
так, чтобы она оказалась полезной для многих программ? Она не может возвращать никакого значения, потому что неизвестно, что с ним делать дальше. Вместо этого лучше, чтобы функция
error
прекращала выполнение программы после получения сообщения об ошибке. Кроме того, перед выходом иногда следует выполнить определенные несложные действия, например, оставить окно с сообщением активным достаточно долго, чтобы пользователь мог прочесть сообщение. Все это вполне естественно для исключений (подробнее об этом — в разделе 7.3).

В стандартной библиотеке определено несколько типов исключений, таких как

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

Итак, нашу простую функцию

error
можно переписать следующим образом:

void error(string s)

{

throw runtime_error(s);

}

Когда нам потребуется поработать с исключением

runtime_error
, мы просто перехватим его. Для простых программ перехват исключения
runtime_error
в функции
main
является идеальным.

int main

try {

// наша программа

return 0; // 0 означает успех

}

catch (runtime_error& e) {

cerr << "runtime error: " << e.what << '\n';

keep_window_open;

return 1; // 1 означает сбой

}

Вызов

e.what
извлекает сообщение об ошибке из исключения
runtime_error
.

Символ

&
в выражении

catch(runtime_error& e) {

означает, что мы хотим передать исключение по ссылке. Пожалуйста, пока рассматривайте это выражение просто как техническую подробность. В разделах 8.5.4–8.5.6 мы объясним, что означает передача сущности по ссылке.

Поделиться с друзьями: