在应用程序开发中如何检测、处理程序的运行错误是一个很重要的问题。在 Delphi 的集成开发环境( IDE )中提供了一个完善的内置调试器,可以帮助你发现大部分程序错误。但并不是所有的错误都可以被发现,而且当程序涉及到与外设的数据交换或操作外设,如要求用户输入、读写磁盘等时,错误的发生是程序无法控制的,如输入非法字符、磁盘不能读写等。这些情况不仅会导致应用程序异常中止而且可能引起系统的崩溃。针对这些问题,Delphi同时提供了一套强大的异常处理机制。巧妙地利用它,可以使你的程序更为强健,使用更为友好。
虽然Delphi为应用程序提供了一套缺省的自动异常处理机制,即当前模块发生错误后退出当前模块并给出错误信息,而并不立即引起应用程序的中止。但当应用程序执行的过程性很强时,仅仅利用这种方法是不够的,而且很容易导致程序执行的不可预测性。
12.1 Delphi异常处理机制与异常类
Delphi异常处理机制建立在保护块(Protected Blocks)的概念上。所谓保护块是用保留字try和end封装的一段代码。保护块的作用是当应用程序发生错误时自动创建一个相应的异常类(Exception)。程序可以捕获并处理这个异常类,以确保程序的正常结束以及资源的释放和数据不受破坏。如果程序不进行处理,则系统会自动提供一个消息框。
异常类是Delphi异常处理机制的核心,也是Delphi异常处理的主要特色。下面我们对异常类的概念和体系进行详细的介绍。
Delphi提供的所有异常类都是类Exception的子类。用户也可以从Exception派生一个自定义的异常类。
Exception类的定义如下,对于不常用的成员没有列出。
{SysUtils 单元中}
Exception = class(TObject)
private
FMessage: PString;
FHelpContext: Longint;
function GetMessage: String;
procedure SetMessage(const Value: String);
public
constructor Create(const Msg: String);
constructor CreateFmt(const Msg: String; const Args: array of const);. . .
destructor Destroy; override;
property HelpContext: Longint
property Message: String;
property MessagePtr: PString;
end;
Exception的一系列构造函数中最重要的参数是显示的错误信息。而数据成员中最重要的也是可被引用的消息字符串(message,messagePtr)。 这些信息分别对自定义一个异常类和处理一个异常类有重要作用。
Delphi提供了一个很庞大的异常类体系,这些异常类几乎涉及到编程的各个方面。从大的方面我们可以把异常类分为运行时间库异常、对象异常、部件异常三类。下面我们分别进行介绍。
12.1.1 运行时间库异常类(RTL Exception)
运行时间库异常可以分为七类,它们都定义在SysUtils库单元中。
12.1.1.1 I/O异常
I/O异常类EInOutError是在程序运行中试图对文件或外设进行操作失败后产生的,它从Exception派生后增加了一个公有数据成员ErrorCode,用于保存所发生错误的代码。这一成员可用于在发生I/O异常后针对不同情况采取不同的对策。
当设置编译指示{$I- } 时,不产生I/O异常类而是把错误代码返回到预定义变量IOResult中。
12.1.1.2 堆异常
堆异常是在动态内存分配中产生的,包括两个类EOutOfMemory和EInvalidPointer。
表12.1 堆异常类及其产生原因
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
异常类 引发原因
─────────────────────────────────
EOutOfMemory 没有足够的空间用于满足所要求的内存分配
EInvalidPointer 非法指针。一般是由于程序试图去释放一个业已释放的指针而引起的
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12.1.1.3 整数异常
整数异常都是从一个EIntError类派生的,但程序运行中引发的总是它的子类:EDivByZero,ERangeError,EIntOverFlow。
表12.2 整数异常及其产生原因
━━━━━━━━━━━━━━━━━━━━━
异常类 引发原因
─────────────────────
EDivByZero 试图被零除
ERangeError 整数表达式越界
EIntOverFlow 整数操作溢出
━━━━━━━━━━━━━━━━━━━━━━
ERangeError当一个整数表达式的值超过为一个特定整数类型分配的范围时引发。比如下面一段代码将引发一个ERangeError异常。
var
SmallNumber: ShortInt;
X , Y: Integer;
begin
X := 100;
Y := 75;
SmallNumber := X * Y;
end;
特定整数类型包括ShortInt、Byte以及与整数兼容的枚举类型、布尔类型等。例如:
type
THazard = ( Safety , Marginal , Critical , Catastrophic );
var
Haz: THazard;
Item: Integer;
begin
Item:= 4;
Haz:= THazard ( Item );
end;
由于枚举数越界而引发一个ERangeError异常。
数组元素越界也会引发一个ERangeError异常,如:
var
Values: array[1..10] of Integer;
i: Integer;
begin
for i := 1 to 11 do
Values[i] := i;
end;
ERangeError异常只有当类型检查打开时才会引发。这可以在代码中包含{$R+} 编译指示或设置IDE Option|Project的Range_Checking Option选择框。
EIntOverFlow异常类在Integer、Word、Longint三种整数类型越界时引发。如:
var
I : Integer;
a,b,c : Word;
begin
a := 10;
b := 20;
c := 1;
for I := 0 to 100 do
begin
c := a*b*c;
end;
end;
引发一个EIntOverFlow异常。
EIntOverFlow异常类只有在编译选择框Option|Project|Over_Flow_Check Option选中时才产生。当关闭溢出检查,则溢出后变量保留该类整数的最大范围值。
整数类型的范围如下表。
表12.3 整数类型的范围
━━━━━━━━━━━━━━━━━━━━━━━━━━━
类型 范围 格式
───────────────────────────
Shortint -128 .. 127 有符号8位
Integer -32768 .. 32767 有符号16位
Longint -2147483648 .. 2147483647 有符号32位
Byte 0 .. 255 无符号8位
Word 0 .. 65535 无符号16位
━━━━━━━━━━━━━━━━━━━━━━━━━━━
12.1.1.4 浮点异常
浮点异常是在进行实数操作时产生的,它们都从一个EMathError类派生,但与整数异常相同,程序运行中引发的总是它的子类EInvalidOp、EZeroDivide、EOverFlow、EUnderFlow。
表12.4 浮点异常类及其引发原因
━━━━━━━━━━━━━━━━━━━━━━━━
异常类 引发原因
────────────────────────
EInvalidOp 处理器碰到一个未定义的指令
EZeroDivide 试图被零除
EOverFlow 浮点上溢
EUnderFlow 浮点下溢
━━━━━━━━━━━━━━━━━━━━━━━━
EInvalidOp最常见的引发原因是没有协处理器的机器遇到一个协处理器指令。由于在缺省情况下Delphi总是把浮点运算编译为协处理器指令,因而在386以下微机上常常会碰到这个错误。此时只需要在单元的接口部分设置全局编译指示{$N-},选择利用运行时间库进行浮点运算,问题就可以解决了。
各种类型的浮点数(Real、Single、Double、Extended)越界引起同样的溢出异常。这同整数异常类是不同的。
12.1.1.5 类型匹配异常
类型匹配异常EInvalidCast当试图用As 操作符把一个对象与另一类对象匹配失败后引发。
12.1.1.6 类型转换异常
类型转换异常EConvertError当试图用转换函数把数据从一种形式转换为另一种形式时引发,特别是当把一个字符串转换为数值时引发。下面程序中的两条执行语句都将引发一个EConvertError异常。
var
rl : Real;
int: Integer;
begin
rl := StrToFloat(\' $140.48\');
int := StrToInt(\' 1,402 \');
end;
要注意并不是所有的类型转换函数都会引发EConvertError异常。比如函数Val当它无法完成字符串到数值的转换时只把错误代码返回。利用这一点我们在(6.2)节中实现了输入的类型和范围检查。
12.1.1.7 硬件异常
硬件异常发生的情况有两种:或者是处理器检测到一个它不能处理的错误,或者是程序产生一个中断试图中止程序的执行。硬件异常不能编译进动态链接库(DLLs)中,而只能在标准的应用中使用。
硬件异常都是EProcessor异常类的子类。但运行时间并不会引发一个EProcessor 异常。
表12.5 硬件异常类及其产生原因
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
异常类 引发原因
─────────────────────────────────
Efault 基本异常类。是其它异常类的父类
EGPFault 一般保护错。通常由一个未 初始化的指针或对象引起
EStackFault 非法访问处理器的栈段
EPageFault Windows内存管理器不能正确使用交换文件
EInvalidOpCode 处理器碰到一个未定义的指令。这通常意味着处理器
试图去操作非法数据或未初始化的内存
EBreakPoint 应用程序产生一个断点中断
ESingleStep 应用程序产生一个单步中断
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EFault、EGPFault 往往意味着致命的错误。而EBreakPoint、ESingleStep被Delphi IDE的内置调试器处理。事实上前边的五种硬件异常的响应和处理对开发者来说都是十分棘手的问题。
12.1.2 对象异常类
所谓对象异常是指非部件的对象引发的异常。Delphi定义的对象异常包括流异常、打印异常、图形异常、字符串链表异常等。
12.1.2.1 流异常类
流异常类包括EStreamError、EFCreateError、 EFOpenError、EFilerError、EReadError、EWriteError、EClassNotFound。它们的结构关系如下:
EStreamError
|---------- EFCreateError
|---------- EFOpenError
|---------- EFilerError
|--------- EReadError
|--------- EWriteError
|--------- EClassNotFound
图12.1 流异常结构图
流异常在Classes库单元中定义。
流异常引发的原因如表12.6。
表12.6 流异常类及其产生原因
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
异常类 引发原因
─────────────────────────────────
EStreamError 利用LoadFromStream方法读一个流发生错误
EFCreateError 创建文件时发生错误
EFOpenError 打开文件时发生错误
EFilerError 试图再次登录一个存在的对象
EReadError ReadBuffer方法不能读取特定数目的字节
EWriteError WriteBuffer方法不能写特定数目的字节
EClassNotFound 窗口上的部件被从窗口的类型定义中删除
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12.1.2.2 打印异常类
打印异常类EPrinter当打印发生错误时引发。它在printers库单元中定义。例如你的应用程序试图向一个不存在的打印机打印或由于某种原因打印工作无法送到打印机时,就会产生一个打印异常。
12.1.2.3 图形异常类
图形异常类定义在Graphic 库单元中,包括EInvalidGraphic和EInvalidGraphicOperation两类。
EInvalidGraphic当应用程序试图从一个并不包含合法的位图、图标、元文件或用户自定义图形类型的文件中装入图形时引发。例如下面的代码:
Image1.Picture.LoadFromFile(\'Readme.txt\');
由于Readme.txt并不包含一个合法的图形,因而将引发一个EInvalidGraphic异常。
EInvalidGraphicOperation当试图对一个图形进行非法操作时引发。例如试图改变一个图标的大小。
var
AnIcon: TIcon;
begin
AnIcon := TIcon.Create;
AnIcon.LoadFromFile(\'C:\WINDOWS\DIRECTRY.ICO\');
AnIcon.Width := 100; { 引发一个图形异常 }
...
12.1.2.4 字符串链表异常
字符串链表异常EStringListError、EListError在用户对字符串链表进行非法操作时引发。由于许多部件(如TListBox,TMemo,TTabSet,…)都有一个TStrings类的重要属性,因而字符串链表异常在部件操作编程中非常有用。
EStringListError异常一般在字符串链表越界时产生。例如对如下初始化的列表框:
ListBox1.Items.Add(\'First item\');
ListBox1.Items.Add(\'Second item\');
ListBox1.Items.Add(\'Third item\');
则以下操作都会引起EStringListError异常:
ListBox1.Item[3] := \' Not Exist\';
str := ListBox1.Item [3];
EListError异常一般在如下两种情况下引发:
1.当字符串链表的Duplicates属性设置为dupError时,应用程序试图加入一个重复的字符串;
2.试图往一个排序的字符串链表中插入一个字符串。
12.1.3 部件异常类
12.1.3.1 通用部件异常类
通用部件异常类常用的有三个:EInvalidOperation、EComponentError、EOutOfResource。其中EInvalidOperation、EOutOfResource在Controls单元中定义;EComponentError在Classes单元中定义。
1.非法操作异常 EInvalidOperation
EInvalidOperation 引发的原因可能有:
● 应用程序试图对一个Parent属性为nil的部件进行一些需要Windows句柄的操作
● 试图对一个窗口进行拖放操作
● 操作违反了部件属性间内置的相互关系等
例如,ScrollBar、Gauge等部件要求Max属性大于等于Min属性,因而下面的语句:
ScrollBar1.Max := ScrollBar1.Min-1;
将引发一个EInvalidOperation异常。
2.部件异常EComponentError
引发该异常的原因可能有:
● 在Register过程之外试图登录一个部件(常用于自定义部件开发中)
● 应用程序在运行中改变了一个部件的名称并使该部件与另一个部件重名
● 一个部件的名称改变为一个Object Pascal非法的标识符
● 动态生成一个部件与已存在的另一部件重名
3.资源耗尽异常EOutOfResource
当应用程序试图创建一个Windows句柄而Windows 却没有多余的句柄分配时引发该异常。
12.1.3.2 专用部件异常类
许多部件都定义了相应的部件异常类。但并不是有关部件的任何错误都会引发相应的异常类。许多情况下它们将引发一个运行时间异常或对象异常。
下面列出几个典型的部件异常类。
1.EMenuError
非法的菜单操作,例如试图删除一个不存在的菜单项。这一异常类在Menus库单元中定义。
2.EInvalidGridOpertion
非法的网格操作,比如试图引用一个不存在的网格单元。这一异常类在Grids库单元中定义。
3.EDDEError
DDE异常。比如应用程序找不到特定的服务器或会话,或者一个联接意外中止。这一异常类在DDEMan库单元中定义。
4.EDatabaseError,EReportError
数据库异常(EDatabaseError)和报表异常(EReportError) 在进行数据库和报表操作出现错误时引发。有关数据库的问题请读者参阅本书第二编。
12.1.4 小结
在这一节中重点介绍了Delphi提供的异常类体系。我们力求给读者一个清晰、全面的印象,使读者能在自己的程序开发中实际使用它们。为便于理解我们也提供了一些简单的说明性示例。虽然在具体的使用中读者还可能会碰到许多问题,但意识到应该用异常类来增强程序的健壮性却是程序设计水平走上新台阶的标志。
12.2 异常保护
确保回收分配的资源是程序健壮性的一个关键。但缺省情况下异常发生时程序会在出错点自动退出当前模块,因此需要一种特殊的机制来确保即使在异常发生的情况下释放资源的语句仍能被执行。而Delphi的异常处理正提供了这种机制。
12.2.1 需要保护的资源
一般说来需要保护的资源包括:
● 文件
● 内存
● Windows资源
● 对象
比如下面一段程序就会造成1K内存资源的丢失。
var
APointer : Pointer ;
AInt , ADiv: Integer ;
begin
ADiv := 0;
GetMem ( APointer , 1024 );
AInt := 10 div ADiv ;
FreeMem ( Apointer , 1024 );
end;
由于程序从异常发生点退出从而FreeMem永远没有执行的机会。
12.2.2 产生一个资源保护块
Delphi提供了一个保留字finally,用于实现资源的保护:
{分配资源}
try
{资源使用情况}
finally
{释放资源}
end;
try…finally…end就形成了一个资源保护块。finally后面的语句是在任何情况下,不论程序是否发生异常,都会执行的。
对于(12.2.1)中的例子如下代码即可确保所分配内存资源的释放:
var
APointer : Pointer ;
AInt , ADiv : Integer;
begin
ADiv := 0;
GetMem ( APointer , 1024 );
try
AInt := 10 div ADiv ;
finally
FreeMem ( Apointer , 1024 );
end;
end;
下面的例子摘自(6.4)节,是在文件拷贝中实现文件资源的保护:
procedure CopyFile(const FileName, DestName: TFileName);
var
CopyBuffer: Pointer;
TimeStamp, BytesCopied: Longint;
Source, Dest: Integer;
Destination: TFileName;
const
ChunkSize: Longint = 8192;
begin
Destination := ExpandFileName(DestName);
if HasAttr(Destination, faDirectory) then
Destination := Destination + \'\\' + ExtractFileName(FileName);
TimeStamp := FileAge(FileName);
GetMem(CopyBuffer, ChunkSize);
try
Source := FileOpen(FileName, fmShareDenyWrite);
if Source < 0 then
raise EFOpenError.Create(FmtLoadStr(SFOpenError, [FileName]));
try
Dest := FileCreate(Destination);
if Dest < 0 then
raise EFCreateError.Create(FmtLoadStr(SFCreateError, [Destination]));
try
repeat
BytesCopied := FileRead(Source, CopyBuffer^, ChunkSize);
if BytesCopied > 0 then
FileWrite(Dest, CopyBuffer^, BytesCopied);
until BytesCopied < ChunkSize;
finally
FileClose(Dest);
end;
finally
FileClose(Source);
end;
finally
FreeMem(CopyBuffer, ChunkSize);
end;
end;
程序的具体解释见 (6.4)节。
在异常保护的情况下,当异常发生时,系统会自动弹出一个消息框用于显示异常的消息。退出当前模块后异常类自动清除。
12.3 异常响应
异常响应为开发者提供了一个按自己的需要进行异常处理的机制。try …except …end形成了一个异常响应保护块。与finally不同的是:正常情况下except 后面的语句并不被执行,而当异常发生时程序自动跳到except,进入异常响应处理模块。当异常被响应后异常类自动清除。
下面的例子表示了文件打开、删除过程中发生异常时的处理情况:
uses Dialogs;
var
F: Textfile;
begin
OpenDialog1.Title := \'Delete File\';
if OpenDialog1.Execute then
begin
AssignFile(F, OpenDialog1.FileName);
try
Reset(F);
if MessageDlg(\'Erase \' +OpenDialog1.FileName + \'?\',
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
System.CloseFile(F);
Erase(F);
end;
except
on EInOutError do
MessageDlg(\'File I/O error.\', mtError, [mbOk], 0);
on EAccessDenied do
MessageDlg(\'File access denied.\', mtError, [mbOk], 0);
end;
end;
end.
保留字on…do用于判断异常类型。必须注意的是:except后面的语句必须包含在某一个on…do模块中,而不能单独存在。这又是同finally不同的一个地方。
12.3.1 使用异常实例
上面所使用的异常响应方法可总结为如下的形式:
on ExceptionType do
{响应某一类的异常}
这种方法唯一使用的信息是异常的类型。一般情况下这已能满足我们的需要。但我们却无法获取异常实例中包含的信息,比如异常消息、错误代码等。假设我们需要对它们进行处理,那么就必须使用异常实例。
为了使用异常实例,需要为特定响应模块提供一个临时变量来保存它:
on EInstance : ExceptionType do …
在当前响应模块中我们可以象使用一个普通对象那样来引用它的数据成员。但在当前响应模块之外不被承认。
下面的代码用于获取异常消息并按自己的方式显示它:
{窗口中包括一个ScrollBar部件,一个Button部件}
procedure TErrorForm.Button1Click(Sender: TObject);
begin
try
ScrollBar1.Max := ScrollBar1.Min-1;
except
on E: EInvalidOperation do
MessageDlg(\'Ignoring Exception:\'+E.Message,
mtInformation,[mbOK],0);
end;
end;
12.3.2 提供缺省响应
在异常响应模块中,一般我们只对希望响应的特定异常进行处理。如果一个异常发生而响应模块并没有包含对它的处理代码,则退出当前响应模块,异常类仍被保留。
为了保证任何异常发生后都能在当前响应模块中被清除,可以定义缺省响应:
try
{程序正常功能}
except
on ESomething do
{响应特定异常}
else
{提供缺省响应}
end;
由于else可以响应任何异常,包括我们一无所知的异常,因此在缺省响应中最好只包括诸如显示一个消息框之类的处理,而不要改变程序的运行状态或数据。
12.3.3 响应一族异常
诸如
on ExceptionType do
的异常响应语句不仅可响应本类异常,而且可以响应子类异常。对于象EIntError、EMathError等系统不会引发的异常,它们将只响应其子类异常。而对于象
on Exception do
这样的语句将会对任何异常进行响应。
下面一段代码对整数越界异常进行单独处理,而对其它整数异常进行统一处理:
try
{整数运算}
except
on ERangeError do
{越界处理}
on EIntError do
{其它整数异常处理}
end;
由于异常在处理后即被清除,因而上面的代码可保证不会使ERangeError异常被多次处理。假如颠倒两条响应语句的顺序,则ERangeError异常响应将永远没有被执行的机会。
由于异常在处理后即被清除,因而当希望对异常进行多次处理时就需要使用保留字raise来重引发一个当前异常。
下面的代码同时使用了异常响应和异常保护。异常响应用于设置变量的值,异常保护用于释放资源。当异常响应结束时利用raise重引发一个当前异常。
var
APointer: Pointer ;
AInt , ADiv: Integer;
begin
ADiv := 0;
GetMem ( APointer , 1024 );
try
try
AInt := 10 div ADiv ;
except
on EDivByZero do
begin
AInt := 0 ;
raise;
end;
end;
finally
FreeMem ( APointer , 1024 );
end;
end;
上面一段代码体现了异常处理的嵌套。异常保护、异常响应可以单独嵌套也可以如上例所示的那样相互嵌套。
12.3.5 自定义异常类的应用
利用Delphi的异常类机制我们可以定义自己的异常类来处理程序执行中的异常情况。同标准异常不同的是:这种异常情况并不是相对于系统的正常运行,而是应用程序的预设定状态。比如输入一个非法的口令、输入数据值超出设定范围、计算结果偏离预计值等等。
使用自定义异常需要:
1.自己定义一个异常对象类;
2.自己引发一个异常。
12.3.5.1 定义异常对象类
异常是对象,所以定义一类新的异常同定义一个新的对象类型并无太大区别。由于缺省异常处理只处理从Exception或Exception子类继承的对象,因而自定义异常类应该作为Exception或其它标准异常类的子类。这样,假如在一个模块中引发了一个新定义的异常,而这个模块并没有包含对应的异常响应,则缺省异常处理机制将响应该异常,显示一个包含异常类名称和错误信息的消息框。
下面是一个异常类的定义:
type
EMyException = Class(Exception) ;
12.3.5.2 自引发异常
引发一个异常,调用保留字raise,后边跟一个异常类的实例。
假如定义:
type
EPasswordInvalid = Class(Exception);
则在程序中如下的语句将引发一个EPasswordInvalid异常:
If Password <> CorrectPassword then
raise EPasswordInvalid.Create(\'Incorrect Password entered\');
异常产生时把System库单元中定义的变量ErrorAddr的值置为应用程序产生异常处的地址。在你的异常处理过程中可以引用ErrorAddr的值。
在自己引发一个异常时,同样可以为ErrorAddr分配一个值。
为异常分配一个错误地址需要使用保留字at,使用格式如下:
raise EInstance at Address_Expession;
12.3.5.3 自定义异常的应用举例
下面我们给出一个利用自定义异常编程的完整实例。
两个标签框(Label1、Label2)标示对应编辑框的功能。编辑框PassWord和InputEdit用于输入口令和数字。程序启动时Label2、InputEdit不可见。当在PassWord中输入正确的口令时,Label2、InputBox出现在屏幕上。此时Label1、PassWord隐藏。
设计时,令Label2、InputEdit的Visible属性为False。通过设置PassWord的PassWordChar可以确定输入口令时回显在屏幕上的字符。
自定义异常EInvalidPassWord和EInvalidInput分别用于表示输入的口令非法和数字非法。它们都是自定义异常EInValidation的子类。而EInValidation直接从Exception异常类派生。
下面是三个异常类的定义。
type
EInValidation = class(Exception)
public
ErrorCode: Integer;
constructor Create(Const Msg: String;ErrorNum: Integer);
end;
EInvalidPassWord = class(EInValidation)
public
constructor Create;
end;
EInvalidInput = class(EInValidation)
public
constructor Create(ErrorNum: Integer);
end;
EInValidation增加了一个公有成员ErrorCode来保存错误代码。错误代码的增加提供了很大的编程灵活性。对于异常类,可以根据错误代码提供不同的错误信息;对于使用者可以通过截取错误代码,在try...except模块之外来处理异常。
从以上定义可以发现:EInvalidPassWord和EInvalidInput的构造函数参数表中没有表示错误信息的参数。事实上,它们保存在构造函数内部。下面是三个自定义异常类构造函数的实现代码。
constructor EInValidation.Create(Const Msg: String; ErrorNum: Integer);
begin
inherited Create(Msg);
ErrorCode := ErrorNum;
end;
constructor EInValidPassWord.Create;
begin
inherited Create(\'Invalid Password Entered\',0);
end;
constructor EInValidInput.Create(ErrorNum: Integer);
var
Msg: String;
begin
case ErrorNum of
1:
Msg := \'Can not convert String to Number\';
2:
Msg := \'Number is out of Range\';
else
Msg := \'Input is Invalid\';
end;
inherited Create(Msg,ErrorNum);
end;
对于EInvalidInput,ErrorCode=1表示输入的不是纯数字序列,而ErrorCode=2表示输入数值越界。
口令检查是用户在PassWord中输入口令并按下回车键后开始的。实现代码在PassWord的OnKeyPress事件处理过程中:
procedure TForm1.PassWordKeyPress(Sender: TObject; var Key: Char);
const
CurrentPassWord = \'Delphi\';
begin
if Key = #13 then
begin
try
if PassWord.text <> CurrentPassWord then
raise EInvalidPassWord.Create;
Label2.Visible := True;
InputEdit.Visible := True;
InputEdit.SetFocus;
PassWord.Visible := False;
Label1.Visible := False;
except
on EInvalidPassWord do
begin
PassWord.text := \'\';
raise;
end;
end;
Key:=#0;
end;
end;
同样,在InputEdit的OnKryPress事件处理过程中实现了输入数字的合法性检查:
procedure TForm1.InputEditKeyPress(Sender: TObject; var Key: Char);
var
Res: Real;
Code: Integer;
begin
if Key = #13 then
begin
try
val(InputEdit.text,Res,Code);
if Code <> 0 then
raise EInValidInput.create(1);
if (Res > 1) or (Res < 0) then
raise EInValidInput.create(2);
MessageDlg(\'Correct Input\', mtInformation,[mbOk], 0);
Key := #0;
except
on E:EInValidInput do
begin
InputEdit.text := \'\';
MessageDlg(E.Message, mtWarning,[mbOk], 0);
end;
end;
end;
end;
由于异常响应后即被清除,所以要显示异常信息,需要另外的手段。在以上两段程序中我们采用了两种不同的方法:在口令合法性检查中,利用异常重引发由系统进行缺省响应;在输入数字合法性检查中,通过异常实例来获取异常信息并由自己来显示它。
以上所举的是一个非常简单的例子,但从中已可以发现:使用自定义异常编程,为程序设计带来了很大的灵活性。
12.3.6 利用异常响应编程
利用异常处理机制不仅能使程序更加健壮,而且也提供了一种使程序更加简捷、明了的途径。事实上,使用自定义异常类就是一种利用异常响应编程的方式。这里我们再讨论几个利用标准异常类编程的例子。
比如为了防止零作除数,可以在进行除法运算前使用if…then…else语句。但如果有一系列这样的语句则繁琐程度是令人难以忍受的。这时候我们可能倾向于使用EDivByZero异常。例如如下一段程序就远比用if…then…else实现简捷明了。
function Calcu(x,y,z,a,b,c:Integer):Real;
begin
try
Result := x/a+y/b+z/c ;
except
on EDivByZero do
Result := 0;
end;
end;
在(6.2.3)记录文件的打开与创建中就是利用异常响应来实现文件的打开或创建。
procedure TRecFileForm.OpenButtonClick(Sender: TObject);
begin
if OpenDialog1.Execute then
FileName := OpenDialog1.FileName
else
exit;
AssignFile(MethodFile,Filename);
try
Reset(MethodFile);
FileOpened := True;
except
on EInOutError do
begin
try
if FileExists(FileName) = False then
begin
ReWrite(MethodFile);
FileOpened := True;
end
else
begin
FileOpened := False;
MessageDlg(\'文件不能打开\',mtWarning,[mbOK],0);
end;
except
on EInOutError do
begin
FileOpened := False;
MessageDlg(\'文件不能创建\',mtWarning,[mbOK],0);
end;
end;
end;
end;
if FileOpened = False then exit;
Count := FileSize(MethodFile);
if Count > 0 then
ChangeGrid;
RecFileForm.Caption := FormCaption+\' -- \'+FileName;
NewButton.Enabled := False;
OpenButton.Enabled := False;
CloseButton.Enabled := True;
end;
总之,利用异常响应编程的中心思想是虽然存在预防异常发生的确定方法,但却对异常的产生并不进行事前预防,而是进行事后处理,并以此来简化程序的逻辑结构。
12.4 程序调试简介
Delphi提供了一个功能强大的内置调试器(Integrated Debugger), 因而对程序的调试不用离开集成开发环境(IDE)就可以进行。
程序错误基本可以分为两类,即运行时间错和逻辑错。所谓运行时间错是指程序能正常编译但在运行时出错。逻辑错是指程序设计和实现上的错误。程序语句是合法的,并顺利执行了,但执行结果却不是所希望的。
对于这两类错误,调试器都可以帮助你快速定位错误,并通过对程序运行的跟踪和对变量值的监视帮助你寻找错误的真正原因和解决错误的途径。
程序调试的主要内容可以概括为如下的几方面:
1.调试的准备和开始;
2.控制程序的执行;
3.断点的使用;
4.检查数据的值。
程序调试只有用户实际上机操作才能真正掌握。在这一节中我们主要对调试中的主要问题和一些关键点进行介绍。至于一些很细小的问题相信读者可以在上机实际应用中掌握,因而没有列出。
12.4.1 调试的准备和开始
在程序开发过程中程序编码和调试是一个持续的循环过程,只有在你对程序进行了彻底的测试后才能交付最终用户使用。为了保证调试的彻底性,在调试前应制定一个详细的调试计划。一般说来应该把程序划分为几个相对独立的部分,分别进行调试,以利于错误的迅速定位,确保每一部分程序都按设计的要求运行。
调试计划准备好后就可以开始程序的调试。
开始一个调试过程包括:
1.编译时产生调试信息;
2.从Delphi里运行你的程序。
在程序调试过程中,程序的执行完全在你的控制之中。你可以在任何位置暂停程序的执行去检查变量和数据结构的值,去显示函数调用序列,去修改程序中变量的值以便观察不同值对程序行为的影响。
12.4.1.1 产生调试信息
要使用内部调试器必须选中Option| Environment菜单References页的Integrated Debugging
全部评论
请发表评论