📄 chapt9.htm
字号:
<html><head><title>第九章 处 理 异 常</title><meta http-equiv="Content-Type" content="text/html; charset=gb2312"></head><body bgcolor="#00000" text="#00cc66"><p align="center"><b><font color="#FF6666" size="4">第九章 处 理 异 常</font></b></p><p> 解决错误是每个程序人员必有的经历,编写安全、健壮的应用程序是每个程序员的始终追求。然而,产生错误总是难免的,尽管程序员尽职尽责,即使程序没有一个错误,各种与程序一起工作的软件、硬件也可能发生错误。一个可靠的程序应该可以处理所有可能导致程序停止或产生不恰当结果的状态。<br> 这种状态在Delphi中称之为异常(Exception)。Delphi提供了高级异常处理机制,可以确保程序从可能发生的错误中恢复过来,而且可以保证数据或资源不丢失,并在需要的情况下,关闭系统。<br> 每个Delphi程序都有一个缺省的异常处理器,用来显示错误消息,防止程序意外停止。通过加入附加异常处理,用户可以更好地处理应用程序对意外条件的响应,以便当麻烦发生时,执行清理工作,保存重要数据和资源。下面,我们就来讨论有关Delphi异常处理机制的使用。<br> <b>9.1 异常的产生</b><br> 设计健壮程序的关键之一是,如果程序分配了资源,最后就必须释放它,而不管异常是否发生。例如,如果你的程序分配了一块内存,必须确保最终释放这块内存;如果打开了一个文件,必须确保最后关闭这个文件。<br> 在正常情况下,程序在申请资源后,能够释放它。然而,就算异常发生了,也必须保证释放它。<br> 异常往往由一些偶然的事件导致。例如,程序中调用一个RTL例程,或使用一个构件都可能产生异常。你的程序必须确保即使这些偶然事件发生了,也要释放被占用的资源。<br> 常见的需要保护的资源有:文件、内存、Windows资源 、各种对象等。 <br> <b>9.2 异常处理语句</b><br> 要处理异常,首先必须识别异常,判断异常在什么时候发生,然后对出现的异常作出处理。因此对于程序员来说,其工作就是确定程序中哪个地方产生错误及发生错误时的处理程序,特别是容易导致数据或系统资源丢失的错误。<br> 被保护代码块是用户处理异常的基本工具。对异常的响应是在代码保护块中进行的。当有若干条语句需要处理同一个错误时,可以将这些语句放在一个代码块里,并对整个代码块定义一个响应过程。这个对异常响应的代码块称为保护块。<br> 处理代码保护块异常及其异常处理有两种方式,即try...finally语句和try...except语句。这两种语句可以互相嵌套,形成更加牢固的异常处理结构。<br> <b>9.2.1 try...finally</b><br> 有时侯,我们只需要程序中操作某项任务的代码执行完成,而不管是否发生了异常。例如,当一段程序获得了某项资源时,我们需要释放它,而不管程序是否正常终止。在这种情况下,就可以使用try...finally语句。<br> try...finally语句的语法结构是:<br> try statementList1 finally statementList2 end<br> 这里statementList1和 statementList2是由分号隔开的一系列执行语句。程序首先从statementList1开始执行,如果一切正常,将依此执行statementList2中的语句。但是,当程序在statementList1执行时产生异常时,程序将直接从出错的地方跳到statementList2执行。因此,statementList2语句总是会被执行的,无论statementList1程序是否出错。statementList2语句通常用于释放程序占用的资源。<br> 又例如,下面是一段操作文件的代码:<br> Reset(F);<br> try<br> ... // process file F<br> finally <br> CloseFile(F);<br> end;<br> 文件用来存放在硬盘的资源,在打开后,不管操作情况如何,最后都必须关闭它,否则容易造成数据丢失。<br> 但是,假如statementList2语句中出现了问题,并且触发了异常,则该异常就被传递到try...finally语句的外面,同时原来在statementList1语句中触发的所有异常都将丢失。因此,try...finally语句也有一定缺陷。<br> <b>9.2.2 try... except</b><br> 异常通常是在try... except中进行的。<br> try...except 语句的语法结构是:<br> try statements except exceptionBlock end<br> 这里statements是由分号隔开的一系列执行语句。exceptionBlock可以是由分号隔开的一系列执行语句,也可以是一系列专门处理异常的程序句柄。<br> 处理异常的程序句柄的语法规则是:<br> on identifier: type do statement<br> 这里,identifier是任何合法的标识符,也可以省略,type表示异常的类型,statement是执行语句,但如果是复合语句,必须用begin...end括起来。处理异常程序句柄之后可以跟有else statements语句。<br> try...except 语句首先从statements开始执行,如果一切正常,exceptionBlock将被忽略,直接执行try...except 语句后面的语句。但是,当程序statements在执行中产生异常时,程序将直接从出错的地方跳到exceptionBlock执行。<br> exceptionBlock执行时,首先按顺序判断处理块中异常程序句柄中异常的类型,如果有一个与产生的异常类型相同,则执行类型后面的statement语句,如果没有与产生的异常类型相同的类型,但是在exceptionBlock后面有else子句,则程序转到该else子句执行。如果exceptionBlock仅仅是由一些普通的语句组成,则程序转到其中的第一条语句执行。如果在exceptionBlock什么也没有,则该异常将向外传递。<br> 下面是一个例子:<br> try<br> ...<br> except<br> on EZeroDivide do HandleZeroDivide;<br> on EOverflow do HandleOverflow; <br> on EMathError do HandleMathError;<br> end;<br> 在上面的代码中,第一个异常处理句柄处理被0除异常;第二个异常处理句柄处理溢出异常;第三个异常处理句柄处理数学运算错误异常。这里,第三个异常处理句柄放在最后,是因为数学运算错误异常包含了前面两个异常类,如果它放在前面,则其它异常处理都不会被执行。前面我们看到,异常类名的前面可以加一个标识符。这个标识符用来在该异常句柄中表示该异常对象。这个标识符的使用范围只限于该异常句柄。例如:<br> try<br> ...<br> except on E: Exception do ErrorDialog(E.Message, E.HelpContext);<br> end;<br> 如果异常块后面带有else子句,则else子句用于处理没有被exceptionBlock处理的任何异常。<br> except <br> on EZeroDivide do HandleZeroDivide;<br> on EOverflow do HandleOverflow;<br> on EMathError do HandleMathError;<br> else <br> HandleAllOthers;<br> end;<br> 这里,else子句处理任何不是EMathError异常类的异常。如果exceptionBlock仅仅是由一些普通的语句组成,则产生的任何异常都会执行这些语句。例如:<br> try<br> ...<br> except <br> HandleException;<br> end;<br> 这里,HandleException例程将处理执行try和except之间语句时发生的任何异常。 <br> <b>9.2.3 嵌套异常响应</b><br> 由于Pascal允许语句嵌套,因此,可以在已经定义了响应的保护块内再定义响应,即try...finally语句和try...except语句可以互相嵌套。 <br> <b>9.3 缺省的异常处理</b><br> 如果你没有对异常作出响应,Delphi就提供缺省的处理方式。Delphi缺省异常处理会提示一个错误信息。Delphi缺省异常处理实际上是调用TApplication类的HandleException方法。<br> 对于所有异常(EAbort除外), HandleException首先检查OnException事件句柄是否存在。如果存在,则执行这个事件句柄。如果不存在,就自动调用SHowException方法。<br> 如果你既要处理异常,又要像VCL构件那样提供缺省的行为,可以在响应异常时,调用HandleException方法。例如下面代码<br> try <br> { 保护语句 } <br> except <br> on ESomething do <br> begin<br> { 处理异常 }<br> Application.HandleException(Self); {调用HandleException }<br> end; <br> end;<br> 另外,OnException事件的处理代码可以被修改,可以用自己的代码代替缺省的异常处理句柄。办法就是响应OnException事件,参考下面的程序示例。这段程序显示,如果异常没有被处理,则调用缺省异常处理。缺省异常处理显示错误,结束程序。procedure TForm1.FormCreate(Sender: TObject);<br> begin <br> Application.OnException := AppException;<br> end;<br> procedure TForm1.AppException(Sender: TObject; E: Exception);<br> begin <br> Application.ShowException(E); <br> Application.Terminate;<br> end; <br> <b>9.4 Exception类</b><br> Exception类是所有异常类的基类,其本身直接继承于TObject类,在Delphi的Sysutils单元中,Exception类的定义如下(只列出公共成员):<br> Type Exception=Class(TObject) <br> public <br> Constructor Create(const Msg:string); <br> Constructor CreateFmt(const Msg:string;const Args:array of const); <br> Constructor CreateRes(Ident:Integer); <br> Constructor CreateResFmt(Ident:Integer;const Args:array of const); <br> Constructor CreateHelp(const Msg:string;HelpContext:Integer); <br> Constructor CreateFmtHelp(const Msg:string;const Args:array of const;HelpContext:Integer); <br> Constructor CreateResHelp(Ident,HelpContext:Integer); <br> Constructor CreateResFmtHelp(Ident:Integer;const Args:array of const;HelpContext:Integer); <br> Property HelpContext:Integer; <br> Property Message:String;<br> end;<br> Exception类中只申明了两个特性:一个是Message特性,用于给出有关异常的简短说明;另一个是HelpContext特性,用于指定帮助的上下文编号,任何一个异常都可以访问这两个特性 。<br> Exception类提供了一些构造异常的方法。所有构造函数的名字都以Create开头,它们都有相同的目的,就是根据不同的参数来创建一个错误提示信息字符串。用户可以调用这些构造函数来创建自己的派生类的异常实例,或者可以为Exception对象调用它们。<br> 例如,把一个字符串或变量作为错误消息来传递,生成异常的语句可以是:<br> raise Exception.Create('Error In Memory');<br> 又例如,下面语句生成的异常显示带有两个整数的错误信息字符串:<br> raise Exception.CreateFmt('Error:X=%d Y=%d',{X,Y]);<br> 下面是这个语句的一个运行结果: Error:X=-1 Y=12 <br> <b>9.5 自定义异常</b><br> 当用户需要产生异常时,可以选择从Exception中派生出来的一个类或自己创建异常类。用户可以从Exception中派生自己的类,也可以设计一个全新的类,这样的类不需要建立在Exception的基础上。但是,Delphi建议定义异常时,最好用Exception类作为基类,因为不是从Exception中派生的类必须处理所有不是基于Exception的异常,任何未处理的异常都会引起致命的应用程序错误。<br> 自定义异常类的程序如下: <br> type EMyException = class(Exception)<br> 上面,定义了一个异常类,该类的基类是Exception.<br> 当然,异常不一定必须直接从Exception类继承,也可以用其它Exception类的派生类作为自己的基类,例如: <br> type <br> EMathError = class(Exception); <br> EInvalidOp = class(EMathError); <br> EZeroDivide = class(EMathError); <br> EOverflow = class(EMathError); <br> EUnderflow = class(EMathError);<br> 在声明异常时,可以根据需要声明一些字段、方法和特性。例如,SysUtils单元中的EInOutError异常就声明了一个ErrorCode字段,用于存储引起异常的文件I/O错误代码,示例如下:<br> type EInOutError = class(Exception) <br> ErrorCode: Integer; <br> end;<br> 声明了自己的异常后,需要在程序中触发这个异常,使用Raise语句。Raise语句的语法格式:<br> raise object at address<br> 这里object和 at address都是可选的。如果省略了object,表示要重新触发当前异常。 这种形式只能出现在try...Except结构的Except部分,主要用于过程或函数的调用中出现无法完全处理异常情况。raise后面的object是异常的对象实例,而不是异常的类型。通常把创建对象实例与触发这个异常并在一句中,因为构造函数返回的总是该类型的对象实例。如<br> EPasswordInvalid = class(Exception);<br> 在触发异常时,创建对象实例与触发这个异常并在一句中。<br> if Password <> CorrectPassword then<br> raise EPasswordInvalid.Create('Incorrect password entered');<br> 注意:虽然程序创建了异常对象实例,但程序不必自己删除它,因为异常处理句柄会自动删除异常的实例。<br> 关键步骤是程序中定义了一个TMouseException类。其定义方法是:<br> type <br> TMouseException = class(Exception) <br> X, Y: Integer; <br> constructor Create(const Msg: string; XX, YY: Integer);<br> end; <br> TMouseException类是从Exception类派生出来的。除了从Exception继承来的成员外,TMouseException类还增加了整型成员X和Y,并说明了一个构件函数,可以调用这个构造函数来初始化该类的对象。其构造函数的编程为:<br> constructor TMouseException.Create(const Msg: string; XX, YY: Integer);<br> begin <br> X := XX; // Save X and Y values in object <br> Y := YY; <br> Message := // Create message string Msg + ' (X=' + IntToStr(X) + ', Y=' + IntToStr(Y) + ')';<br> end;<br> 这里,Message是从Exception来继承过来的,它被赋值给了包括这两个值的一个消息。 </p></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -