|
||||||||
§31. Исключения. Данная тема не была изучена ранее, хотя пользоваться ей можно было уже давно, по той причине, что для полного её понимания необходимо знание ООП. Данная тема расписана в разделах справки: «Справочник по языку -> Обработка исключений», ««Справочник по языку -> Операторы -> Оператор raise», «Справочник по языку -> Операторы -> Оператор try … except» и «Справочник по языку -> Операторы -> Оператор try … finally». Исключение – это объект, который может быть создан при появлении ошибки во время работы программы. Этот объект можно перехватить и узнать, какая именно ошибка произошла, и выполнить какие-либо действия. Если происходит ошибка, то такая ситуация называется исключительной. Отсюда и появился сам термин «исключение». Если исключительная ситуация возникает, то программа завершает свою работу. Для того, что бы программа продолжила свою работу после исключительной ситуации, и можно было перехватить исключение, существует конструкция try … except … end. После слова try пишется код, при выполнении которого может возникнуть ошибка, после слова except – код, который будет выполнен в случае возникновения ошибки. Если ошибки не произойдёт, то код после слова except выполнен не будет. После слова end программа продолжить свою работу в любом случае. Как раз между словами except и end можно перехватить исключение и выполнить какие-либо действия. Исключение можно не перехватывать, так же можно не выполнять ни каких действий, в таком случае при появлении ошибки программа просто не закроется и продолжит работу. Пример: var f: file; begin Assign(f,'file.dat'); try reset(f); except writeln('Файл не найден!') end; end. _________________________________ Файл не найден! Как было сказано, между словами except и end можно перехватить исключение. Это исключение, как было так же сказано, является объектом, который создаётся в случае возникновения ошибки. Для того, что бы этот объект был создан необходимо, что бы в подпрограмме, в результате работы которой возникла ошибка, был прописан код, создающий этот объект. Разберём вышесказанное по полочкам. Представим себе, что мы создаём подпрограмму чтения данных из файла. Этой подпрограммой должны будут пользоваться другие программисты, и она будет поставляться в откомпилированном виде. В качестве параметра в данную подпрограмму будет передаваться имя файла и переменная, в которую будет происходить чтение. Если файл с таким именем не существует, то прочитать данные мы не сможем. В таком случае мы должны сообщить программисту, почему подпрограмма не прочитала данные. Как раз для этого и существуют исключения. В нашей подпрограмме в случае отсутствия файла мы создадим объект-исключение, в котором будет содержаться информация об ошибке. Делается это следующим образом: procedure ChteniyeFayla(ImyaFayla:string;var pp:pointer); begin if not FileExists(ImyaFayla) then raise new Exception('Файл "'+ImyaFayla+'" не найден!'); ...................//Читаем данные из фала end; В коде данного примера появилось два незнакомых слова: raise и exception. Слово raise – оператор, который предназначен для «возбуждения» исключения. Если это слово не использовать, то исключение не будет «возбуждено», и при работе самой программы, в конструкции try … except … end, код после слова except выполнен не будет. Так же при использовании данного слова выход из подпрограммы происходит автоматически из того места, где оно находится. После слова raise необходимо создать объект-исключение с помощью операции new. Слово Exception – имя класса. Этот класс уже описан в модуле System, и мы можем им пользоваться для создания исключений. Описание этого класса выглядит следующим образом: type Exception = class public constructor Create; constructor Create(message: string); property Message: string; // только на чтение property StackTrace: string; // только на чтение end; Сообщение, которое мы посылаем конструктору в качестве параметра, содержится в свойстве Message. Свойство StackTrace содержит, как сказано в справке, «стек вызовов подпрограмм на момент генерации исключения». На данном этапе развития оно вам ничего не даст. Пока будем пользоваться только сообщением. Теперь представим себе, как будет пользоваться программист нашей процедурой чтения файла. Код, который позволяет перехватить исключение, он напишет следующим образом: var p:pointer;
begin try ChteniyeFayla('Data.dat',p); except on Isklucheniye:Exception do writeln(Isklucheniye.Message); end; end. _______________________________________________________________ Файл "Data.dat" не найден! Конструкция on … do … позволяет перехватывать исключения и производить какие-либо действия. Перехват исключения происходит после слова on путём создания переменной-указателя на объект-исключение, который был создан при появлении ошибки. В данной ситуации делается это с помощью двоеточия. После создания такой переменной, через неё мы можем получить доступ к свойствам этого объекта-исключения. Произвести какие-либо действия можно после слова do, например, вывести сообщение об ошибке. Такая конструкция называется обработчиком исключения. Теперь попробуем вызвать нашу процедуру ChteniyeFayla не внутри конструкции try … except … end, а просто в теле программы. Если файла на диске не окажется, то вся программа завершит свою работу с ошибкой, в описании которой будет как раз наше сообщение: В данном примере строчка writeln(‘Всё хорошо’) так и не была выполнена. Так как программа завершила свою работу из-за того, что было возбуждено исключение. Думаю, что такие ошибки вы видели уже не раз. Все они заранее продуманы и созданы с помощью исключений, оператора raise и конструкции try … except … end, точно так же как и в нашем примере с процедурой ChteniyeFayla. Теперь вы знаете, что каждая ошибка заранее продумана программистами, и все сообщения так же созданы ими. Если возникнет ошибка не продуманная программистом, то компьютер может в лучшем случае зависнуть, в худшем могут быть повреждены файлы или ещё что-либо. Поэтому корректность работы ваших программ лежит только на ваших плечах. Двигаемся дальше. Представим себе, что в процедуре ChteniyeFayla может возникнуть не одно, а несколько исключений. В таком случае необходимо назвать их различными именами. Для этого необходимо создать производные классы от класса Exception, но с другими именами. Тела таких классов можно не описывать, главное, что бы у них были различные имена, соответствующие исключениям. Соответствовать исключениям они должны только для вашего удобства. Здесь стоит отметить, что все исключения должны быть производными от класса Exception. Для примера представим, что в нашей процедуре может быть ошибка не только при отсутствии файла, но и при переполнении оперативной памяти. Тогда создадим два исключения: FaylOtsutstvuyet и PamyatPolna: type FaylOtsutstvuyet = class (exception); PamyatPolna = class (exception); Эти два класса абсолютно одинаковы, но имеют различные имена. Делается так для того, что бы различать их друг от друга. Для примера создадим ещё одно исключение и создадим примерный код, в котором все они обрабатываются: type TFaylOtsutstvuyetException = class (exception); TPamyatPolnaException = class (exception); TDrugoyeException = class (exception); var p:pointer; procedure ChteniyeFayla(ImyaFayla:string;var p:pointer); begin if not FileExists(ImyaFayla) then raise new TFaylOtsutstvuyetException('Файл "'+ImyaFayla+'" не найден!'); // ...................Читаем данные из фала if {Память полна} true then raise new TPamyatPolnaException('Нехватка памяти.'); // ...................Читаем данные из фала raise new TDrugoyeException; end; begin try ChteniyeFayla('Data.dat',p); except on FaylOtsutstvuyetException:TFaylOtsutstvuyetException do writeln(FaylOtsutstvuyetException.Message); on PamyatPolnaException:TPamyatPolnaException do writeln(PamyatPolnaException.Message); else writeln('Какое-то другое исключение.'); end; end. Как видите, конструкций, обработчиков исключений, может быть сколько угодно, в зависимости от того, сколько исключений необходимо обработать. Нужный обработчик выбирается автоматически в зависимости от произошедшего исключения. В конце раздела except после всех конструкций on … do … можно поставить слово else и код, который нужно выполнить в случае, если не нашлось ни одного обработчика для случившегося исключения. Предлагаю поэкспериментировать с кодом данного примера следующим образом. В начале, запустите программу при отсутствующем файле на диске – вы получите следующий результат: Файл "Data.dat" не найден! Затем создайте файл с именем «Data.dat» в том же месте жёсткого диска, где находится сама программа, и запустите программу. Результат будет следующим: Нехватка памяти. Затем замените в 12 строчке true на false: Какое-то другое исключение. Надеюсь, что после этих экспериментов у вас в голове немного просветлело по поводу данной темы. На данном этапе развития, возможно, вы не будете создавать свои исключения, т.к. вряд ли вы будете писать готовые классы или модули и поставлять их кому-то в откомпилированном виде. В своих программах все ошибки можно обойти и без использования исключений. Однако, в любом случае, вы будете пользоваться чужими готовыми модулями и классами, поэтому принцип формирования исключений вы должны знать для того, что бы эффективно ими пользоваться. Рекомендую перечитать все разделы справки связанные с этой темой самостоятельно. Они были приведены в начале данной темы. Данная тема описана там несколько другим языком. Думаю, будет полезно взглянуть на эти же вещи с другой стороны. На мой взгляд, зацикливаться на данной теме не стоит, т.к. в ближайшее время вы, может быть, вообще ей не воспользуетесь. Однако придёт время, и вы к ней вернётесь. И если сейчас вы потратите время и поразбираетесь в ней, то потом вам будет легче. В данном параграфе мы изучили тему исключения. Задача. В предыдущем параграфе необходимо было усовершенствовать класс TSpisok, создав на его основе производный – TModernSpisok. В данной задаче необходимо во всех методах этого класса, работающих с номерами элементов, «возбудить» исключение в случае, если элемента с таким номером не существует. Переписать программу ModernSpisok таким образом, что бы она демонстрировала работу исключений. Решение. program ModernSpisokSIsklucheniyami; type TElement = record Data:integer; p:pointer; end; type TSpisok = class .................. end; type TNomerMensheKolichestvaElem = class (Exception); type TModernSpisok = class (TSpisok) public //Содержит текущий элемент property TekElem:integer write write read read; //Возвращает количество элементов в списке function KolElem:integer; begin var i:=0; Nachalo; while not EOS do begin pEl:=pEl^.p; Inc(i); end; KolElem:=i; end; //Возвращает значение элемента по номеру function ElemPoNomeru(n:integer):integer; begin ProverkaNomera(n); Poziciya(n); ElemPoNomeru:=pEl^.Data; end; //Меняет значение элемента с номером n procedure PerepisElPoNomeru(n,dat:integer); begin ProverkaNomera(n); Poziciya(n); pEl^.Data:=Dat; end; //Вставляет новый элемент на место старого с номером n //при этом весь список сдвигается procedure Vstavit(n,dat:integer); begin ProverkaNomera(n); Poziciya(n); new(PSled); pSled^.p:=pEl^.p; pSled^.Data:=pEl^.Data; pEl^.Data:=dat; pEl^.p:=pSled; end; //Удаляет из списка элемент с номером n procedure Udalit(n:integer); begin if (n>1) and (n<>KolElem) then begin//Если элемент где-то в середение Poziciya(n); pSled:=pEl^.p; dispose(pEl); Poziciya(n-1); pEl^.p:=pSled; end else if n=1 then begin//если удаляем первый элемент Poziciya(1); P1:=pEl^.p; dispose(pEl); end else begin//если удаляем последний элемент Poziciya(n); pEl^.p:=nil; Poziciya(n+1); Dispose(pEl) end; end; //Очищает весь список procedure Ochistit; begin Nachalo; var pTemp:pointer; pTemp:=pEl^.p; pEl^.p:=nil; pEl:=pTemp; while pEl^.p<>nil do begin pTemp:=pEl^.p; dispose(pEl); pEl:=pTemp; end; end; protected procedure Poziciya(n:integer);//Переход на позицию n begin ProverkaNomera(n); Nachalo; var i:integer:=1; for i:=1 to n-1 do pEl:=pEl^.p; end; //Если n больше чем количество элементов, то возбуждается исключение procedure ProverkaNomera(n:integer); begin if n>KolElem then raise new TNomerMensheKolichestvaElem('Элемента с номером ' +IntToStr(n)+' нe существует'); end; end; var Sp:TModernSpisok:=new TModernSpisok; begin writeln('В списке ',Sp.KolElem,' элементов.'); var max:=random(1,10); for var i:=1 to max do begin var temp:=random(1000); Sp.TekElem:=temp; writeln(i:2,' temp = ',temp); end; //Выводим список на экран Sp.Nachalo; while not Sp.EOS do write(Sp.TekElem,' '); writeln; writeln('Теперь в списке ',Sp.KolElem,' элементов.'); //Выводим элемент с номером temp var temp:=Random(1,max+3); try Writeln('Элемент с номером ',temp,' равен ',Sp.ElemPoNomeru(temp)); except on Ex1:TNomerMensheKolichestvaElem do writeln('При доступе к элементу произошла следующая ошибка: ' ,Ex1.Message); end; //Меняем значение этого элемента try Sp.PerepisElPoNomeru(temp,Random(1,1000)); except on Ex2:TNomerMensheKolichestvaElem do writeln('При замене элемента ', 'произошла следующая ошибка:',Ex2.Message); end; //Снова выводим этот элемент try Writeln('После изменения элемент с номером ',temp,' равен ' ,Sp.ElemPoNomeru(temp)); except on Ex3:TNomerMensheKolichestvaElem do writeln('При повторном выводе ', 'элемента произошла следующая ошибка:',Ex3.Message); end; //Вставляем элемент try Sp.Vstavit(temp,Random(1,1000)); except on Ex4:TNomerMensheKolichestvaElem do writeln('Во время вставки элемента', ' произошла следующая ошибка: ',Ex4.Message); end; //Выводим новый список writeln('После вставки нового элемента на место ',temp, ' список выглядит следующим образом:'); Sp.Nachalo; while not Sp.EOS do write(Sp.TekElem,' '); writeln; //Удаляем элемент под номером random(1,max+3) и выводим новый список temp:=Random(1,max+3); try Sp.Udalit(temp); except on Ex5:TNomerMensheKolichestvaElem do writeln('Во время удаления элемента', ' произошла следующая ошибка: ',Ex5.Message); end; writeln('После удаления элемента под номером ',temp,' список выглядит'+ ' следующим образом:'); Sp.Nachalo; while not Sp.EOS do write(Sp.TekElem,' '); writeln; //Очищаем список и для проверки вызываем метод Sp.KolElem Sp.Ochistit; writeln('После очистки списка количество элементов в нём равно - ', IntToStr(Sp.KolElem)); end. _________________________________________________________________________ В списке 0 элементов. 1 temp = 551 2 temp = 429 3 temp = 804 4 temp = 565 5 temp = 564 6 temp = 184 7 temp = 235 551 429 804 565 564 184 235 Теперь в списке 7 элементов. При доступе к элементу произошла следующая ошибка: Элемента с номером 9 нe существует При замене элемента произошла следующая ошибка: Элемента с номером 9 нe существует При повторном выводе элемента произошла следующая ошибка: Элемента с номером 9 нe существует Во время вставки элемента произошла следующая ошибка: Элемента с номером 9 нe существует После вставки нового элемента на место 9 список выглядит следующим образом: 551 429 804 565 564 184 235 Во время удаления элемента произошла следующая ошибка: Элемента с номером 10 нe существует После удаления элемента под номером 10 список выглядит следующим образом: 551 429 804 565 564 184 235 После очистки списка количество элементов в нём равно - 0 Примечание: класс TModernSpisok остался практически без изменения. Добавился только метод ProverkaNomera, в котором и возбуждается исключение. Этот метод вызывается всеми другими методами, которые обращаются к элементу с определённым номером.
|