12.3 異常響應
異常響應為開(kāi)發(fā)者提供了一個(gè)按自己的需要進(jìn)行異常處理的機制。try …except …end形成了一個(gè)異常響應保護塊。與finally不同的是:正常情況下except 后面的語(yǔ)句并不被執行,而當異常發(fā)生時(shí)程序自動(dòng)跳到except,進(jìn)入異常響應處理模塊。當異常被響應后異常類(lèi)自動(dòng)清除。
下面的例子表示了文件打開(kāi)、刪除過(guò)程中發(fā)生異常時(shí)的處理情況:
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用于判斷異常類(lèi)型。必須注意的是:except后面的語(yǔ)句必須包含在某一個(gè)on…do模塊中,而不能單獨存在。這又是同finally不同的一個(gè)地方?!?
12.3.1 使用異常實(shí)例
上面所使用的異常響應方法可總結為如下的形式:
on ExceptionType do
{響應某一類(lèi)的異常}
這種方法唯一使用的信息是異常的類(lèi)型。一般情況下這已能滿(mǎn)足我們的需要。但我們卻無(wú)法獲取異常實(shí)例中包含的信息,比如異常消息、錯誤代碼等。假設我們需要對它們進(jìn)行處理,那么就必須使用異常實(shí)例。
為了使用異常實(shí)例,需要為特定響應模塊提供一個(gè)臨時(shí)變量來(lái)保存它:
on EInstance : ExceptionType do …
在當前響應模塊中我們可以象使用一個(gè)普通對象那樣來(lái)引用它的數據成員。但在當前響應模塊之外不被承認。
下面的代碼用于獲取異常消息并按自己的方式顯示它:
{窗口中包括一個(gè)ScrollBar部件,一個(gè)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 提供缺省響應
在異常響應模塊中,一般我們只對希望響應的特定異常進(jìn)行處理。如果一個(gè)異常發(fā)生而響應模塊并沒(méi)有包含對它的處理代碼,則退出當前響應模塊,異常類(lèi)仍被保留。
為了保證任何異常發(fā)生后都能在當前響應模塊中被清除,可以定義缺省響應:
try
{程序正常功能}
except
on ESomething do
{響應特定異常}
else
{提供缺省響應}
end;
由于else可以響應任何異常,包括我們一無(wú)所知的異常,因此在缺省響應中最好只包括諸如顯示一個(gè)消息框之類(lèi)的處理,而不要改變程序的運行狀態(tài)或數據?!?
12.3.3 響應一族異?!?
諸如
on ExceptionType do
的異常響應語(yǔ)句不僅可響應本類(lèi)異常,而且可以響應子類(lèi)異常。對于象EIntError、EMathError等系統不會(huì )引發(fā)的異常,它們將只響應其子類(lèi)異常。而對于象
on Exception do
這樣的語(yǔ)句將會(huì )對任何異常進(jìn)行響應。
下面一段代碼對整數越界異常進(jìn)行單獨處理,而對其它整數異常進(jìn)行統一處理:
try
{整數運算}
except
on ERangeError do
{越界處理}
on EIntError do
{其它整數異常處理}
end;
由于異常在處理后即被清除,因而上面的代碼可保證不會(huì )使ERangeError異常被多次處理。假如顛倒兩條響應語(yǔ)句的順序,則ERangeError異常響應將永遠沒(méi)有被執行的機會(huì )?!?
由于異常在處理后即被清除,因而當希望對異常進(jìn)行多次處理時(shí)就需要使用保留字raise來(lái)重引發(fā)一個(gè)當前異常。
下面的代碼同時(shí)使用了異常響應和異常保護。異常響應用于設置變量的值,異常保護用于釋放資源。當異常響應結束時(shí)利用raise重引發(fā)一個(gè)當前異常?!?
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 自定義異常類(lèi)的應用
利用Delphi的異常類(lèi)機制我們可以定義自己的異常類(lèi)來(lái)處理程序執行中的異常情況。同標準異常不同的是:這種異常情況并不是相對于系統的正常運行,而是應用程序的預設定狀態(tài)。比如輸入一個(gè)非法的口令、輸入數據值超出設定范圍、計算結果偏離預計值等等。
使用自定義異常需要:
1.自己定義一個(gè)異常對象類(lèi);
2.自己引發(fā)一個(gè)異常?!?
12.3.5.1 定義異常對象類(lèi)
異常是對象,所以定義一類(lèi)新的異常同定義一個(gè)新的對象類(lèi)型并無(wú)太大區別。由于缺省異常處理只處理從Exception或Exception子類(lèi)繼承的對象,因而自定義異常類(lèi)應該作為Exception或其它標準異常類(lèi)的子類(lèi)。這樣,假如在一個(gè)模塊中引發(fā)了一個(gè)新定義的異常,而這個(gè)模塊并沒(méi)有包含對應的異常響應,則缺省異常處理機制將響應該異常,顯示一個(gè)包含異常類(lèi)名稱(chēng)和錯誤信息的消息框。
下面是一個(gè)異常類(lèi)的定義:
type
EMyException = Class(Exception) ;
12.3.5.2 自引發(fā)異?!?
引發(fā)一個(gè)異常,調用保留字raise,后邊跟一個(gè)異常類(lèi)的實(shí)例。
假如定義:
type
EPasswordInvalid = Class(Exception);
則在程序中如下的語(yǔ)句將引發(fā)一個(gè)EPasswordInvalid異常:
If Password <> CorrectPassword then
raise EPasswordInvalid.Create('Incorrect Password entered');
異常產(chǎn)生時(shí)把System庫單元中定義的變量ErrorAddr的值置為應用程序產(chǎn)生異常處的地址。在你的異常處理過(guò)程中可以引用ErrorAddr的值。
在自己引發(fā)一個(gè)異常時(shí),同樣可以為ErrorAddr分配一個(gè)值。
為異常分配一個(gè)錯誤地址需要使用保留字at,使用格式如下:
raise EInstance at Address_Expession;
12.3.5.3 自定義異常的應用舉例
下面我們給出一個(gè)利用自定義異常編程的完整實(shí)例。
兩個(gè)標簽框(Label1、Label2)標示對應編輯框的功能。編輯框PassWord和InputEdit用于輸入口令和數字。程序啟動(dòng)時(shí)Label2、InputEdit不可見(jiàn)。當在PassWord中輸入正確的口令時(shí),Label2、InputBox出現在屏幕上。此時(shí)Label1、PassWord隱藏。
設計時(shí),令Label2、InputEdit的Visible屬性為False。通過(guò)設置PassWord的PassWordChar可以確定輸入口令時(shí)回顯在屏幕上的字符。
自定義異常EInvalidPassWord和EInvalidInput分別用于表示輸入的口令非法和數字非法。它們都是自定義異常EInValidation的子類(lèi)。而EInValidation直接從Exception異常類(lèi)派生。
下面是三個(gè)異常類(lèi)的定義?!?
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增加了一個(gè)公有成員ErrorCode來(lái)保存錯誤代碼。錯誤代碼的增加提供了很大的編程靈活性。對于異常類(lèi),可以根據錯誤代碼提供不同的錯誤信息;對于使用者可以通過(guò)截取錯誤代碼,在try...except模塊之外來(lái)處理異常。
從以上定義可以發(fā)現:EInvalidPassWord和EInvalidInput的構造函數參數表中沒(méi)有表示錯誤信息的參數。事實(shí)上,它們保存在構造函數內部。下面是三個(gè)自定義異常類(lèi)構造函數的實(shí)現代碼?!?
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表示輸入數值越界。
口令檢查是用戶(hù)在PassWord中輸入口令并按下回車(chē)鍵后開(kāi)始的。實(shí)現代碼在PassWord的OnKeyPress事件處理過(guò)程中:
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事件處理過(guò)程中實(shí)現了輸入數字的合法性檢查:
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;
由于異常響應后即被清除,所以要顯示異常信息,需要另外的手段。在以上兩段程序中我們采用了兩種不同的方法:在口令合法性檢查中,利用異常重引發(fā)由系統進(jìn)行缺省響應;在輸入數字合法性檢查中,通過(guò)異常實(shí)例來(lái)獲取異常信息并由自己來(lái)顯示它。
以上所舉的是一個(gè)非常簡(jiǎn)單的例子,但從中已可以發(fā)現:使用自定義異常編程,為程序設計帶來(lái)了很大的靈活性?!?
12.3.6 利用異常響應編程
利用異常處理機制不僅能使程序更加健壯,而且也提供了一種使程序更加簡(jiǎn)捷、明了的途徑。事實(shí)上,使用自定義異常類(lèi)就是一種利用異常響應編程的方式。這里我們再討論幾個(gè)利用標準異常類(lèi)編程的例子。
比如為了防止零作除數,可以在進(jìn)行除法運算前使用if…then…else語(yǔ)句。但如果有一系列這樣的語(yǔ)句則繁瑣程度是令人難以忍受的。這時(shí)候我們可能傾向于使用EDivByZero異常。例如如下一段程序就遠比用if…then…else實(shí)現簡(jiǎn)捷明了?!?
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)記錄文件的打開(kāi)與創(chuàng )建中就是利用異常響應來(lái)實(shí)現文件的打開(kāi)或創(chuàng )建?!?
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('文件不能打開(kāi)',mtWarning,[mbOK],0);
end;
except
on EInOutError do
begin
FileOpened := False;
MessageDlg('文件不能創(chuàng )建',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;
總之,利用異常響應編程的中心思想是雖然存在預防異常發(fā)生的確定方法,但卻對異常的產(chǎn)生并不進(jìn)行事前預防,而是進(jìn)行事后處理,并以此來(lái)簡(jiǎn)化程序的邏輯結構?!?
12.4 程序調試簡(jiǎn)介
Delphi提供了一個(gè)功能強大的內置調試器(Integrated Debugger), 因而對程序的調試不用離開(kāi)集成開(kāi)發(fā)環(huán)境(IDE)就可以進(jìn)行。
程序錯誤基本可以分為兩類(lèi),即運行時(shí)間錯和邏輯錯。所謂運行時(shí)間錯是指程序能正常編譯但在運行時(shí)出錯。邏輯錯是指程序設計和實(shí)現上的錯誤。程序語(yǔ)句是合法的,并順利執行了,但執行結果卻不是所希望的。
對于這兩類(lèi)錯誤,調試器都可以幫助你快速定位錯誤,并通過(guò)對程序運行的跟蹤和對變量值的監視幫助你尋找錯誤的真正原因和解決錯誤的途徑。
程序調試的主要內容可以概括為如下的幾方面:
1.調試的準備和開(kāi)始;
2.控制程序的執行;
3.斷點(diǎn)的使用;
4.檢查數據的值。
程序調試只有用戶(hù)實(shí)際上機操作才能真正掌握。在這一節中我們主要對調試中的主要問(wèn)題和一些關(guān)鍵點(diǎn)進(jìn)行介紹。至于一些很細小的問(wèn)題相信讀者可以在上機實(shí)際應用中掌握,因而沒(méi)有列出。
12.4.1 調試的準備和開(kāi)始
在程序開(kāi)發(fā)過(guò)程中程序編碼和調試是一個(gè)持續的循環(huán)過(guò)程,只有在你對程序進(jìn)行了徹底的測試后才能交付最終用戶(hù)使用。為了保證調試的徹底性,在調試前應制定一個(gè)詳細的調試計劃。一般說(shuō)來(lái)應該把程序劃分為幾個(gè)相對獨立的部分,分別進(jìn)行調試,以利于錯誤的迅速定位,確保每一部分程序都按設計的要求運行。
調試計劃準備好后就可以開(kāi)始程序的調試。
開(kāi)始一個(gè)調試過(guò)程包括:
1.編譯時(shí)產(chǎn)生調試信息;
2.從Delphi里運行你的程序。
在程序調試過(guò)程中,程序的執行完全在你的控制之中。你可以在任何位置暫停程序的執行去檢查變量和數據結構的值,去顯示函數調用序列,去修改程序中變量的值以便觀(guān)察不同值對程序行為的影響?!?
12.4.1.1 產(chǎn)生調試信息
要使用內部調試器必須選中Option| Environment菜單References頁(yè)的Integrated Debugging檢查框。缺省情況下該框被選中。
在開(kāi)始調試前需要使用Symbols Debug Information(調試符號信息)編譯工程文件。調試符號信息包含了一個(gè)符號表,能夠使調試器在程序的源代碼與編譯器產(chǎn)生的機器代碼間建立聯(lián)系。這樣在程序執行中可以同時(shí)查看對應的源代碼。
Delphi 在缺省情況下自動(dòng)產(chǎn)生調試符號信息。在集成開(kāi)發(fā)環(huán)境中的開(kāi)關(guān)選項是Option|project菜單Compiler Options頁(yè)的Debug Information and Local Symbols檢查框。
當產(chǎn)生的調試符號信息供內部調試器使用時(shí),編譯器把調試符號表儲存在每個(gè)相應的.dcu文件中。
如果希望在集成環(huán)境外使用Turbo Debugger,則需要把調試信息儲存在最終的 .exe文件中。為此需要選定Option|Project菜單Linker頁(yè)的Include TDW Debug Info檢查框。
由于儲存調試信息大大增加了執行文件的大小,因而調試完成后應重新生成一個(gè)不包含調試信息的執行文件?!?
12.4.1.2 運行程序
通過(guò)調試器(包括內置調試器)運行程序,當程序處于等待狀態(tài)時(shí),調試器可以獲得控制,利用調試器的功能來(lái)檢查當前程序的狀態(tài)。通過(guò)合理布置屏幕顯示,使應用程序運行窗口和Code Editor(代碼編輯器)互不重疊,可以讓用戶(hù)在它們間方便地切換以觀(guān)察代碼執行的效果。
如果希望使用命令行參數來(lái)調試程序,則可以通過(guò)Run|Parameters 菜單打開(kāi)運行參數對話(huà)框進(jìn)行設置?!?
12.4.2 程序運行的控制
程序運行控制的方法和使用如下表?!?
表12.7 程序運行控制的方法和使用途徑
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方法 使用途徑
───────────────────────────────
運行到光標位置 ● Code Editor加速菜單的Run to Cursor項
(Run to Cursor) ● Run主菜單的Run to Cursor項
● F4
跟蹤(Trace Into) ● Run主菜單的Trace Into項
● Trace Into加速按鈕
● F7
步進(jìn)(Step Over) ● Run主菜單的Step Over項
● Step Over加速按鈕
● F8
運行到斷點(diǎn) 設置斷點(diǎn)并按正常方式運行
暫停程序執行 Run主菜單的Program Pause項
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
跟蹤和步進(jìn)都是一種單步執行方式。但“步”的含義不同。對跟蹤而言它一次執行一條簡(jiǎn)單程序語(yǔ)句。當碰到包含調試信息的函數或過(guò)程調用時(shí)則跳入該函數或過(guò)程,并執行其第一條可執行語(yǔ)句。對步進(jìn)而言它一次執行一條當前模塊的可執行語(yǔ)句,而不管該語(yǔ)句是否是函數或過(guò)程調用。
運行到光標位置和運行到斷點(diǎn)都是程序正常運行到某一確定的源代碼位置,而后進(jìn)入調試狀態(tài)。但相對于運行到光標位置而言,運行到斷點(diǎn)更為靈活。因為斷點(diǎn)一次可設置多個(gè),同時(shí)也可以對斷點(diǎn)設置一定的條件。只有滿(mǎn)足該條件程序運行才會(huì )中止
聯(lián)系客服