Object Pascal 语法之异常处理

时间:2022-07-10 05:36:12

http://www.cnblogs.com/spider518/archive/2010/12/30/1921298.html

3 结构化异常处理

结构化异常处理(SHE)是一种处理错误的手段,使得应用程序能够从致命的错误中很好地恢复。

异常的来源
  在Delphi的应用程序中,下列的情况都比较有可能产生异常。  
  (1)文件处理  
  (2)内存分配  
  (3)Windows资源 
  (4)运行时创建对象和窗体  
  (5)硬件和操作系统冲突

在早期的Delphi 中,异常是由Object Pascal 语言来处理的;从Delphi 2 开始,异常成为Win32 API 的一部分。用Object Pascal 来处理异常比较简单,因为异常中包含了错误的位置和特征性信息,这使得异常的使用和实现与普通的类一样。当一个错误或一些其他事件中止了程序的正常运行,系统就会抛出一个异常。

Delphi 中包含了一些预定义的通用的程序错误异常,例如内存不足、被零除、数字上溢和下溢以及文件的输入输出错误,程序开发人员可以定义自己的异常类来适应程序的需要。通过Delphi 的异常处理机制,可以捕获这个异常并进行处理。异常实际上可以是一些对象,也可以是任何类的一个实例。通常程序开发人员总是自己定义一个从Exception 类派生出的异常类,其定义的方法与普通类的定义方法基本一致。Exception 类是在SysUtils 单元中定义的。如果一个程序的Uses 语句中包含了SysUtils 单元,发生运行错误时就会抛出一个异常。可以利用类的继承性将一组异常组合成一个系列。例如在SysUtils 单元中就定义了有关数学方面的一组异常类:

type
EMathError = class(Exception);
EInvalidOp = class(EMathError);
EZeroDivide = class(EMathError);
EOverflow = class(EMathError);
EUnderflow = class(EMathError);

有时在异常类中还定义一些字段、属性和方法,通过它们可以传达一些错误信息。例如:

type
EInOutError = class(Exception)
ErrorCode: Integer;
end;
3.1 Try...Except 语句和Try...Finally 语句

在Try...Except 语句中可以进行抛出异常和处理异常的工作。Try...Except 的一般形式如下:

try
Statements1;
except
on Exception1 do HandleStatements1;
on Exception2 do HandleStatements2;
...
on ExceptionN do HandleStatementsN;
else
Statements2;
end.

对于有些操作,在异常处理部分要进行,在正常情况下也要进行。例如在正常情况下,使用完文件之后关闭文件;如果在对文件操作的过程中出现了异常,也需要关闭已经打开的文件。这时,就可以把关闭文件的过程放在Try...Finally 语句的Finally 部分,不管Try 部分的操作是否正常,都要进行Finally 部分的操作。

通常Try...Finally 语句的形式如下:

try
statementList1
finally
statementList2
end

可以看到,Try...Finally 语句的用法与Try...Except 语句的用法很相似。StatementList1 可以为简单语句,也可以为复合语句。如果在StatementList1 中抛出了异常,程序立即转到Finally 部分;如果在StatementList1 中执行了Exit、Break 或Continue 过程而导致程序的控制离开StatementList1 部分时,程序也会跳转到Finally 部分;如果在Try 部分正常执行完毕,接着执行的还是Finally 部分。

下面的例子介绍了在文件输入/输出时, 怎样用异常处理。可以区分与Try...Except 语句和Try...Finally 语句的用法。

program Project1;

uses
Classes,Dialogs,SysUtils; {$APPTYPE CONSOLE} var
F:TextFile;
S:String; begin
AssignFile(F,'f1.txt');
try
reset(F);
try
readln(F,s);
finally
CloseFile(F);
end;
except
on EinOutError do
ShowMessage('Error in FileIO');
end;
end.

内层的Try...Finally 代码块用来确保文件总是关闭的,而不管是否发生了异常。这段代码的执行过程是:先执行Try 与Finally 之间的代码;如果执行完毕或出现异常,就执行Finally 与End 之间的代码;如果确实有异常发生,就跳到外层的异常处理块。这样,即使出现异常,文件也总是关闭的,并且异常总能得到处理。

注意:在Try...Finally 块中,Finally 后面的语句不管有没有异常都被执行。因此,Finally 后面的语句不能以发生异常为前提。另外,由于Finally 后面的语句并没有处理异常,因此,异常被传递到下一层的异常处理块。外层的Try...Except 块用于处理程序中发生的异常。在Finally 中关闭文件,并且在Except 块显示一个信息,告诉用户发生了I/O 错误。这种异常处理机制比传统的错误处理方式优越,它使得错误检测代码从错误纠正代码中分离出来。这是一件好事,它会使程序更可读,使得开发人员能够集中处理程序的其他代码。

使用Try...Finally 代码块,但不捕捉特定种类的异常是有一定意义的。当代码中使用Try...Finally块的时候,意味着程序并不关心是否发生异常,而只是想最终总是能进行某项任务。Finally 块最适合于释放先前分配的资源(例如文件或Windows 资源),因为它总是执行的(即使发生了错误)。不过,很多情况下,可能需要对特定的异常做特定的处理,这时候就要用Try...Except 块来捕捉特定的异常。

例如:

Program Project1;
{$APPTYPE CONSOLE} uses
SysUtils,Dialogs; var
R1,R2:double; begin
while True do begin
try
write('Enter a real number: ');
readln(R1);
write('Enter another real number: ');
readln(R2);
writeln('The first number divided by the Second is: ',(R1/R2):5:3);
except
on EInOutError do
ShowMessage('It is not a valid number! ');
on EZeroDivide do
ShowMessage('Can not divide by zero! ');
end;
end;
end.

尽管在Try...Except 块中可以捕捉特定的异常,也可以用Try...Except...Else 结构来捕捉其他异常。当使用Try...Except...Else 结构的时候,应当明白Else 部分会捕捉所有的异常,包括那些并没有预料到的异常,例如内存不足或其他运行期异常。因此,使用Else 部分时要小心,能不用则不用。当进入不合格的异常处理过程中时,应当触发这个异常。

不存在try…except…finally…end结构来既处理异常,又保护资源分配的结构,但是,try…except…end结构允许嵌套到try…finally…end结构中,从而实现既处理异常,又保护资源的分配。

3.2 Raise 语句

使用Raise 语句调用一个异常类的构造函数,并抛出一个异常。例如:raise EInOutError.Create;
通常,Raise 语句的形式如下:

raise object at address

其中Object 和At Address 是可选项,Address 通常是一个指向过程或函数的指针。一个抛出的异常在处理后自动地被删除,一般不需要主动地删除一个异常对象。

3.3 异常类

异常是一种特殊的对象实例,它在异常发生时才实例化,在异常被处理后自动删除。异常对象的基类被称为Exception。在异常对象中最重要的元素是Message 属性,它是一个字符串,它提供了对异常的解释,由Message所提供的信息是根据产生的异常来决定的。

注意:如果定义自己的异常对象,一定是要从一个已知的异常对象例如Exception 或它的派生类派生出来的,因为这样,通用的异常处理过程才能捕捉这个异常。当在Except 块中处理一个特定的异常时,可能会捕捉到该异常的派生异常。例如EMathError 是所有与数学有关的异常(例如EZeroDivide、EOverflow)的祖先。凡是没有显式地处理的异常最终将被传送到Delphi 运行期库中的默认处理过程并在此得到处理。

Exception异常类

Exception是所有异常类的基类,它并不是以’T'开头,而是以’E'开头,它的派生类也是以’E'开头的。Exception类定义于SysUtils单元中。Exception类最常用的方法是Create方法:

Constructor Create(const Msg:string);
Exception.Create(‘我自己创建的异常!’);

该方法用于创建一个异常类的实例,也可以显示错误信息,也可直接用这个方法提交一个异常

raise Exception.Create(‘我抛出的异常!’);

例:

  try
raise Exception.Create('我抛出的异常!');
except
on E: Exception do
showmessage('异常类名称:' + E.ClassName + ## + '异常信息:' + E.Message);
end;

Delphi7内置的异常类

Delphi7根据异常现象的类型定义了相应的异常类,这些异常类又称为Delphi7内置的异常类。具体分为运行时库异常类对象异常类组件异常类三大类.

运行时库异常类(RTL)

运行时库异常类可分为以下几种:

  • 1 整数计算异常
  • 2 浮点计算异常
  • 3 硬件异常
  • 4 堆异常
  • 5 输入输出异常(I/O异常)
  • 6 字符转换异常
  • 7 类型转换异常
  • 8 哑异常

整数计算异常

EIntError 整数计算异常(基类)
EDivByZero 整数除0溢处
EIntOverFlow 整数溢出
ERangeError 整数越界

浮点计算异常

EMathError 浮点计算异常(基类)
EInvalidOp 无效浮点操作指令
Eoverflow 浮点操作上溢
Eunderflow 浮点操作下溢
EZeroDivide 浮点计算除0

硬件异常

EProcessorException 硬件异常(基类)
ESingleStep 应用程序产生单步中断
Ebreakpoint 应用程序产生断点中断
Efault 故障(继承EProcessorException,也是基类)
EStackFault 对处理器栈段的非法访问
EPageFault 内存管理器无法正确使用交换文件
EGPFault 保护性错误,一般由未初始化指针或对象造成
EInvalidOpCode 处理器遇到未定义指令

堆异常

EOutOfMemory 堆中没有足够的内存完成操作
EInvalidPointer 试图访问一个堆外的指针

I/O异常

EInOutError DOS输入/输出错误

字符转换异常

EConvertError 数字到字符串或字符串到数
字转换错误

类型转换异常

EInvalidCast 类型转换异常

哑异常

EAbort 调用Abort产生,不显示错误提示框

对象异常类

对象异常类是针对非组件对象引发的异常而定义的.
对象异常类包括:

  • 1 流异常类
  • 2 打印异常类
  • 3 图形异常类
  • 4 字符串链表异常类

流异常类

流异常是指在程序中进行与流相关的操作时产生的异常.流异常类的基类是EStreamError,其他流异常类都直接或间接从它派生.
派生关系见书48页图

打印异常

打印异常是由于应用程序向不存在的打印机发送打印命令或由于某种原因打印工作无法送到打印机时引发的.
打印异常类为Eprinter,定义于Printers单元

图形异常

图形异常主要包括EInvalidGraphic 和 EInvalidGraphicOperation两个类均定义于Graphics单元

EInvalidGraphic异常满足下列情况之一时引发:

  • 当应用程序试图向一个并不包含合法的位图,图象,元文件或用户自定义图形类型的文件中装入图象时.
  • 当应用程序试图装入不可识别扩展名的文件时
  • 当图象与LoadFromClipboardFormat或SaveToClipboardFormat中的格式不匹配时.
  • 当应用程序试图将图象的PixelFormat设为不支持的值

EInvalidGraphicOperation异常在满足下列条件之一时发生:

  • 应用程序访问图象中不存在的扫描线时.
  • 应用程序不能成功写入图象时.
  • 应用程序在画布未处于有效状态时进行绘制.
  • 应用程序装入未知或不支持的图象格式时.
  • 应用程序将图象的PixelFormat设为不支持的值时
  • 不能分配该操作的句柄时.

字符串链表异常

字符串链表异常是由于用户对字符串链表进行非法操作时引发的。包括EStringListError,EListError等.由于许多部件都有一个Tstrings抽象类的属性(如Tiistbox组件的Items属性等),因而字符串链表异常在组件编程中很重要.

EStringListError一般在字符串链表越界时产生.而EListError异常通常在以下情况下发生:

当索引项超出链表范围时
当字符串链表的Duplicates属性设置为dupError
同时应用程序试图加入一个重复的字符串时.
当向已排序的字符串链表中插入字符串时.

组件异常类

组件异常类用于响应组件异常,组件异常是由于对VCL组件进行操作时,违反了组件的使用规则及其特征而引发的,可分为两大类: 用组件异常、专用组件异常。

通用组件异常
常见的有非法操作异常,组件异常和资源不足异常三种类型,对应于EInvalidOpetation,EComponentError和EOutOfResource异常类.

引发非法操作异常的原因有:

应用程序试图对Parent属性为nil的组件进行一些需要窗口句柄的操作.
试图对窗体拖放操作.

引发组件异常的原因有:

Delphi不能注册某个组件

应用程序不能重命名某个组件

资源不足异常被引发是由于当应用程序试图创建窗口句柄而操作系统没有多余的句柄可分配

专用组件异常:许多组件都定义了相应的组件异常类.

列出几个典型的组件异常类:

EMenuError异常,菜单异常,是由于程序对菜单的非法操作而引发的.定义于Memus单元
EInvalidGridOperation异常.非法的网格操作,如试图引用一个不存在的网格单元时引发.定义于Grids单元
EDatabaseError异常.数据库异常是由于对数据库的非法操作引起的.

用户自定义异常类

创建用户自定义异常类的方法

选择Exception作为基类,按照定义类的一般方法,建立自定义的异常类就可以了.
如:

type
EMyException = class(Exception)
//需要定义属性或方法时,写在此处即可
end;抛出自定义异常

异常的调试
  在Delphi IDE中,解除“Debugger Options”(可以使用菜单Tools—>Debugger Options…进行访问)中的Integrated Debugging复选框的勾选状态可以进行异常的调试。