Исключительные ситуации. Предотвращение и обработка ошибок

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

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

Инструкция обработки исключения в общем виде выглядит так: try

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

Except // начало секции обработки исключений on ТипИсключения! do Обработка!; on ТипИсключения2 do 0бработка2;

on ТипИсключенияJ do Обработка J; else // здесь инструкции обработки остальных исключений

Try - ключевое слово, обозначающее, что далее следуют инструкции, при выполнении которых возможно возникновение исключений, и что обработку этих исключений берет на себя программа; except - ключевое слово, обозначающее начало секции обработки исключений. Инструкции этой секции будут выполнены, если в программе возникнет ошибка; on - ключевое слово, за которым следует тип исключения, обработку которого выполняет инструкция, следующая за do;

else - ключевое слово, за которым следуют инструкции, обеспечивающие обработку исключений, тип которых не указан в секции except.

Как было сказано выше, каждое исключение характеризуется типом. В табл. 13.1 перечислены наиболее часто возникающие исключения и указаны причины, которые могут привести к их возникновению.

Таблица 13.1. Типичные исключения

Следующая программа, вид ее диалогового окна приведен на рис. 13.3, а текст - в листинге 13.1, демонстрирует обработку исключений при помощи инструкции try.

Рис. 13.3. Диалоговое окно программы

Листинг 13.1. Обработка исключения

Unit UsTry_; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForml = class(TForm) Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel;

Editl: TEdit; // напряжение

Edit2: TEdit; // сопротивление

Label5: TLabel; // результат расчета - ток

Buttonl: TButton; // кнопка Вычислить

Procedure ButtonlClick(Sender: TObject); private { Private declarations } public { Public declarations } end;

var Forml: TForml; implementation

{$R *.DFM} procedure TForml.ButtonlClick(Sender: TObject); var и: real; // напряжение г: real; // сопротивление i: real; // ток begin Labe15.Caption:= " "; try

// инструкции, которые могут вызвать исключение (ошибку) u:= StrToFloat(Editl.Text); г:= StrToFloat(Edit2.Text); i:= u/r; except // секция обработки исключений on EZeroDivide do // деление на ноль begin ShowMessage("Сопротивление не может быть равно нулю!"); exit; end; on EConvertError do // ошибка преобразования строки в число begin ShowMessage("Напряжение и сопротивление должны быть " + "заданы числом." +#13+ "При записи дробного числа используйте запятую."); exit; end; end; Label5.Caption:= FloatToStr(i) + " A"; end; end.

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

При ВЫПОЛНеНИИ ИНСТРУКЦИИ i:=u/r ВОЗНИКНеТ ИСКЛЮЧеНИе EZeroDivide.

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

Задание 1: Создайте приложение, которое создает текстовый файл text1.txt и записывает в него текст, введенный пользователем в окно Edit, после чего закрывает файл.

Решение:

Создайте форму и задайте для ее свойства Caption значение «Создание файла и вывод в него текста». Разместите на форме компоненты Edit1, Labbel1, Button1, как показано на рис. 6.1. Задайте значения для свойства Label1. Caption – «Введите текст», Button1. Caption – «Сохранить». Выровняйте компоненты и зафиксируйте их положение на форме.

Сохраните файлы модуля под именем main и проекта под именем TextEditFile в папке Обработка текстовых файлов.

Рис. 6.1 Пример формы проекта

Создайте процедуру обработки события кнопки «Сохранить», введите в окне Редактора кода следующий текст:

f : TextFile ; {описание файловой переменной}

AssignFile ( f , " text 1. txt "); {связь файловой переменной с файлом}

Rewrite ( f ); {создать новый файл}

Writeln (f, Edit1. Text); {записать в файл}

CloseFile(f); end; {закрыть файл}

Запустите приложение и введите в окно Edit следующее предложение – «Мой первый пример текста». Щелкните мышкой на кнопке «Сохранить» и закройте окно приложения.

Откройте окно Проводника Windows папку Обработка текстовых файлов, в которой сохранены файлы проекта. В списке файлов этой папки находится вновь созданный файл text1.txt. Дважды щелкните левой кнопкой мыши на имени файла text1.txt. Убедитесь, что это – тот самый текст, который введен в окне приложения. Откроется окно редактора Блокнот с этим файлом. Закройте окно редактора Блокнот и Проводник.

Задание 2: Создайте приложение, открывающее текстовый файл для чтения и считывающие из него текст в окно Memo. Перед открытием файла следует проверить его наличие, в случае его отсутствия должно выводиться соответствующее сообщение.

Решение:

Создайте новое приложение (проект). Создайте форму «Чтение текста из файла в окно». На форме разместите компоненты Memo1, Label1, Button1, как показано на рис. 6.2. Задайте значения свойств Label1. Caption – «Текст из файла», Button1. Caption – «Прочитать текст из файла».

Для удаления текста Memo1 из компонента выберите в окне Инспектора объектов объект Memo1, затем на странице Свойства выберите свойсво Lines и в поле со значением Strings произведите двойной щелчок. После этого в окне StringListEditor удалите текст и щелкните мышью на кнопке ОК.

Для обеспечения возможности просмотра в окне Memo1 длинных текстов с использованием вертикальной полосы прокрутки в окне Инспектора объектов выберите свойство ScrollBars значение ssVertical. Выровняйте компоненты и зафиксируйте их положение на форме.


Рис. 6.2 Пример формы приложения

Создайте процедуру обработки события кнопки «Прочитать текст из файла», отредактируйте текст процедуры следующим образом:

procedure TForm1. Button1Click (Sender: TObject);

var f: TextFile;

ch: Char;

AssignFile (f, "text1.txt");

Reset(f);

if IOResult=0 then begin

while not Eof(f) do

Read (f, ch);

Memo1. Text:=Memo1. Text+ch;

CloseFile(f);

end else

ShowMessage (" Нет такого файла ");

Сохраните файл модуля под именем main1, а файл проекта – под именем TextMemoFile1 в папке Обработка текстовых файлов. Откомпилируйте и запустите приложение, проверьте его работу.

Задание 3 : Создайте приложение, открывающее текстовый файл для дополнения и затем добавляющее в него введенный текст.

Решение:

Создайте новый проект, задайте название формы «Добавление текста в файл». На форме разместите компонентыButton1,2, Memo1, Label1,2, Edit1 как показано на рис. 6.3. Присвойте значения свойствам Label1. Caption – «Текст из файла», Button1. Capton – «Прочитать текст из файла», Label2. Caption – «Добавляемый текст», Button2. Caption – «Добавить текст в файл». Удалите текст из компонентов Memo1, Edit1. Установите линейку вертикальной прокрутки для обеспечения возможности просмотра длинных текстов в компоненте Memo1. Выровняйте компоненты и зафиксируйте их положение на форме.



Рис. 6.3 Пример формы приложения

Создайте обработчик нажатия кнопки «Прочитать текст из файла» самостоятельно. Для кнопки «Добавить текст в файл» запишите следующий код события:

Procedure TForm1. Button2Click (Sender: TObject);

F: TextFile;

AssingFile (f, ‘text1.txt’);

Append (f);

Writeln (f, Edit1. Text);

CloseFile (f);

End ;

Сохраните файл модуля под именем Main2, а файл проекта – под именем TextMemoFile2 в папке Обработка текстовых файлов.

Запустите и проверьте работу приложения.

Задание 4: Создайте приложение, которое открывает текстовый файл с использованием метода OpenDialog, считывает текст из него в объект Memo, затем сохраняет измененный текст в файл с использованием метода SaveDialog и выводит текст на печать, используя метод PrintDialog.

Решение:

Создайте новый проект и сохраните в папке «Диалоговая панель». На форме разместите компоненты Memo1, Button1,2,3. Кнопки назовите «Сохранить», «Открыть», «Печать» соответственно. Задайте компоненту Memo1 вертикальную полосу прокрутки и удалите текст. Выровняйте и зафиксируйте компоненты на форме (см. рис. 6.4).


Рис. 6.4 Форма проекта «Диалоговая панель»

Выберите в палитре компонентов страницу Dialog и поместите на форму компоненты OpenDialog, SaveDialog, PrintDialog. Так как они не являются визуальными компонентами, то их можно поместить в любое место формы.

Задайте для свойства SaveDialog. Title значение «Сохранить текстовый файл», которое будет отображаться в заголовке диалогового окна сохранения файла. Чтобы при сохранении файла в окне диалога обеспечить выбор типа файла, выберите свойство Filter и произведите двойной щелчок в списке значений. Откроется окно FilterEditor. Задайте фильтры для выбора типа и расширения файла:

И щелкните по кнопке ОК, затем установите расширение *.txt по умолчанию – задайте свойству OpenDialog1. FilterIndex значение 1.

Чтобы в диалоговом окне Печать включить возможность выбора диапазона печатаемых страниц и печати выделенного фрагмента, задайте для свойств PrintDialog1. Options.poPageNums и PrintDialog1. Options.poSelection значение True.

После этого в раздел описания модулей добавьте в список USES модуль PRINTERS , чтобы не вводить свою переменную. Этот модуль позволяет управлять процессом печати.

Отредактируйте раздел описания переменных:

Form1: TForm1;

FName: string;

F: TextFile;

S : string ;

Создайте процедуры обработки событий трех кнопок, следующим образом:

1) сохранение:

procedure TForm1. Button1Click (Sender: TObject);

fName:="Text1";

SaveDialog1. FileName:=FName;

if SaveDialog1. Execute then begin

fName:=SaveDialog1. FileName;

case SaveDialog1. FilterIndex of

1: fName:=fName+".txt";

2: fName:=fName+".doc";

Memo1. Lines. SaveToFile(fName);

2) открытие:

procedure TForm1. Button2Click (Sender: TObject);

if OpenDialog1. Execute then

AssignFile (f, OpenDialog1. FileName);

fName:=OpenDialog1. FileName;

Reset(F);

Readln (F, s);

Memo1. Text:=s;

CloseFile(F);

3) печать:

procedure TForm1. Button3Click (Sender: TObject);

if PrintDialog1. Execute then

AssignPrn(f);

Rewrite(F);

Writeln (f, Memo1. Text);

Разрабатывая какое-нибудь приложение, вы должны написать код, который будет решать поставленную задачу, а также код, который будет выполнять проверку на наличие ошибок. Как правило, код для обработки ошибок строится на основе оператора if . Оператор if часто используется для проверки данных, вводимых пользователем, а также результатов выполнения функций. В простых алгоритмах можно ограничиться применением оператора if , однако в приложениях с графическим интерфейсом пользователя, где пользователи имеют полную свободу действий, ошибки могут возникать когда угодно и где угодно. Использование одного только оператора if для защиты приложения - не самая лучшая идея.

С задачей перехвата ошибок и реагирования на них лучше всего справляется механизм обработки исключений (Структурированная обработка исключений (Structured exception handling – SHE ) представляет собой метод обработки ошибок, благодаря которому можно восстановить нормальную работу приложения после сбоя в работе программы, который в противном случае был бы фатальным) . Если в приложении, написанном с помощью Delphi, возникает ошибка, то приложение автоматически генерирует исключение. Исключением представляет собой объект, который описывает возникающую ошибку.

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

Procedure TForm1.Button1Click(Sender: TObject); var x: Integer; begin x:= StrToInt(Edit1.Text); end;

Чтобы обработать исключение, сгенерированное функцией StrToInt , мы должны поместить вызов функции StrToInt в защищенный блок кода. Защищенным является блок кода, который может реагировать на некоторое исключение. В Delphi защищенный блок выглядит следующим образом:

try
оператор(ы)
except
операторы обработки исключения
end ;

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

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

Procedure TForm1.Button1Click(Sender: TObject); var x: Integer; begin try x:= StrToInt(Edit1.Text); except ShowMessage("Ошибка преобразования"); end; end;

Обработка специфических исключений в Delphi

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

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

Одно из них, EConvertError , может быть сгенерировано в том случае, если значение одного из компонентов TEdit невозможно преобразовать к целому типу, а другое, EDivByZero , может быть сгенерировано тогда, когда предпринимается попытка разделить первое число на 0.

Procedure TForm1.Button1Click(Sender: TObject); var Num1,Num2: Integer; begin try Num1:= StrToInt(Edit1.Text); Num2:= StrToInt(Edit2.Text); ShowMessage("Результат: " + IntToStr(Num1 div Num2)); except ShowMessage("Вы не можете делить эти числа"); end; end;

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

on Некоторое-Исключение do Обработка_Исключения;

Конструкцию on-do можно использовать только в рамках обработчика исключений:

try
оператор (операторы);
except
on Исключение do Обработка_Исключения;
on Другое_Исключение do Его_Обработка;

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

Procedure TForm1.Button1Click(Sender: TObject); var Num1,Num2: Integer; begin try Num1:= StrToInt(Edit1.Text); Num2:= StrToInt(Edit2.Text); ShowMessage("Результат: " + IntToStr(Num1 div Num2)); except on EConvertError do ShowMessage("Одно из чисел неправильно"); on EDivByZero do begin ShowMessage("Делитель не может быть равным 0"); Edit2.Text:= "1"; Edit2.SetFocus; end; // Завершение конструкции on EDivByZero do end; end;

Если конструкцию on-do использовать для обработки специфических исключений, вы должны также написать код для обработки ошибок, о которых вам ничего не будет известно. Чтобы обработать исключения, которые вам не удастся обработать специфическим образом, можно добавить к обработчику исключения часть else .

try
оператор (операторы);
except
on Исключение do Его_Обра6отка;
on Другое_Исключение do Его_Обработка;
else
Обработка_Всех_Остальных_Исключений;
end ;

Генерация исключений

Зарезервированное слово raise используется для генерации исключения. Чтобы сгенерировать исключение в Delphi, используйте зарезервированное слово raise , указывая вслед за ним экземпляр объекта исключения. Экземпляром объекта исключения обычно является вызов конструктора исключения.

Синтаксис генерации исключения обычно выглядит следующим образом:

raise Класс_Исключения.Create ("Сообщение_Об_Ошибке");

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

Unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Edit1: TEdit; Edit2: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } function CustomStrToInt(const s:string): Integer; end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var Num1, Num2: Integer; begin Num1:= CustomStrToInt(Edit1.Text); Num2:= CustomStrToInt(Edit2.Text); ShowMessage(IntToStr(Num1 div Num2)); end; function TForm1.CustomStrToInt(const s: string): Integer; var ErrorCode: Integer; begin Val(s, Result, ErrorCode); if ErrorCode 0 then begin if s = "" then raise EConvertError.Create("Пустая строка не может использоваться") else raise EConvertError.Create("Привет. Вы не можете конвертировать "" + s + "" в целое"); end; end; end.

Использование объекта исключения

Конструкция on-do позволяет получать на время объект исключения с помощью следующего синтаксиса

on Идентификатор: Исключение do Его_Обработка;

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

Procedure TForm1.Button1Click(Sender: TObject); var x,y: Integer; begin x:= 20; y:= 0; try Caption:= IntToStr(x div y); except on E: EDivByZero do ShowMessage("Exception: " + E.ClassType.ClassName + #13 + "Exception.Message: " + E.Message); end; end;

Создание специальных исключений в Delphi

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

type
EMyException = class(Exception);

В листинге ниже показана генерация и перехват специального исключения в Delphi.

Unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type ENoUpperCaseLetters = class(Exception); TForm1 = class(TForm) Label1: TLabel; Edit1: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } function CountUpperCase(const s: string): Integer; end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var Cnt: Integer; begin try Cnt:= CountUpperCase(Edit1.Text); Caption:= IntToStr(Cnt) + " заглавных букв"; except on E: ENoUpperCaseLetters do Caption:= E.Message; end; end; function TForm1.CountUpperCase(const s: string): Integer; var ch: Char; begin Result:= 0; for ch in s do if ch in ["A".."Z"] then Inc(Result); {Вызываем исключение, если отсутствуют буквы в верхнем регистре} if Result = 0 then raise ENoUpperCaseLetters.Create("Нет заглваных букв"); end; end.

Защита распределения ресурсов

Зарезервированное слово try позволяет построить два различных блока: блок обработчика, исключений и блок защиты ресурсов. Блок обработчика исключений создается с помощью зарезервированного слова except , а блок защиты ресурсов- с помощью зарезервированного слова finally . Синтаксическая структура блока защиты ресурсов в Delphi выглядит следующим образом:

try
...
finally
...
end ;

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

Подходящим способом использования блока защиты ресурсов является распределение или, с другой стороны, затребование ресурса перед блоком try . После того как вы затребуете ресурс, поместите операторы, использующие ресурс, внутрь блока try . Когда работа с ресурсом будет завершена, вы должны будете освободить его. Операторы, освобождающие ресурс, должны быть написаны в блоке finally .

{ Запрос ресурса }
try
{ Использование полученного ресурса }
finally
{ Освобождение ресурса }
end ;

Блок защиты ресурса часто используется для того, чтобы обеспечить надлежащее освобождение динамически созданных объектов. Например, динамическое создание модальной формы необходимо всегда защищать с помощью блока try-finally .

Procedure TForm1.Button1Click(Sender: TObject); var NewForm: TForm; begin NewForm:= TForm.Create(Self); try NewForm.ShowModal; finally NewForm.Free; end; end;

В листинге ниже показан более короткий способ динамического создания формы, защищенной блоком try-finally .

Procedure TForm1.Button1Click(Sender: TObject); begin with TForm.Create(Self) do try ShowModal; finally Free; end; end;

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

Procedure TForm1.Button1Click(Sender: TObject); begin with TForm.Create(Self) do try {Вызов исключения EDivByZero, поскольку значение Tag равно 0} Caption:= IntToStr(Top div Tag); ShowModal; finally Free; end; end;

Если вы хотите обработать исключение EDivByZero (или любое другое исключение) внутри блока защиты ресурсов, вы должны написать вложенный блок обработчика исключений.

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

Procedure TForm1.Button1Click(Sender: TObject); begin with TForm.Create(Self) do try try Caption:= IntToStr(Top div Tag); except on EDivByZero do Caption:= "Tag = 0"; end; ShowModal; finally Free; end; end;

ИЗМЕНЕНИЕ ОБРАБОТЧИКА ИСКЛЮЧЕНИЙ, ИСПОЛЬЗУЕМОГО ПО УМОЛЧАНИЮ

Глобальный объект Application отвечает за обработку исключений, не обрабатываемых блоком обработки исключений, который может находиться где-то в приложении. Чтобы изменить обработчик исключений, используемый по умолчанию, мы можем использовать компонент TApplicationEvents относящийся к категории Additional (Дополнительные).

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

Событие OnException может быть обработано с помощью процедуры типа TExceptionEvent . Процедура, обрабатывающая событие OnException , принимает два параметра: объект Sender и объект Exception .

procedure TMainForm.AppEventsException }


Top