С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 90
Текст из файла (страница 90)
Функция main() возвращает 0;•если функция-член push(), вызванная из первой инструкции if внутри цикла for,возбуждает исключение, то вторая и третья инструкции if игнорируются,управление покидает цикл for и try-блок, и выполняется обработчик исключенийтипа pushOnFull;•если функция-член pop(), вызванная из третьей инструкции if внутри цикла for,возбуждает исключение, то вызов display() игнорируется, управление покидаетцикл for и try-блок, и выполняется обработчик исключений типа popOnEmpty.Когда возбуждается исключение, пропускаются все инструкции, следующие за той, гдеоно было возбуждено. Исполнение программы возобновляется в catch-обработчике этогоисключения.
Если такого обработчика не существует, то управление передается вфункцию terminate(), определенную в стандартной библиотеке C++.Try-блок может содержать любую инструкцию языка C++: как выражения, так иобъявления. Он вводит локальную область видимости, так что объявленные внутри негопеременные недоступны вне этого блока, в том числе и в catch-обработчиках. Например,функцию main() можно переписать так, что объявление переменной stack окажется вtry-блоке.
В таком случае обращаться к этой переменной в catch-обработчиках нельзя:526С++ для начинающихint main() {try {iStack stack( 32 );527// правильно: объявление внутри try-блокаstack.display();for ( int ix = 1; ix < 51; ++ix ){// то же, что и раньше}}catch//}catch//}( pushOnFull ) {здесь к переменной stack обращаться нельзя( popOnEmpty ) {здесь к переменной stack обращаться нельзя// и здесь к переменной stack обращаться нельзяreturn 0;}Можно объявить функцию так, что все ее тело будет заключено в try-блок. При этом необязательно помещать try-блок внутрь определения функции, удобнее заключить ее тело вфункциональный try-блок.
Такая организация поддерживает наиболее чистое разделениеint main()try {iStack stack( 32 );// правильно: объявление внутри try-блокаstack.display();for ( int ix = 1; ix < 51; ++ix ){// то же, что и раньше}return 0;}catch ( pushOnFull ) {// здесь к переменной stack обращаться нельзя}catch ( popOnEmpty ) {// здесь к переменной stack обращаться нельзякода для нормальной обработки и кода для обработки исключений.
Например:}Обратите внимание, что ключевое слово try находится перед фигурной скобкой,открывающей тело функции, а catch-обработчики перечислены после закрывающей егоскобки. Как видим, код, осуществляющий нормальную обработку, находится внутри телафункции и четко отделен от кода для обработки исключений. Однако к переменным,объявленным в main(), нельзя обратиться из обработчиков исключений.Функциональный try-блок ассоциирует группу catch-обработчиков с телом функции.
Еслиинструкция возбуждает исключение, то поиск обработчика, способного перехватить этоисключение, ведется среди тех, что идут за телом функции. Функциональные try-блокиособенно полезны в сочетании с конструкторами классов. (Мы еще вернемся к этой темев главе 19.)С++ для начинающихУпражнение 11.3Напишите программу, которая определяет объект IntArray (тип класса IntArrayрассматривался в разделе 2.3) и выполняет описанные ниже действия.Пусть есть три файла, содержащие целые числа.1.
Прочитать первый файл и поместить в объект IntArray первое, третье, пятое, ..., n-оезначение (где n нечетно). Затем вывести содержимое объекта IntArray.2. Прочитать второй файл и поместить в объект IntArray пятое, десятое, ..., n-оезначение (где n кратно 5). Вывести содержимое объекта.3. Прочитать третий файл и поместить в объект IntArray второе, четвертое, ..., n-оезначение (где n четно). Вывести содержимое объекта.Воспользуйтесь оператором operator[]() класса IntArray, определенным вупражнении 11.2, для сохранения и получения значений из объекта IntArray. Так какoperator[]() может возбуждать исключения, обработайте их, поместив необходимоеколичество try-блоков и catch-обработчиков.
Объясните, почему вы разместили try-блокиименно так, а не иначе.11.3. Перехват исключенийВ языке C++ исключения обрабатываются в предложениях catch. Когда какая-тоинструкция внутри try-блока возбуждает исключение, то просматривается списокпоследующих предложений catch в поисках такого, который может его обработать.Catch-обработчик состоит из трех частей: ключевого слова catch, объявления одноготипа или одного объекта, заключенного в круглые скобки (оно называется объявлениемисключения), и составной инструкции. Если для обработки исключения выбранонекоторое catch-предложение, то выполняется эта составная инструкция. Рассмотримcatch-обработчики исключений pushOnFull и popOnEmpty в функции main() болееcatch ( pushOnFull ) {cerr << "trying to push value on a full stack\n";return errorCode88;}catch ( popOnEmpty ) {cerr << "trying to pop a value on an empty stack\n";return errorCode89;подробно:}В обоих catch-обработчиках есть объявление типа класса; в первом это pushOnFull, а вовтором – popOnEmpty.
Для обработки исключения выбирается тот обработчик, длякоторого типы в объявлении исключения и в возбужденном исключении совпадают. (Вглаве 19 мы увидим, что типы не обязаны совпадать точно: обработчик для базовогокласса подходит и для исключений с производными классами.) Например, когдафункция-член pop() класса iStack возбуждает исключение popOnEmpty, то управлениепопадает во второй обработчик. После вывода сообщения об ошибке в cerr, функцияmain() возвращает код errorCode89.А если catch-обработчики не содержат инструкции return, с какого места будетпродолжено выполнение программы? После завершения обработчика выполнение528С++ для начинающихвозобновляется с инструкции, идущей за последним catch-обработчиком в списке.
Внашем примере оно продолжается с инструкции return в функции main(). После тогоint main() {iStack stack( 32 );try {stack.display();for ( int x = 1; ix < 51; ++ix ){// то же, что и раньше}}catch ( pushOnFull ) {cerr << "trying to push value on a full stack\n";}catch ( popOnEmpty ) {cerr << "trying to pop a value on an empty stack\n";}// исполнение продолжается отсюдаreturn 0;как catch-обработчик popOnEmpty выведет сообщение об ошибке, main() вернет 0.}Говорят, что механизм обработки исключений в C++ невозвратный: после того какисключение обработано, управление не возобновляется с того места, где оно быловозбуждено. В нашем примере управление не возвращается в функцию-член pop(),возбудившую исключение.11.3.1.
Объекты-исключенияОбъявлением исключения в catch-обработчике могут быть объявления типа или объекта.В каких случаях это следует делать? Тогда, когда необходимо получить значение или както манипулировать объектом, созданным в выражении throw. Если классы исключенийспроектированы так, что в объектах-исключениях при возбуждении сохраняетсянекоторая информация и если в объявлении исключения фигурирует такой объект, тоинструкции внутри catch-обработчика могут обращаться к информации, сохраненной вобъекте выражением throw.Изменим реализацию класса исключения pushOnFull, сохранив в объекте-исключениито значение, которое не удалось поместить в стек. Catch-обработчик, сообщая об ошибке,теперь будет выводить его в cerr. Для этого мы сначала модифицируем определение// новый класс исключения:// он сохраняет значение, которое не удалось поместить в стекclass pushOnFull {public:pushOnFull( int i ) : _value( i ) { }int value { return _value; }private:int _value;типа класса pushOnFull следующим образом:529С++ для начинающих};Новый закрытый член _value содержит число, которое не удалось поместить в стек.Конструктор принимает значение типа int и сохраняет его в члене _data.
Вот какvoid iStack::push( int value ){if ( full() )// значение, сохраняемое в объекте-исключенииthrow pushOnFull( value );// ...вызывается этот конструктор для сохранения значения из выражения throw:}У класса pushOnFull появилась также новая функция-член value(), которую можноиспользовать в catch-обработчике для вывода хранящегося в объекте-исключенииcatch ( pushOnFull eObj ) {cerr << "trying to push value " << eObj.value()<< " on a full stack\n";значения:}Обратите внимание, что в объявлении исключения в catch-обработчике фигурируетобъект eObj, с помощью которого вызывается функция-член value() классаpushOnFull.Объект-исключение всегда создается в точке возбуждения, даже если выражение throw –enum EHstate { noErr, zeroOp, negativeOp, severeError };enum EHstate state = noErr;int mathFunc( int i ) {if ( i == 0 ) {state = zeroOp;throw state;// создан объект-исключение}// иначе продолжается обычная обработкаэто не вызов конструктора и, на первый взгляд, не должно создавать объекта.