後一頁
前一頁
回目錄
回首頁
第十二章 異常處理與程式偵錯(二)

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 利用異常響應程式設計 

  利用異常處理機制不僅能使程式更加健壯,而且也提供了一種使程式更加簡捷、明了的途徑。事實上,使用自定義異常類就是一種利用異常響應程式設計的方式。這�塈畯怞A討論幾個利用標準異常類程式設計的例子。

  比如為了防止零作除數,可以在進行除法運算前使用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�堸鶡瑽A的程式。

  在程式偵錯過程中,程式的執行完全在你的控制之中。你可以在任何位置暫停程式的執行去檢查變數和數據結構的值,去顯示函數呼叫序列,去修改程式中變數的值以便觀察不同值對程式行為的影響。 

12.4.1.1 產生偵錯資訊 

  要使用內部偵錯器必須選中Option| Environment選擇表References頁的Integrated Debugging檢查框。缺省情況下該框被選中。

  在開始偵錯前需要使用Symbols Debug Information(偵錯符號資訊)編譯工程文件。偵錯符號資訊包含了一個符號表,能夠使偵錯器在程式的源代碼與編譯器產生的機器代碼間建立聯繫。這樣在程式執行中可以同時查看對應的源代碼。

  Delphi 在缺省情況下自動產生偵錯符號資訊。在整合開發環境中的開關選項是Option|project選擇表Compiler Options頁的Debug Information and Local Symbols檢查框。

  當產生的偵錯符號資訊供內部偵錯器使用時,編譯器把偵錯符號表儲存在每個相應的.dcu文件中。

  如果希望在整合環境外使用Turbo Debugger,則需要把偵錯資訊儲存在最終的 .exe文件中。為此需要選取Option|Project選擇表Linker頁的Include TDW Debug Info檢查框。

  由於儲存偵錯資訊大大增加了執行文件的大小,因而偵錯完成後應重新產生一個不包含偵錯資訊的執行文件。 

12.4.1.2 執行程式 

  通過偵錯器(包括內置偵錯器)執行程式,當程式處於等待狀態時,偵錯器可以獲得控制,利用偵錯器的功能來檢查目前程式的狀態。通過合理布置螢幕顯示,使應用程式執行視窗和Code Editor(代碼編輯器)互不重疊,可以讓用戶在它們間方便地切換以觀察代碼執行的效果。

  如果希望使用命令行參數來偵錯程式,則可以通過Run|Parameters 選擇表打開執行參數對話方塊進行設定。 

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

步進(Step Over) ● Run主選擇表的Step Over項

● Step Over加速按鈕

● F8

執行到斷點 設定斷點並按正常方式執行

暫停程式執行 Run主選擇表的Program Pause項

─────────────────────────────── 

  跟蹤和步進都是一種單步執行方式。但“步”的含義不同。對跟蹤而言它一次執行一條簡單程式語句。當碰到包含偵錯資訊的函數或過程呼叫時則跳入該函數或過程,並執行其第一條可執行語句。對步進而言它一次執行一條目前模群組的可執行語句,而不管該語句是否是函數或過程呼叫。

  執行到遊標位置和執行到斷點都是程式正常執行到某一確定的源代碼位置,而後進入偵錯狀態。但相對於執行到遊標位置而言,執行到斷點更為靈活。因為斷點一次可設定多個,同時也可以對斷點設定一定的條件。只有滿足該條件程式執行才會中止。

 


後一頁
前一頁
回目錄
回首頁