Delphi 應用程式中的單元,或說程式模組可謂老道精深。實際上,單元是程式模組化的基礎,類是繼它之後才有的。在Delphi 應用程式中,每個表單都有一個相對應的單元。用相應的工具按鈕, 或File > New Form 功能表命令,在工程中添加一個新表單,實際上是增加了一個新單元,也就是建立了該新表單的類。
單元
雖然所有表單都在單元中定義,但反之則不然。除表單外,單元中還可以定義一系列能訪問的常式。選擇File > New功能表命令,然後在Object Repository的New 頁中選擇Unit 表徵圖,隨即當前工程中就會加入一個空白單元。單元代碼分區存放,空白單元的代碼如下:
unit Unit1;interfaceimplementationend.
單元的概念比較簡單,單元名與檔案名稱相同,而且必須唯一。單元包括介面區(interface)及實現區(implementation),介面區用於聲明其它單元能看到的部分;實現區存放介面的實現代碼及外部不可見的聲明。此外還有兩個可選的區,即初始化區及結束區,其中初始化區存放初始化代碼,當程式載入到記憶體時執行;結束區存放程式終止時執行的代碼。
單元總體結構如下:
unit unitName;interface// other units we need to refer touses A, B, C;// exported type definitiontype newType = TypeDefinition;// exported constantsconst Zero = 0;// global variablesvar Total: Integer;// list of exported functions and proceduresprocedure MyProc;implementationuses D, E;// hidden global variablevar PartialTotal: Integer;// all the exported functions must be codedprocedure MyProc;begin // ... code of procedure MyProcend;initialization // optional initialization partfinalization // optional clean-up codeend.
介面區頭部的uses子句表示需要訪問的外部單元,這些外部單元中定義了你需要引用的資料類型,如自訂表單內所用的控制項。
實現區頭部的uses子句用於表示只在實現部分訪問的單元。如果常式或方法的代碼需要引用其他單元,你應該把這些單元加到實現區子句中,而不是介面區。你所引用的單元必須在工程檔案目錄中能找到,或在工程選項對話方塊的 Directories/Conditionals 頁設定這些單元的搜尋路徑。
C++程式員應該知道uses語句和include 指令是不同的。uses語句只是用於輸入引用單元的先行編譯介面部分,引用單元的實現部分在單元編譯時間才考慮。你引用的單元可以是原始碼格式(PAS),也可以是編譯格式(DCU),但是必須用同一版本的Delphi進行編譯。
在單元的介面區中可以聲明許多不同的元素,包括過程、函數、全程變數及資料類型。在Delphi 應用程式中,資料類型可能用得最頻繁。每建立一個表單,Delphi 會在單元中自動建立一個新的資料類型--類(class)。在Delphi 單元中不僅能定義表單;還能象傳統單元一樣,只包含過程及函數;還可以定義與表單和任何可視控制項無關的類。
單元的工作空間
Pascal單元是封裝性和可視性的關鍵,它很可能比類中的 private 和 public 關鍵字還要重要。(實際上,private關鍵字與類單元的工作空間有關)。一個標識符(如一個變數、過程、函數或資料類型)的工作空間是指能訪問標識符的程式碼片段。基本原則是:標識符在它工作空間內才有意義,也就是說,只在其聲明的代碼塊中才有意義,在工作空間外你不能訪問這些標識符。例如:
- 局部變數:如果你在常式或方法代碼塊內聲明一個變數,那麼單元外部不能訪問這個變數。該標識符的工作空間就是定義標識符的整個常式,其中包括嵌套常式(除非嵌套常式內有一個同名標識符覆蓋了外部定義)。當調用到常式時,其變數壓入棧記憶體中,常式一結束,棧中的記憶體就自動釋放。
- 全程隱藏變數:如果你在單元的實現部分聲明一個標識符,那麼在單元外你不能使用它,但是能在單元內任一代碼塊及過程中使用它。程式一啟動就會為全程隱藏變數分配記憶體,程式終止記憶體釋放,你可以在單元初始化區給它賦初值。
- 全程變數:如果你在單元的介面部分宣告身份識別符,那麼該標識符的工作空間就擴大了,任何Use它的單元都能訪問它。這類變數的記憶體配置及生命週期與上類變數相同,唯一不同的是其可見度。
只要程式單元的uses子句中列出某一單元名,那麼所列單元介面區中聲明的任何標識符該程式都能訪問。表單類的變數就是這樣聲明的,所以你可以在其他表單代碼中訪問這個表單以及它的公用域、方法、屬性和組件。當然把什麼都聲明為全域標識這種編程習慣並不好。除了明顯的記憶體消耗問題外,使用全程變數使代碼維護和更新變得困難。一句話,你應該盡量少用全程變數。
單元用作命名空間
uses 語句是訪問其他單元工作空間的標準技術,通過該語句你能訪問其它單元的定義內容。如果恰巧兩個單元聲明的標識符同名,也就是說你可能有兩個同名的類或常式,遇到這種情況,你可以用單元名作首碼定義類型或過程名,由此進行區分。例如用Totals.ComputeTotal訪問Totals 單元中的ComputeTotal 過程。不過這種情況最好不要經常遇到,因此強烈建議不要在同一程式中用同一名字表示兩個不同的東西。
然而,如果查閱VCL庫和Windows 檔案,你會發現一些Delphi 函數和Delphi 可用的Windows API 函數同名,不過參數往往不同,下面以Beep 過程為例說明這個問題。
建立一個Delphi 程式,添加一個按鈕,然後寫入以下代碼:
procedure TForm1.Button1Click(Sender: TObject);begin Beep;end;
執行程式,單擊按鈕,你會聽到一個短促的聲音。現在移到單元的uses語句,把原先的代碼:
uses Windows, Messages, SysUtils, Classes, ...
改為下面的樣式,只要把SysUtils單元移到Windows之前:
uses SysUtils, Windows, Messages, Classes, ...
現在如果重新編譯這段代碼,你會得到一個編譯錯誤:”Not enough actual parameters”(實際參數不夠)。問題在於Windows 單元定義了另一個帶兩個參數的Beep 函數。應該說uses子句中第一個單元的定義被後面單元的定義覆蓋,解決方案很簡單:
procedure TForm1.Button1Click(Sender: TObject);begin SysUtils.Beep;end;
不管uses子句中單元順序如何排列,以上代碼都能編譯通過。在Delphi中很少有其他命名衝突的情況,因為Delphi 代碼通常放在類的方法中,如果不同類中有兩個同名的方法不會產生任何衝突,只是使用全程常式會產生衝突問題。
單元和程式
Delphi 應用程式原始碼檔案可分成兩類,它們是一個或多個單元檔案及一個程式檔案,單元檔案可以看成是次一級的檔案,它被應用程式的主體——程式檔案——引用。理論上如此,實際上程式檔案通常是自動產生的,作用有限,程式啟動並運行主表單時才會用到它。程式檔案的代碼,或說Delphi 工程檔案(DPR),可用手工進行編輯,也可通過工程管理器及其與應用程式、表單相關的選項進行編輯。
程式檔案的結構通常比單元檔案的結構簡單得多。下面是一個程式檔案的原始碼:
program Project1;uses Forms, Unit1 in Unit1.PAS� {Form1DateForm};begin Application.Initialize; Application.CreateForm (TForm1, Form1); Application.Run;end.
從上可見,檔案中只有一個uses區和應用程式的主體代碼,主體程式碼封裝含在begin 和 end 關鍵字之間。程式的uses子句特別重要,因為需要通過它來管理應用程式的編譯和串連。
結束語
討論完Delphi中Pascal 應用程式的結構,本書就翻過了最後一章,至少目前是這樣,歡迎來email發表你的意見和要求。
如果想進一步學習Delphi Object Pascal中物件導向的內容,你可以參考我已出版的書《精通Delphi 5》(Mastering Delphi 5 ,Sybex,1999),詳細情況可訪問網址http://www.marcocanto.com/,你也可以從該網址上下載本書的最新版本。