Out.close();//закрытие потока 5 глава




Если исключение не перехвачено, то оно обрабатывается методом Application->HandleException. Он обеспечивает стандартную реакцию программы на большинство исключений - выдачу пользователю краткой информации в окне сообщений и уничтожение экземпляра исключения.

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

 

Базовый класс исключений VCL Exception

Все предопределенные в C++Builder классы исключений являются прямыми или косвенными наследниками класса Exception, объявленного в модуле SysUtils и наследующего непосредственно TObject.

Класс Exception имеет два свойства:

 

Свойство Тип Описание
HelpContext int Целый идентификатор экрана контекстно-зависимой справки. Этот экран справки отображается, если пользователь, находясь в окне с сообщением об ошибке, нажимает клавишу F1. По умолчанию значение равно 0.
Message System::AnsiString   Строка сообщения, которая в дальнейшем при обработке исключения системным обработчиком отображается в окне сообщений; устанавливается конструктором по умолчанию.

 

 

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

Свойство HelpContext хранит целый идентификатор соответствующий экрану контекстно-зависимой справки. Этот экран справки отображается, если пользователь, находясь в окне с сообщением об ошибке, нажимает клавишу Fl.

 

По умолчанию значение свойства HelpContext равно 0. Это значение может изменяться некоторыми конструкторами исключений. Например, оператор

throw Exception("He хватает исходных данных", 4);

генерирует исключение со значением свойства Message, равным тексту «Не хватает исходных данных», и значением свойства HelpContext, равным 4. При получении сообщения об этом исключении пользователь сможет нажать клавишу F1 и получить пояснения, что ему делать в этом случае. Генерация исключения осуществляется ключевым словом throw. Пользователь может, проанализировав какие-то данные и обнаружив ошибку, сгенерировать таким образом собственное исключение, которое будет затем обработано приложением.

Чтобы свойство HelpContext работало, надо создать соответствующий файл справки и связать его с приложением, установив соответствующую опцию Help file (файл справки) в окне Project Options (опции проекта) на странице Application (приложение).

 

 

Обработка исключений в блоках try... catch

Рассмотрим следующий пример. В приложении имеется два окна редактирования Edit1 и Edit2, в которых пользователь вводит действительные числа типа float. Приложение должно разделить их одно на другое.

Выполняемый код очень простой:\

float A;

A=StrToFloat(Edit1->Text)/StrToFloat(Edit2->Text);

 

Если в Edit2 поместить ноль, то после запуска программы появится сообщение

 

Если в любом окне вместе с цифрами закрадется символ, то появится сообщение

 

 

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

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

В языке С++ имеется множество классов, которые создают соответствующие объекты в момент возникновения исключительной ситуации. Эти объекты, также как и исключительные ситуации, называются исключениями. Путаницы из-за одинакового имени разных понятий, обычно, не возникает, т.к. из контекста понятно о чем идет речь.

Наиболее кардинальный путь борьбы с исключениями - отлавливание и обработка их с помощью блоков try... catch. Синтаксис этих блоков следующий:

Try

{

Исполняемый код в котором возможны исключения

}

catch (Тип_соответствующего_исключения)

{

Код, исполняемый в случае ошибки

}

Операторы блока catch представляют собой обработчик исключения. Параметр Тип_соответствующего_исключения может быть или одним из целых типов (int, char и т.п.), или ссылкой на класс исключения, или многоточием, что означает обработку любых исключений. Остановимся на случае, когда параметр является ссылкой на класс исключений.

Операторы обработчика выполняются только в случае генерации в операторах блока try исключения типа, указанного в заголовке catch. После блока try может следовать несколько блоков catch для разных типов исключений. Таким образом, в обработчиках catch можно предпринять какие-то действия: известить пользователя о возникшей проблеме и подсказать ему пути ее решения, принять какие-то меры к исправлению ошибки (например, при переполнении заслать в результат очень большое число соответствующего знака) и т.д. Наиболее ценным является то, что вы можете определить тип сгенерированного исключения и дифференцированно реагировать на,различные исключительные ситуации. Причем перехват исключения блоком catch приводит к тому, что это исключение далее не обрабатывается стандартным образом, т.е. пользователю не предъявляется окно с непонятными ему английскими текстами.

Приведем пример обработки исключений. Пусть в вашем приложении имеется два окна редактирования Edit1 и Edit2, в которых пользователь вводит действительные числа типа float. Приложение должно разделить их одно на другое. При этомвозможен ряд ошибок: пользователь может ввести в окно символы, не преобразуемые в целое число, может ввести слишком большое число, может ввести вместо делителя нуль, результат деления может быть слишком большим для типа float. Следующий код отлавливает все эти ошибки:

 

float A;

Try

{

A = StrToFloat(Edit1->Text)/StrToFloat(Edit2->Text);

}

catch (EConvertError&)

{

Application->MessageBox "Вы ввели ошибочное число",

"Повторите ввод", MB_OK);

}

catch (EZeroDivide&)

{

Application->MessageBox (("Вы ввели нуль","Повторите ввод", MB_OK);

}

catch (EOverflow&)

{

Application->MessageBox ("Переполнение",

"Ошибка вычислений",MB_OK);

if (StrToFloat (Edit1->Text) * StrToFloat (Edit2->Text) >= 0)

A = 3.4E38;

else A = -3.4E38;

}

 

 

Если пользователь ввел неверное число (например, по ошибке нажал не цифру, а какой-то буквенный символ), то при выполнении функции StrToFloat возникнет исключение класса EConvertError. Соответствующий обработчик исклюю чения сообщит пользователю о сделанной ошибке и посоветует повторить ввод. Аналогичная реакция последует на ввод пользователем в качестве делителя нуля (класс исключения EZeroDivide). Если возникает переполнение, то соответствующий блок catch перехватывает исключение, сообщает о нем пользователю и исправляет ошибку: заносит в результат максимально возможное значение соответствующего знака.

Поскольку исключения образуют иерархию, можно обрабатывать сразу некоторую совокупность исключений,производных от одного базового исключения. Для этого надо в заголовке блока catch указать имя этого базового исключения. Например, исключения EZeroDivide (целочисленное деление на нуль), EOverflow (переполнение при целочисленных операциях), EInvalidArgument (выход числа за допустимый диапазон) и некоторые другие являются производными от класса исключений EMathError. Поэтому все их можно отлавливать с помощью одного блока catch, например, такого:

 

catch (EMathError&)

{

Application->MessageBox("Ошибка вычислений", "Повторите ввод", МВ_ОК);

}

Правда, в этом случае не конкретизируется причина прерывания исключений. Однако такая конкретизация возможна, если воспользоваться свойствами исключений. Все исключения имеют свойство Message, которое представляет собой строку, отображаемую пользователю при стандартной обработке исключений.

Чтобы воспользоваться свойствами исключений, надо в заголовке блока catch не только указать тип исключения, но и создать временный указатель на объект этого типа. Тогда через имя этого объекта вы получаете доступ к его свойствам. Ниже приведен пример использования свойств исключений при перехвате исключений, наследующих классу EMathError:

catch (EMathError& E)I

{

AnsiString S="Ошибка вычислений:";

if (E.Message == "EZeroDivide") S+="деление на нуль";

if (E.Message == "EOverflow") S+="переполнение";(E. Message == "EInvalidArgument") S+="недопустимое число";

Application->MessageBox (S.c_str (), "Повторите ввод", MB_OK);

}

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

Как уже говорилось выше, если в заголовке блока catch указано многоточие, то этот блок перехватит любые исключения:

 

Catch (...)

{

ShowMessage("Произошла ошибка.");

}

Блок catch(...) может сочетаться и с другими блоками catch, но в этом случае он должен, конечно, располагаться последним. Поскольку этот блок перехватит все исключения, то все блоки, следующие за ним, окажутся недоступными. C++Builder следит за этим. Если блок catch(...) оказался не последним, вам будет выдано компилятором сообщение об ошибке с текстом: «The handler must be last» «Обработчик должен быть последним»).

 

Предупреждение

Следует указать на некоторую опасность применения блока catch(...). Перехват всех исключений способен замаскировать какие-то непредвиденные ошибки в программе, что затруднит их поиск и снизит надежность работы.

 

 

Блоки try...catch могут быть вложенными явным или неявным образом. Примером неявной вложенности является блок try...catch, в котором среди операторов раздела try имеются вызовы функций, которые имеют свои собственные блоки try...catch. При генерации исключения сначала ищется соответствующий ему обработчик в том блоке try...catch, в котором создалась исключительная ситуация. Если соответствующий обработчик не найден, поиск ведется в обрамляющем блоке try...catch (при наличии явным образом вложенных блоков) и т.д. Если в данной функции обработчик не найден или вообще в ней отсутствуют блоки try...catch, то поиск переходит на следующий уровень — в блок, из которого была вызвана данная функция. Этот поиск продолжается по всем уровням. И только если он закончился безрезультатно, выполняется стандартная обработка исключения, заключающаяся, как уже было сказано, в выдаче пользователю сообщения о типе исключения. Как только блок catch, соответствующий данному исключению, найден и выполнен, объект исключения разрушается и управление передается оператору, следующему за соответствующим блоком try...catch.

Если исключение не перехвачено ни одним обработчиком в функциях, вы можете обработать его на уровне приложения. Для этого предусмотрены события OnException компонента Application - самого приложения. Обработчик этих событий можно ввести в ваше приложение следующим образом. Пусть вы решили назвать этот обработчик MyException. Тогда в заголовочный файл приложения надо добавить его объявление:

void __fastcall MyException(TObject *Sender, Exception *E);

В файл вашего модуля надо внести реализацию обработчика:

void fastcall Tform1::MyException(TObject *Sender, Exception *E)

{

// операторы обработки

}

Осталось указать приложению на вашу функцию MyException как на обработчик события OnException. Вы можете это сделать, включив, например, в обработку события формы OnCreate оператор:

Application->OnException = MyException;

 

Обработчик не перехваченных ранее исключений готов. Осталось только наполнить его операторами, сообщающими пользователю о возникших неполадках и обеспечивающими дальнейшую работу программы. К функции MyException приложение будет обращаться, если было сгенерировано исключение и ни один блок catch его не перехватил. В функцию передается указатель Е на объект класса Exception. Этот объект является сгенерированным исключением, а класс Exception — базовый класс всех исключений. Простейшая обработка исключения могла бы производиться функцией ShowException, обеспечивающей отображение информации об исключении:

Application->ShowException(Е);

В заголовке окна пишется имя приложения, а текст содержит описание причины генерации исключения. Основным недостатком функции являются сообщения на английском языке, что вряд ли порадует пользователей вашего приложения. Поэтому лучше сделать собственные сообщения. При этом для определения истинного класса сгенерированного исключения можно воспользоваться методом ClassName. Тогда обработчик события OnException может иметь, например, следующий вид:

 

void __fastcall TForml::MyException(TObject *Sender, Exception *E)

{

AnsiString S = "Ошибка вычислений

if (String(E->ClassName()) == "EZeroDivide")

S+= "деление на нуль";

if (String(E->ClassName()) == "EOverflow")

S+= "переполнение";

if (String (E->ClassName())=="EInvalidArgument")

S+= "недопустимое число";

if(String(E->ClassName())=="EConvertError")

S+= "ввели недопустимое число";

Application->MessageBox(S.c_str(),"Повторите ввод",MB_OK);

}

 

В C++Builder имеется исключение EAbort, несколько отличающееся от рассмотренных ранее. Генерация этого исключения, как и любых других, прерывает процесс вычисления. Но если приложение не отлавливает соответствующим блоком catch исключений этого класса, то онипопадают в обработчик TApplication::HandleException и там, в отличие от других исключений, разрушаются без всяких сообщений. Таким образом, это «молчаливое» прерывание процесса вычисления, при котором не должно отображаться диалоговое окно с сообщением обошибке. Простейший путь генерации исключения EAbort — вызов функции Abort. Например:

 

if (...) Abort();

Только нельзя путать две похожие внешне функции: Abort – генерация “молчаливого” исключения, и abort — аварийное завершение программы. Обычное применение EAbort — прерывание вычислений при выполнении некоторого условия окончания или условия прерывания пользователем (например, при нажатии клавиши Esc или какого-то оговоренного сочетания клавиш). Функция Abort прерывает текущую процедуру и все вызвавшие ее процедуры, передавая управление на самый верх. Таким образом, это наиболее простой выход из глубоко вложенных процедур. Впрочем, можно при необходимости перехватить исключение на каком-то промежуточном уровне, предусмотрев на нем блок try...catch и вставив соответствующий оператор обработки:

catch(EAbort&)

 

Рассмотрим простой пример

//Простой пример обработки исключительной ситуации

#include<iostream>

#include<windows>

using namespace std;

int main(){

SetConsoleOutputCP(1251);

cout<<"Начало"<<endl;

try{ //начало блока try

cout<<"Внутри блока try"<<endl;

throw 10; //возбуждение ошибки

cout<<"Эта инструкция выполнена не будет, ошибка уже произошла";

}catch(int i){ //перехват ошибки

cout<<"Перехвачена ошибка номер: "<<i<<endl;

}

 

return 0;

char z;

cin>>z;

}

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

Глядя на этот пример можно сказать, что в блоке try, в случае возникновения исключительной ситуации, оператор throw определяет объект для передачи в блок catch, выполнение блока try прекращается, т.е. остаток блока игнорируется. Далее выполнение программы переходит в блок catch. Сформированный в блоке try объект, в данном случае это число типа int равное 10, передается блоку catch в качестве параметра. Блоков catch может быть несколько, в зависимости от числа генерируемых исключений. Но у каждого блока try олжен быть хотя бы один блок catch и наоборот.

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

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

/*Возбуждение исключительной ситуации из функции,

находящейся вне блокаtry */

#include <iostream>

#include<windows>

using namespace std;

void Xtest(int test){

cout << "Внутри функции Xtest, test равно: " << test << "\n"; if(test) throw test;

}

int main (){

SetConsoleOutputCP(1251);

cout << "начало\n";

try { // начало блока try

cout <<"Внутри блока try\n";

Xtest(0);

Xtest (1);

Xtest (2);

}

catch (int i) { // перехват ошибки

cout << "перехвачена ошибка номер: "; cout << i << "\n";

}

cout << "конец";

return 0;

}

 

 

Следует напомнить, что C++ поддерживает синтаксис языка «С», в котором false записывается как 0, а все, что не ноль есть true. Именно поэтому в функции возникает исключение.

Блок try можно располагать внутри функции. В этом случае при каждом входе в функцию обработчик исключительной ситуации устанавливается снова. Например,

// Блоки try и catch могут находиться не только в функции main()

#include <iostream>

using namespace std;

void Xhandler(int test){

try {

if(test) throw test;

catch(int i) {

cout << "перехвачена ошибка номер:<<i<<endl;

}

}

//=====================

int main(){

cout<<"начало\n";

Xhandler (1);

Xhandler (2);

Xhandler(0);

Xhandler(3);

cout<<"конец";

return 0;

}

На экран программа выводит следующее:

начало

Перехвачена ошибка номер: 1

Перехвачена ошибка номер: 2

Перехвачена ошибка номер: 3

конец

В данном примере обработаны три исключительные ситуации. После вызова каждой исключительной ситуации функция возвращает свое значение. При повторном вызове функции обработчик исключительной ситуации устанавлив ется вновь.

Как упоминалось ранее, с блоком try можно связать более одной инструкции catch. Как правило, так и делается. При этом каждая инструкция catch предназначена для перехвата своего типа исключттельной ситуации

#include<iostream>

#include<windows>

 

using namespace std;

//Можно перехватывать разные типы исключительных ситуаций

voidXhandler(int test){

try{

if(test) throw test;

else throw "Значение равно нулю";

}

 

catch(int i) {

cout <<"Перехвачена ошибка номер: "<<i<<endl;

}

catch(char *str) {

cout <<"Перехвачена строка: " <<str << endl;

}

}

//======================

 

main(){

SetConsoleOutputCP(1251);

cout << "начало\n";

Xhandler(1);

Xhandler(2);

Xhandler(0);

Xhandler(3);

cout << "конец";

return 0;

}

На экран программа выводит следующее:

 

Как видите, каждая инструкция catch перехватывает только исключительные ситуации соответствующего ей типа.

Обычно выражения инструкций catch проверяются втом порядке, в кoтopoм они появляются в программе. Выполняется только та инструкция, которая совпадает по типу даниых с исключительной ситуации. Все оетальные блоки catch игнорируются.

 

Исключения с Builder

 

 

Пример для Builder

Создайте форму

с обработчиком кнопки

 

 

void __fastcall TForm1::Button1Click(TObject *Sender)

{

float a;

try{

a=StrToFloat(Edit1->Text)/StrToFloat(Edit2->Text);

} catch(EConvertError&){

Application->MessageBox("Вы ввели ошибочное число ",

"Повторите ввод", MB_OK);

}

catch(EZeroDivide&){

Application->MessageBox("Вы ввели ноль",

"Повторите ввод", MB_OK);

}

catch(EOverflow&){

Application->MessageBox("Переполнение",

"Ошибка вычислений", MB_OK);

if(StrToFloat(Edit1->Text)*StrToFloat(Edit2->Text)>=0)

a=3.4E38;

else a=-3.4E38;

}

}

//---------------------------------------------------------------------------

Отключите дебаггер. Для этго войдите в меню Tools/Debugger Options. В появившемся окне снимите флажок Integrated debugging.

Упражнения

1. Введите, откомпилируйте и запустите предыдущие примеры программ. Затем поэкспериментируйте с ними, меняя фрагменты и исследуя результаты.

2. Что неправильно в данном фрагменте?

int main(){

throw 12.23;

...

3. Что неправильно в данном фрагменте?

try {

//...

throw 'а';

catch(char *){

4. Что может произойти при возбуждении исключительной ситуации, для которой не задано соответствующей инструкции catch?

 

 

Обработка исключительных ситуаций, возбуждаемых оператором new

 

При неудачной попытке выделения памяти оператор new возбуждает исключительную ситуацию.

С момента появления языка C++ точное определение действий, которые должны выполняться при неудачной попытке выделения памяти с помощью оператора new, менялось несколько раз. После разработки языка при неудачной попытке выделения памяти оператор new возвращал нуль, несколькими годами позднее — возбуждал исключительную ситуацию. Кроме того, неоднократно менялось имя этой исключительной ситуации. В конце концов было решено, что неудачная попытка выделения памяти с помощью оператора new по умолчанию будет возбуждать исключительную ситуацию, но по желанию в качестве опции можно возвращать нулевой указатель. Таким образом, оператор new реализовывался по-разному в разное время разными производителями компиляторов. Хотя в будущем все компиляторы должны быть выполнены в точном соответствии с требованиями стандарта Standard C++, сегодня это не так. Все это сказано для того, что некоторые примеры приведенные ниже на некоторых компбютерах могут не работать.

В соответствии со стандартом Standard C++, когда требование на выделение памяти не может быть выполнено, оператор new возбуждает исключительную ситуацию bad_alloc. При невозможности перехватить эту исключительную ситуацию программа завершается. Хотя для коротких программ такой алгоритм кажется вполне очевидным и понятным, в реальных приложениях должны не только перехватить, но и каким-то разумным образом обратать эту исключительную ситуацию. Для доступа к указанной исключительной ситуации в программу необходимо включить заголовок <new >.


В представленном ниже примере с оператором new использование блока try/catch дает возможность проконтролировать неудачную попытку выделения памяти.

#include <iostream>

#include <windows>

#include <new>

using namespace std;

int main() {

SetConsoleOutputCP(1251);

int *p;

try {

p = new int; // выделение памяти для целого

} catch (bad_alloc xa) {

cout << "Ошибка выделения памяти\n";

return 1;

}

for(*p=0;*p<10;(*p)++)

cout<<*p<<" ";

delete p; // освобождение памяти

return 0;

}

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

 

#include <iostream>

#include <windows>

#include <new>

using namespace std;

main(){

SetConsoleOutputCP(1251);

double *p;

// цикл будет продолжаться вплоть до исчерпания ресурса памяти

do {

try {

p = new double[100000]; } catch (bad_alloc xa) {



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: