版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第7章 Delphi异常处理与调试,7.1 Delphi异常处理 7.2 Delphi异常类 7.3 Delphi异常处理机制 7.4 Delphi调试器 7.5 控制程序的运行 7.6 断点 7.7 监视表达式的值 7.8 实验,7.1 Delphi异常处理,7.1.1 异常处理的意义 异常可以理解为一种特殊的事件。当这种特殊的事件发生时,程序正常的执行流程将被打断。异常处理机制能够确保在发生异常的情况下应用程序不会中止运行,也不会丢失数据或资源。 没有人能保证程序代码绝对不出错。有时候,即使程序本身没有错,但与程序打交道的软、硬件设备出错,也会使程序出现异常。引起异常的设备称为“异常源”,
2、包括操作系统、设备驱动程序、数据库驱动程序(诸如BDE、SQL Links、ODBC、ADO)、动态链接库、常驻内存的防病毒软件、Delphi自身的组件库和运行期库等。,异常几乎是不可避免的。过去,在没有异常处理机制的情况下,程序员不得不小心翼翼地检查每一次函数调用的返回值,或者通过额外的代码捕获可能的错误。 关键是,通过检查函数的返回值或设置错误陷阱只能捕获可预见的错误。如果发生没有预见到的错误,或者函数调用本身就已失败,则程序正常的流程将被打乱。 还有一个问题就是,程序中过多的错误检查代码也破坏了程序的可读性。事实上,这些代码大都是闲置的,因为程序中到处发生异常的可能性毕竟不是很大。 使用
3、Object Pascal语言的异常处理机制,就能解决上述弊端。它既能使程序的安全性得到保证,又不至于使程序代码过分琐碎。下面先看一段没有采用异常处理机制的代码:,var AChar,AString:ShortString; begin AString:=Welcome to Delphi; AChar:=Copy(AString,20,1); if AChar#0 then / #0是空字符,不是空格 begin if AChar! then Insert(AChar,AString,20); Exit; end; end; 在上述代码中,虽然检查了Copy()的返回值,但如果Copy()本
4、身调用失败,则程序就执行不到Exit语句。下面改用异常处理机制来编写这段代码。,var AChar,Astring:ShortString; begin try AString:=Welcome to Delphi; AChar:=Copy(Astring,20,1); if AChar! then Insert(AChar,AString,20); except Exit; end; End; 采用了异常处理机制后,可以先假设程序不可能出错,按正常的顺序编写代码。当代码在运行过程中出现异常时,将跳转到except部分,执行Exit语句。 异常处理机制有两种结构:一种是tryexcept结构,
5、另一种是tryfinally结构,这两种结构在用法上有很大的区别。,7.1.2 错误类型,一般来说,无论在编程的时候如何仔细,程序总会有错误。错误分为4种类型:设计期错误、编译期错误、运行期错误、逻辑错误。 1. 设计期错误 这种错误类型发生在设计期,通常是因为给组件的某个属性输入了非法的值。例如,在设计数据库应用程序时指定了一个没有定义的数据库别名。 这种类型的错误比较容易被发现和纠正,因为Delphi能够对属性的值进行合法性检查。一旦发现这种错误,Delphi将弹出一个警告窗口,提示用户纠正错误。,2. 编译期错误 编译期错误也叫语法错误,当程序代码违反了Object Pascal的语法规
6、则时将发生这种错误。 如果程序代码中有语法错误,编译就不能通过,代码编辑器的状态栏将给出错误信息提示,并在代码编辑器中突出显示有语法错误的行。 比较常见的语法错误是数据类型不匹配,特别是调用RTL、VCL或Windows的API时容易发生参数不匹配的错误。为了避免这类错误,建议使用Delphi的在线帮助,找到RTL、VCL或API的声明,仔细对照它们的参数。不小心输错变量名或命令语句拼写错误也是经常造成编译期错误的原因。 编译器检查语法错误的功能是可以自定义的。可以使用【Project】菜单中的【Options】命令打开【Project Options】对话框,然后设置编译器的语法检查选项。,
7、3. 运行期错误 程序虽然通过了编译,但在运行时失败了,这种错误称为运行期错误。例如,程序试图打开一个不存在的文件,或者在运算时出现了被零除。 运行期错误分两种情况,一种是运行时随运行异常环境或用户输入的错误参数而造成运行错误,这种情况的“错误”往往无法在交付前通过调试排除,而通常由“异常处理”来解决;另一种是不随环境和参数变化都会产生的运行错误,这种情况下,如果不能确认错误发生在什么地方,可以使用Delphi的内部集成调试器帮助找到错误所在。例如,可以通过单步执行命令让程序一条语句一条语句地执行,或者通过一个观察窗口来监视某个变量的变化情况。,4. 逻辑错误 逻辑错误是指程序通过了编译,也能
8、执行,但执行的结果与预期的不同。 逻辑错误有时比较难找,因为编译器无法识别这种错误。此时,需要用内部集成调试器,通过控制程序的运行以及监视程序的输出,来把错误逐步定位在一个较小的范围内。 5. 怎样尽可能地减少错误 虽然错误是很难避免的,但好的编程习惯能够尽可能地减少错误。以下是一些有关编程习惯的建议。 1) 程序应尽可能地模块化 程序分解为模块后,由于每个模块所要完成的任务相对简单了,所以发生错误的可能也就减少了。模块化还简化了程序的维护。,2) 养成良好的代码书写习惯 注释能够增加代码的可读性,方便维护和修改。缩进能够使程序的语法结构更加清晰。 3) 不要忘记检查参数的值 在函数内部,首先
9、要检查传递过来的参数值是否合法,是否在一个可接受的范围内。 4) 不要忘记检查函数的返回值 函数的返回值往往表示函数调用是否成功,以此决定下面的程序流程。如果贸然执行下一步,有可能会出现意想不到的结果。,7.2 Delphi异常类,异常类是Delphi异常处理机制的核心,也是Delphi异常处理的主要特色。Delphi提供的所有异常类都是Exception类的子类。用户也可以从Exception类派生一个自定义的异常类。Exception的一系列构造函数中最重要的参数是显示的错误信息。而数据成员中最重要的也是可被引用的消息字符串(message,messagePtr)。从大的方面可以把异常类分
10、为运行库异常、对象异常、组件异常3类。 1. 整数异常 整数异常都是从一个EIntError类派生的,但程序运行中引发的总是它的子类:EDivByZero、ERangeError和EIntOverFlow,见表7-1。,表7-1 整数异常及其产生原因,当一个整数表达式的值超过为一个特定整数类型分配的范围时引发ERangeError异常。比如下面一段代码将引发一个ERangeError异常。 var SmallNumber:ShortInt; X,Y:Integer; begin X:=100; Y:=75; SmallNumber:=X*Y; end;,特定整数类型包括ShortInt、Byt
11、e以及与整数兼容的枚举类型、布尔类型等,例如 : type THazard=(Safety,Marginal,Critical,Catastrophic); var Haz:THazard; Item:Integer; begin Item:=5; Haz:=THazard(Item); end;,由于枚举类型越界而引发一个ERangeError异常。数组下标越界也会引发一个ERangeError异常,如: var Values:array1.10 of Integer; I:Integer; begin for I:=1 to 11 do ValuesI:=I; end; ERangeErr
12、or异常只有当范围检查打开时才会引发。这可以在代码中包含$R+编译指示或设置IDE Option|Project的Range_Checking Option选择框。注意,Delphi不对长字符串做范围检查。,EIntOverFlow异常类在Integer、Word、Longint 3种整数类型越界时引发。如下面的代码将引发一个EIntOverFlow异常。 var I:Integer; a,b,c:Word; begin a:=10; b:=20; c:=1; for I:=0 to 100 do c:=a*b*c; end; EIntOverFlow异常类只有在编译选择框Option|Pro
13、ject|Over_Flow_Check Option选中时才产生。当关闭溢出检查,则溢出后变量的值是丢弃溢出部分后的剩余值。,2. 浮点异常 浮点异常是在进行实数操作时产生的,它们都从一个EMathError类派生,但与整数异常相同,程序运行中引发的总是它的子类EInvalidOp、EZeroDivide、EOverFlow、EUnderFlow,见表7-2。 EInvalidOp最常见的引发条件是没有协处理器的机器遇到一个协处理器指令。由于在默认情况下Delphi总是把浮点运算编译为协处理器指令,因而在386以下微机上常常会碰到这个错误。此时只需要在单元的接口部分设置全局编译指示$N-,选
14、择利用运行库进行浮点运算,问题就可以解决了。,表7-2 浮点异常类及其引发条件,各种类型的浮点数(Real、Single、Double、Extended)越界引起同样的溢出异常。 4. 类型匹配异常 当试图用As操作符把一个对象与另一类对象匹配失败后引发类型匹配异常EInvalidCast。,5. 类型转换异常 当试图用转换函数把数据从一种形式转换为另一种形式时引发类型转换异常EConvertError,特别是当把一个字符串转换为数值时引发。下面程序中的两条执行语句都将引发一个EConvertError异常。 var r1:Real; int:Integer; begin r1:=StrToF
15、loat($140.48); int:=StrToInt(1,402); end; 要注意,并不是所有的类型转换函数都会引发EConvertError异常。比如当函数Val无法完成字符串到数值的转换时只有返回错误代码。利用这一点我们实现了输入的类型和范围检查。,7.3 Delphi异常处理机制,Delphi异常处理机制建立在保护块(Protected Blocks)的概念上。所谓保护块是用保留字try和end封装的一段代码。保护块的作用是当应用程序发生错误时自动创建一个相应的异常类(Exception)。程序可以捕获并处理这个异常类,以确保程序的正常结束以及资源的释放和数据不受破坏。如果程序不
16、进行处理,则系统会自动提供一个消息框。,7.3.1 异常响应与tryexcept语句,异常响应为开发者提供了一个按自己的需要进行异常处理的机制。tryexceptend形成了一个异常响应保护块。正常情况下except后面的语句并不被执行,而当异常发生时,程序自动跳到except,进入异常响应处理模块。当异常被响应后异常类自动清除。 tryexcept语句的一般格式如下: try /try保护代码块 被保护语句 except /异常处理块 异常处理语句 /异常不发生,不处理 end; 或,try /try保护代码块 被保护语句 except /异常处理块 on do /捕获指定类型的异常对象,进
17、行处理 on do /捕获指定类型的异常对象,进行处理 else /默认的异常处理代码 end; try语句块指出了需要进行异常保护的代码。如果在这部分有不正常的事件发生,则引发一个异常对象。except是异常处理部分,被保护部分引发的异常对象将执行或由这部分代码捕获并进行处理。,【例7.1】 用tryexcept语句处理被0除的情况。 procedure TForm1.Button1Click(Sender: TObject); var a,b,c:real; begin b:=strtofloat(edit1.Text); c:=strtofloat(edit2.Text); try a:
18、=b /c; edit3.Text:=floattostr(a); except edit3.Text:=不能用0除; end; end;,在正常情况下(即由try保护的部分没有异常事件发生时),应用程序执行完try部分代码后,跳过except部分代码,继续运行应用程序。在执行时内代码出现异常情况时,引发一个异常对象后,应用程序将跳过剩余的被保护部分未执行的代码,直接进入except部分进行异常处理,在except异常处理块中,由try保护块引发的异常对象也可以由以下语句来捕获: on do 【例7.2】 两数相除过程中发生异常时的处理情况如下。,procedure TForm1.Button
19、1Click(Sender: TObject); var a,b,c:real; begin try b:=strtofloat(edit1.Text); c:=strtofloat(edit2.Text); a:=b /c; edit3.Text:=floattostr(a); except on Ezerodivide do edit3.Text:=不能用0除; on EMathError do edit3.Text:=计算错误; else edit3.Text:=发生异常; end; end;,保留字ondo用于判断异常类型。这样,可根据不同的异常类型,执行不同的异常处理命令。如上例中,
20、在edit2中输入0,则edit3中将显示不能用0除;若在edit1和edit2同时输入0,则edit3中将显示计算错误;若在edit1或edit2组件中输入非数字字符,则edit3中将显示发生异常。 注意:运行时必须先编译生成exe文件,然后在Windows中运行,否则在发生异常时仍将出现系统的错误信息。,7.3.2 异常保护与tryfinally语句,1. 需要保护的资源 一般说来需要保护的资源包括:文件、内存、Windows资源和对象。 比如下面一段程序就会造成1KB的内存资源的丢失。 var pPointer:Pointer; iInt,iDiv:Integer; begin iDiv
21、:=0; GetMem(pPointer,1024); /分配1KB的内存 iInt:=10 div iDiv; /这里将触发被零除的异常 FreeMem(pPointer,1024); /永远执行不到这里 end; 由于程序从异常发生点退出,因而FreeMem永远没有被执行的机会。,2. 创建一个资源保护块 在应用程序运行中产生了不正常的事件,要保证系统资源的释放和关闭。 tryfinally语句的一般格式如下: try /try保护代码块 被保护语句 finally /异常处理块 异常处理语句 /无论异常发生否,都必须处理 end 若把tryfinally语句用做创建一个资源保护块时,它的
22、格式可写成: (分配系统资源) try (使用系统资源的语句) finally (释放系统资源) end;,try前面的语句中包含了申请或创建系统资源的语句。try部分包含了使用系统资源的代码语句。finally部分包含资源释放语句。在应用程序运行过程中,如果在try部分产生异常情况,将直接跳过随后的代码,直接执行finally部分的资源释放语句。如果不产生异常,则应用程序正常运行,最后执行finally部分中的资源释放语句。 注意:在tryfinally语句中,当try部分产生异常后,应用程序直接执行finally部分的资源释放语句。 【例7.3】 用tryfinally语句确保所分配内存资
23、源的在异常情况下仍被释放。,var pPointer: Pointer; iInt,iDiv: Integer; begin iDiv:=0; GetMem(pPointer,1024); /分配1KB的内存 try iInt:=10 div iDiv; /这将触发异常 finally FreeMem(pPointer,1024);/释放先前分配的内存 end; end;,tryfinally结构与tryexcept结构在用法上主要有以下区别。 (1) 对于tryfinally结构来说,不管try部分的代码是否触发异常,finally部分总是执行的。如果发生异常,就提前跳到finally部分。
24、而对于tryexcept结构来说,只有当触发了异常后,才会执行except部分的代码。 (2) 在tryexcept结构中,当异常被处理后异常对象就被释放,除非重新触发异常。而在tryfinally结构中,即使finally部分对异常作了处理,异常对象仍然存在。 (3) finally部分不能处理特定的异常,因为它没有tryexcept结构中的异常句柄,无法知道确切的异常类型。因此,在finally部分只能对异常做笼统的处理。 (4) 在tryfinally结构中,如果在try部分调用标准命令Exit、Break或Continue,将导致程序的执行流程提前跳到finally部分。finally
25、部分不允许调用上述3个命令。,7.3.3 异常的重引发和处理嵌套,由于异常在处理后即被清除,因而当希望对异常进行多次处理时就需要使用保留字raise 来重新引发一个当前异常。 【例7.4】 同时使用异常响应和异常保护来处理被0除和释放资源,当异常响应结束时利用raise重新引发一个当前异常。 var pPointer:Pointer; iInt,iDiv:Integer;,begin iDiv:=0; GetMem(pPointer,1024); try try iInt:=10 div iDiv; except On EDivByZero do begin iInt:=0; raise; e
26、nd; end; finally FreeMem(pPointer,1024); End; End;,7.3.4 定义自己的异常,使用自定义异常需要自己定义一个异常对象类和引发一个异常。 1. 定义异常对象类 异常是对象,所以定义一类新的异常同定义一个新的对象类型并无太大区别。由于默认异常处理只处理从Exception或Exception子类继承的对象,因而自定义异常类应该作为Exception或其他标准异常类的子类。这样,假如在一个模块中引发了一个新定义的异常,而这个模块并没有包含对应的异常响应,则默认异常处理机制将响应该异常,显示一个包含异常类名称和错误信息的消息框。 下面是一个异常类的定
27、义。 type EMyException=Class(Exception);,2. 自引发异常 引发一个异常需要调用保留字raise,后边跟一个异常类的实例。假如定义: type EPasswordInvalid=Class(Exception); 则在程序中如下的语句将引发一个EPasswordInvalid异常。 If PasswordCorrectPassword then raise EPasswordInvalid.Create(Incorrect Password entered); 异常产生时把System库单元中定义的变量ErrorAddr的值置为应用程序产生异常处的地址。在异
28、常处理过程中可以引用ErrorAddr的值。 在自己引发一个异常时,同样可以为ErrorAddr分配一个值。 为异常分配一个错误地址需要使用保留字at,其使用格式如下: raise EInstance at Address_Expession;,【例7.5】 编写一个利用自定义异常编程的完整实例。程序设计界面如图7.1所示。 1) 新建应用程序 启动Delphi,从【File】菜单中选取【New Application】命令,开始一个新的应用文件,应用程序中将有一个默认窗体Form1。 2) 设置窗体属性 将窗体Form1的Caption属性设置为“自定义异常”;Font属性设置为宋体、五号;
29、Height和Width属性分别设置为200、290。,3) 添加组件 向窗体Form1中添加两个Label组件Label1和Label2,两个Edit组件PassWord和InputEdit,两个Button组件Button1和Button2。 4) 设置组件属性 详见表7-3。,图7.1 自定义异常实例,表7-3 实例7.5组件属性,窗体的布置如图7.1所示。 程序启动时Label2、InputEdit不可见。当在PassWord中输入正确的口令时,Label2、InputBox出现在屏幕上。此时Labell、PassWord隐藏。通过设置PassWord的PassWordChar为“*”
30、,可以使输入口令时回显在屏幕上的字符为“*”。,5) 自定义异常 自定义异常EInvalidPassWord和EInvalidInput分别用于表示输入的口令非法和数字非法。它们都是自定义异常EInValidation的子类。而EInValidation直接从Exception异常类派生。 下面是3个异常类的定义: type TForm1 = class(TForm) private Private declarations public Public declarations end;,EInValidation=class(Exception) public ErrorCode:Integ
31、er; 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来保存错误代码。错误代码的增加提供了很大的编程灵活性。对于异常类,可以根据错误代码提供不同的错误信息
32、。对于使用者可以通过截取错误代码,在tryexcept模块之外来处理异常。 从以上定义可以发现:EInvalidPassWord和EInvalidInput的构造函数参数表中没有表示错误信息的参数。事实上,它们保存在构造函数内部。下面是3个自定义异常类构造函数的实现代码。,constructor EInValidation.Create(Const Msg:String;ErrorNum:Integer); begin inherited Create(Msg); ErrorCode:=ErrorNum; end; constructor EInValidPassWord.Create; be
33、gin inherited Create(输入的密码不正确,0); end;,constructor EInValidInput.Create(ErrorNum:Integer); var Msg:String; begin case ErrorNum of 1: Msg:=无法把字符转换成数字; 2: Msg:=数值超出范围; else Msg:=输入有错; end; inherited Create(Msg,ErrorNum); end; 对于EInvalidInput,ErrorCode=1表示输入的不是纯数字序列,而ErrorCode=2表示输入数值越界。,6) 添加事件 为窗体For
34、m1中的PassWord和InputEdit编辑框添加OnKeyPress事件,事件响应过程分别为PassWordKeyPress和InputEditKeyPress。 7) 编写事件代码 口令检查是用户在PassWord中输入口令并按下回车键后开始的。即在PassWordKeyPress中,要对口令进行核对操作,若不正确,将触发异常EInvalidPassWord。,procedure TForm1.PassWordKeyPress(Sender: TObject; var Key: Char); const CurrentPassWord=Delphi; begin if Key=#13
35、then begin try if PassWord.textCurrentPassWord 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
36、的OnKeyPress事件处理过程中实现了输入数字的合法性检查:,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 Code0 then raise EInValidInput.create(1); if (Res1) or (Res0) then raise EInValidInput.create(2); MessageD
37、lg(输入正确,mtInformation,mbOk,0); Key:=#0;,except on E:EInValidInput do begin InputEdit.text:=; MessageDlg(E.Message,mtWarning,mbOk,0); end; end; end; end; 8) 保存程序 将窗体Form1的单元文件保存为“Unit07_5.pas”,将项目文件保存为“Project07_5.dpr”。,9) 运行程序 必须先编译生成Project07_5.exe文件,然后在Windows资源管理器中运行Project07_5.exe。运行时,不同的输入会触发不同
38、的异常。 由于异常被响应后即被清除,所以要显示异常信息,需要另外的手段。在以上两段程序中采用了两种不同的方法:在口令合法性检查中,利用异常重引发由系统进行默认响应;在输入数字合法性检查中,通过异常实例来获取异常信息并由自己来显示它。,7.3.5 利用异常响应编程,利用异常处理机制不仅能使程序更加健壮,而且也提供了一种使程序更加简捷、明了的途径。事实上,使用自定义异常类就是一种利用异常响应编程的方式。 比如为了防止零作除数,可以在进行除法运算前使用ifthenelse语句。但如果有一系列这样的语句,则繁琐程度是令人难以忍受的。这时可能倾向于使用EDivByZero异常。例如,如下一段程序就远比用
39、ifthenelse语句实现简捷明了。,function Calcu(x,y,z,a,b,c:Integer):Real; begin try Result:=x/a+y/b+z/c; except On EdivByZerod do Result:=0; end; end; 【例7.6】 在文件的打开与创建中可以利用异常响应来实现文件的打开或创建。,Procedure TRecFileForm.OpenButtonClick(Sender:TObject); begin if OpenDialog1.Execute then FileName:=OpenDialog1.FileName el
40、se exit; AssignFile(MethodFile,Filename); try Reset(MethodFile); FileOpened:=True; except OnEInOutError 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 be
41、gin FileOpen:=False; MessageDlg(文件不能创建,mtWarning,mbOK,0); end; end;,end; end; if FileOpened=False then exit; Count:=FileSize(MethodFile); if Count0 then ChangeGrid; RecFileForm.Caption:=FormCaption+-+FileName; NewButton.Enabled:=False; OpenButtomEnabled:=False; CloseButton.Enabled:=True; end; 总之,利用异
42、常响应编程的中心思想是虽然存在预防异常发生的确定方法,但却对异常的产生并不进行事先预防,而是进行事后处理,并以此来简化程序的逻辑结构。,7.4 Delphi调试器,7.4.1 准备调试 在调试程序之前,必须保证程序代码已经没有语法错误,还要正确设置一些选项。为此,需要使用【Project】菜单中的【Options】命令打开【Project Options】对话框。打开 【Compiler】对话框,选中【Debug information】复选框(默认为选中)。这样,编译器将把所有的调试信息加到.dcu文件和.exe文件中。 VCL的代码都是仔细调试过的,一般不会有错误。如果仍然不放心,想跟踪进
43、入VCL的内部,则需要选中【Use Debug DCUs】复选框。 要说明的是,调试信息将使可执行文件增大,但不影响程序的性能和对内存的需求。尽管如此,调试结束后,最好要打开【Project Options】对话框,在【Compiler】选项卡中,清除【Debug information】复选框,再重新编译程序。这样,程序中就不包含任何调试信息。,要使用内部集成调试器来调试程序,还必须使用【Tools】菜单中的【Debugger Options】命令打开【Debugger Options】对话框,选中【Integrated debugging】复选框(默认为选中)。否则,【Run】菜单中的调试
44、命令将变灰。但要说明的是,内部集成的调试器可能会与某些软件冲突,从而引起应用程序运行异常。因此,在调试程序时最好把可能引起冲突的软件退出运行。 使用【Tools】菜单上的【Environment Options】命令打开【Environment Options】对话框,再打开【Preferences】选项卡。如果选中【Hide designers on run】复选框,当程序运行时,【Object Inspector】和【Form】设计器将关闭,以腾出屏幕上的空间。如果选中【Minimize on run】复选框,当程序运行时,IDE将最小化,以避免屏幕上内容太多太乱。不过,当程序暂停运行时,
45、IDE的窗口会重新恢复成原始大小。,7.4.2 设置调试器的选项,1. 设置调试器选项 要设置调试器的选项,可以使用【Tools】菜单中的【Debugger Options】命令打开【Debugger Options】对话框,如图7.2所示。在其中的【General】选项卡、【Event Log】选项卡、【Language Exceptions】选项卡和【OS Exceptions】选项卡中,可以设置调试的一些环境、配置及方法等。,图7.2 【Debugger Options】对话框,2. 编译指令 默认情况下,上述设置对整个项目的所有单元都有效。不过,也可以让这些设置只对部分单元有效,因为有
46、些单元可能没有问题,不需要调试。 要使某个单元不包含调试信息,就在这些单元中加入适当的编译指令,例如: unit Unit1; $DEBUGINFO OFF interface 3. 自定义调试器的颜色 在前面已提到,代码编辑器可以用不同的颜色显示不同的语法成分,在使用调试器调试程序时也有这个功能。例如,通常断点用白底红色表示,当前执行点用白底蓝色表示。 要自定义调试器的颜色,可以使用【Tools】菜单上的【Editor Options】命令打开【Editor Properties】对话框,再打开【Colors】选项卡,在【Element】框中选择某种语法元素,然后设置它的前景颜色和背景颜色。
47、,7.5 控制程序的运行,当调试信息编译了程序后,就可以调试程序了,调试器将接管对程序运行的控制,但程序的运行结果与在非调试状态下运行没有什么两样,包括建立窗口、接受用户输入、计算数值、响应事件、访问数据库等均照常进行。,7.5.1 单步执行,通过【Run】菜单中的【Step Over】命令,可以单步执行程序。所谓单步执行,就是一次只执行一行(一个指令),这样就可以知道哪一行或指令引起了运行期错误或逻辑错误。 【Step Over】命令将把整个过程或函数当做一行。如果把几条语句写在一行上,调试器将把这几条语句当做一条语句。这样,就无法单独调试其中的某一个语句。如果把一条长语句分成几行写,调试器
48、仍然把这几行当做一行。 调试器每执行一行,当前执行点就自动移到下一个要执行的行上,但不一定是源代码的下一行。例如,若正在执行的是goto语句,当前执行点将移到goto语句跳转到的行上。,另外还有一种情况就是,如果开启了优化编译的选项,某些源代码行将被合并或越过,这时候,当前执行点不会移到这些行上。 为了清晰地看出当前执行点在哪儿,代码编辑器将用白底蓝色显示当前执行点,同时,在“装订区”显示一个绿色的箭头指向当前执行点,如图7.3所示。 图7.3 当前执行点,7.5.2 跟踪执行,【Run】菜单中的【Trace Into】命令用于跟踪程序。与单步执行相似,这条命令一次也只执行一行。不同的是,执行
49、到有函数调用的行时,这条命令将进入函数的内部。 如果程序链接了外部代码诸如动态链接库,只要动态链接库包含了符号调试信息,就可以跟踪进入这些外部代码。否则,调试器将把动态链接库当做一行处理。 在调试过程中,可以根据需要交替使用单步执行和跟踪执行。例如,对有疑问的调用命令使用跟踪执行,使控制进入被调用部分内部,调试该被调用部分。而对有把握的调用命令使用单步执行,从而直接跳过被调用部分的调试,这样能提高调试效率。,【Trace Into】命令也能够进入事件句柄的内部,就像进入一般的函数内部一样。要注意的是,OnPaint事件是当应用程序的窗口需要重画的时候触发的,当进入处理该事件的句柄内部时,代码编
50、辑器的窗口将推到前端。也就是说,此时窗口需要重画了,这样又将触发OnPaint事件。而一旦进入处理OnPaint事件的句柄内部,代码编辑器的窗口又将被推到前端。如此反复,构成无限循环。要解决上述问题,必须把代码编辑器与应用程序的窗口在屏幕上重新排列,不要相互覆盖。 程序往往大量调用了VCL的方法,一般情况下,不要跟踪进入VCL的内部,因为VCL的源代码一般是不会出错的。如果怀疑VCL中可能出错,或者想进入VCL方法的内部看看方法是怎样实现的,也可以进入VCL方法的内部。Delphi Enterprise和Delphi Professional附带了VCL的源代码,而且还提供了带调试信息的VCL
51、库。,7.5.3 跳过一段代码,为了节省时间和提高工作效率,不必每次都从头开始单步或跟踪执行程序,可以直接跳到有疑问的地方,然后再一行一行地执行程序。 【Run】菜单中的【Run to Cursor】命令可以实现这个功能。它能够先以非调试方式执行到光标所在的行,再接管对程序的控制,单步或跟踪执行以后的代码。 如果光标所在的行不包含调试信息,调试器将弹出一个错误框显示“No code was generated for the current Line”。 如果不小心进入了例程的内部,想马上退出来,可以把光标移到该例程的最后一行,使用【Run to Cursor】命令,再使用【Step Over
52、】命令,就可以返回到调用该例程的地方。,7.5.4 全速执行剩余的代码,如果不小心进入了一个例程,但又不想调试这个例程,或者确信该例程的代码没有问题,从而想尽快退出这个例程,可以使用【Run】菜单中的【Run Until Return】命令。这个命令将全速执行该例程的代码,直到返回为止。,7.5.5 返回到执行点,在调试过程中,随时可以切换到IDE或其他程序中,进行各种操作。 如果要重新回到调试器的当前执行点,可以使用【Run】菜单中的【Show Execution Point】命令,光标将自动回到先前的执行点上。 如果包含执行点的源文件已关闭,调试器将重新打开这个源文件。如果执行点没有对应的
53、源代码,Delphi将打开CPU窗口,显示相应的机器指令。,7.5.6 暂停运行,使用【Run】菜单中的【Program Pause】命令将使程序运行暂时停止,这样就可以检查程序在此状态下的输出或变量的值是否正确,检查完以后,可以继续对程序进行调试,或者修改变量的值再让程序继续执行,以便看程序对新的值会做出什么反应。 有时候,程序暂停后无法回到调试器中继续运行,这时候可以同时按下Ctrl+Alt+SysRq键终止程序的运行,如果按一次无效,就多按几次。,7.5.7 重新开始运行,在调试过程中,可以使用【Run】菜单中的【Program Reset】命令中止程序的运行并释放所有占用的内存和资源,
54、关闭所有打开的文件,清除所有的变量设置,然后重新运行程序。这通常用于在调试过程中发现了错误并更改了源代码后需要重新编译和运行的情况。 【Program Reset】命令并不删除先前设置的断点和观察表达式,因为重新开始调试程序时可能还要用到这些设置。 【Program Reset】命令可能不能很“干净”地释放应用程序占用的所有资源,这样可能导致其他程序运行失败,碰到这种情形应当退出Delphi或者重新启动Windows。,7.5.8 命令行参数,如果要调试的程序需要传递参数,可以使用【Run】菜单中的【Parameters】命令打开【Run Parameters】对话框,如图7.4所示。 图7.
55、4 命令行参数 在【Parameters】框内键入要传递的参数,也可以从以前键入过的参数中选择一个。,7.6 断 点,所谓断点,就是在程序代码的某一行上设置一个标记,程序执行到这里将暂停,由调试器接管对程序的控制。使用断点与使用【Run to Cursor】命令有些相似,都是执行到某一行后暂停。不同的是,程序中可以设置多个断点并且能够给断点设置条件。 断点通常设置在有疑点的区域。在遇到断点之前,程序以全速运行。遇到断点之后,程序暂时停止运行,以后就可以单步或跟踪执行程序。,7.6.1 源代码断点,要在代码编辑器中设置源代码断点,有以下4种操作方式。 (1) 把光标移到要设为断点的行上,按下F5
56、键。 (2) 用鼠标左键单击要设为断点的行的最左端。图7.5 【Add Source Breakpoint】对话框 (3) 用鼠标右键单击要设为断点的行,在弹出的菜单中选择【Debug】命令,再选择【Toggle Breakpoint】。 (4) 使用【Run】菜单中的【Add Breakpoint】命令,再选择【Source Breakpoint】,Delphi将打开【Add Source Breakpoint】对话框,如图7.5所示。,图7.5 【Add Source Breakpoint】对话框,【Filename】框用于输入断点所在的源文件名(包含路径)。 【Line number】框
57、用于输入断点所在的行号。 【Condition】框用于设置断点有效的条件,通常是一个布尔表达式。布尔表达式中可以包含函数调用,只要该函数返回布尔值即可。 当程序执行到这个断点时,首先计算该布尔表达式的值。如果值为True,则断点有效,程序将暂停运行。如果值为False,则断点无效,程序将继续执行。,【Pass count】框用于指定经过断点多少次后断点有效。例如,在一个For循环中设置一个断点,每次循环时都会遇到这个断点。但并非每次遇到断点时程序都会暂停,因为还需要经过一定次数后断点才有效。 【Group】框用于对断点进行分组。可以在这个框内输入一个新的组名,也可以选择一个已有的组名。一旦若干
58、个断点编成组,就可以分别使用【Disable Group】命令和【Enable Group】命令成组地被禁止或允许它们,还可以给一组断点指定一系列动作。 注意:设为断点的行必须是可执行的代码行。如果把断点设在注释行、空行、变量声明的行上,调试器将认为断点无效。 默认情况下,断点所在的行用白底红字显示,并且在装订区有一个红色的小圆圈。如图7.6所示。,图7.6 断点,当鼠标指向这个小圆圈时,将弹出一个提示窗口,显示断点的条件和经过次数,7.6.2 机器指令断点,Delphi允许针对某个机器指令设断点。当程序执行到这个指令时,就会暂停(必须执行到断点处设置)。要设置机器指令断点,有下列几种方式。
59、(1) 在CPU窗口中用鼠标左键单击某个指令的装订区。 (2) 在CPU窗口中选择一个指令,然后按 F5键。 (3) 在CPU窗口中用鼠标右键单击某个指令,在弹出的菜单中选择【Toggle breakpoint】命令。 (4) 使用【Run】菜单中的【Add Breakpoint】命令,再选择【Address Breakpoint】,弹出【Add Address Breakpoint】对话框,如图7.7所示。,图7.7 【Add Address Breakpoint】对话框,【Address】框用于指定断点(在这里是机器指令)的地址。 【Condition】框用于设置断点有效的条件。 【Pass count】框用于指定经过断点多少次后断点有效。 【Group】框用于把断点进行分组。 当程序执行到这条指令时,就会暂停(如果满足有
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年修订版房屋销售代理协议要览版
- 二零二四年度企业咨询服务合同(标的战略规划与实施指导)2篇
- 江南大学《大学英语Ⅱ(3)》2022-2023学年第一学期期末试卷
- 佳木斯大学《微观经济学》2021-2022学年第一学期期末试卷
- 《互联网公司人才盘点方案》
- 2024婚前债务责任划分协议一
- 佳木斯大学《化工原理》2021-2022学年第一学期期末试卷
- 暨南大学《线性代数》2023-2024学年第一学期期末试卷
- 暨南大学《秘书学》2021-2022学年第一学期期末试卷
- 暨南大学《海外华文传播》2021-2022学年第一学期期末试卷
- 海姆立克急救法教学课件
- 药房质量管理体系文件的管理制度
- 教育心理学-形考作业4(第十至十一章)-国开-参考资料
- 2024年事业单位考试(综合管理类A类)职业能力倾向测验试卷及答案指导
- 2024旋挖桩施工合同范文
- 课内阅读(专项训练)-2024-2025学年统编版语文四年级上册
- 机械设计制造及其自动化专业《文献检索与论文写作》教学大纲
- JJF(津) 136-2024 硬件改装式电子计价秤欺骗性使用特征测试方法
- 【课件】跨学科实践:制作隔音房间模型人教版物理八年级上册
- 期中+(试题)+-2024-2025学年外研版(三起)英语六年级上册
- 2024至2030年中国AG玻璃行业市场发展潜力及投资策略研究报告
评论
0/150
提交评论