跳轉至內容

Pascal 程式設計/單元

來自華夏公益教科書,開放的書籍,開放的世界

在原始的標準 Pascal 中,除了 Pascal 本身定義的標準函式之外,程式的所有功能都必須在一個檔案中定義,即 program 原始碼檔案。雖然在教學的背景下,原始碼仍然比較短,但整個應用程式很快就變得雜亂無章,儘管使用各種註釋來結構化文字。

很快,就出現了各種將程式模組化的嘗試。最值得注意的實現,至今仍在使用,是 UCSD Pascal 的單元概念。

UCSD Pascal 單元

[編輯 | 編輯原始碼]

一個 UCSD Pascal 單元就像一個 program,除了它不能獨立執行,而是應該被 program 使用。一個 unit 可以像任何 program 一樣定義常量、型別、變數和例程,但它沒有可以獨立執行的可執行部分。使用一個單元意味著該單元成為程式的一部分;這類似於將單元的整個原始碼複製到 program 中,但並不完全相同。

通常,單元儲存在單獨的檔案中,從而極大地清理了 program 的原始碼檔案。然而,這不是一個硬性要求,因為在模組的 end. 之後,該模組被認為是完整的,可以跟隨另一個模組。

Note 從現在開始,作為另一層抽象,模組是指 programunit。(在 FP 中,library 也是另一種模組型別。)

定義單元

[編輯 | 編輯原始碼]

一個 unit 定義與普通的 program 非常相似,但具有許多額外的功能。

一個 unit 的第一行看起來像這樣

unit myGreatUnit;

program 不同,它沒有引數列表。一個 unit 是一個包含特定功能的自包含單元,因此無法以任何方式進行引數化。[fn 1]

這一行還聲明瞭一個新的識別符號,在這個例子中是 myGreatUnitMyGreatUnit 成為所謂完全限定識別符號的第一部分。稍後將詳細介紹。

unit 概念提供了封裝其定義的方法,因此使用該 unit 的程式設計師不需要了解特定功能的實現方式。

這是透過將 unit 分成兩部分來實現的

  1. interface 部分,以及
  2. implementation 部分。

使用另一個單元的程式設計師只需要知道如何使用該單元:這在 interface 部分中概述。另一方面,程式設計該單元的程式設計師需要在 implementation 部分中實現該單元的功能。因此,一個最基本的單元看起來像這樣

unit myGreatUnit;
interface
implementation
end.

interface 部分必須 implementation 部分之前。還要注意, units 使用 end. 終止,就像 program 一樣。

interface 部分包含一個塊,但它不能包含任何語句。 interface 僅僅是宣告性的。在 interface 部分中定義的所有識別符號都將成為“公共的”,也就是說,使用該單元的程式設計師可以訪問它們。另一方面,在 implementation 部分中定義的所有識別符號都是“私有的”:它們只能在單元自己的 implementation 部分使用。沒有辦法繞過這種匯出和“私有”程式碼的分離。

unit randomness;

// public  - - - - - - - - - - - - - - - - - - - - - - - - -
interface

// a list of procedure/function signatures makes
// them usable from outside of the unit
function getRandomNumber(): integer;

// a definition (an implementation) of a routine
// must not be in the interface-part

// private - - - - - - - - - - - - - - - - - - - - - - - - -
implementation

function getRandomNumber(): integer;
begin
	// chosen by fair dice roll
	// guaranteed to be random
	getRandomNumber := 4;
end;

end.

使用單元

[編輯 | 編輯原始碼]

現在,我們終於將一些程式碼外包出去,這是件好事,但這一切的目的是要使用外包的程式碼。為此,UCSD Pascal 定義了 uses 子句。一個 uses 子句指示編譯器匯入另一個單元的程式碼,並熟悉該單元 interface 部分中宣告的所有識別符號。因此,來自單元 interface 部分的所有識別符號都將可用,就好像它們是透過 uses 子句匯入它們的模組的一部分一樣。以下是一個示例

program chooseNextCandidate(input, output);
uses
	// imports a unit
	randomness;

begin
	writeLn('next candidate: no. ', getRandomNumber());
end.

注意,program chooseNextCandidate 既沒有定義也沒有宣告函式 getRandomNumber,但仍然使用了它。由於 getRandomNumber 的簽名列在 interface 部分的 randomness 中,因此它可用於其他使用該模組的模組。

Note 每個 program 最多隻能有一個 uses 子句。它必須出現在程式頭之後。

Uses 子句在任何模組中都是允許的。當然,可以在 unit 內使用其他單元。此外,您可以在一個 unit 中使用兩個 uses 子句,一個在 interface 中,另一個在 implementation 部分中。列在 interface 部分 uses 子句中的單元會傳播,這意味著它們也會傳播到使用這些單元的模組中。[fn 2][fn 3]

名稱空間

[edit | edit source]

現在,如果所有編寫的單元都必須顯式定義獨佔識別符號,那麼使用單超程式設計將會很麻煩。但情況並非如此。隨著模組的出現,所有模組都隱式地構成了一個名稱空間。名稱空間是一個自包含的範圍,其中只有範圍內的識別符號需要是唯一的。您可以隨意定義自己的 getRandomNumber 並仍然使用 randomness 單元。

為了區分來自不同名稱空間的識別符號,可以透過在識別符號之前新增名稱空間名稱來限定識別符號,兩者之間用點隔開。因此,randomness.getRandomName 清楚地標識了由 randomness 單元匯出的 getRandomNumber 函式。這種表示法稱為完全限定識別符號,簡稱為 FQI。

優先順序

[edit | edit source]

依賴項

[edit | edit source]

更多功能

[edit | edit source]

初始化和最終化部分

[edit | edit source]

不帶原始碼的釋出

[edit | edit source]

單元設計

[edit | edit source]

需要考慮一些因素

  • 無論何時,如果某些程式碼可能對其他程式也有用,您可能希望建立一個單獨的單元。
  • 一個單元應該提供所有必要的才能使其有用的功能,但是,
  • 一個單元不應該提供與其主要目的無關的功能。
  • 單元的可用性很大程度上取決於定義良好的介面。需要了解具體的實現通常是程式碼質量差的指標。

特殊單元

[edit | edit source]

執行時系統

[edit | edit source]

一些編譯器使用單元來提供某些功能,這些功能服務於編譯器實際任務和 program (即您編寫的) 之間的灰色區域。最值得注意的是,Delphi、FPC 以及 GPC 提供了一個執行時系統 (RTS),其中包含作為語言的一部分定義的所有標準例程(例如 writeLnord)。在 Delphi 和 FPC 中,該單元稱為 system,而 GPC 則帶有一個 GPC 單元。這些單元有時被稱為執行時庫,簡稱為 RTL。

Note 由於這些單元提供了 Pascal 的標準例程,因此它們必須作為第一個單元匯入。但是,由於人們容易犯錯,編譯器將負責確保 RTS 首先載入。因此,禁止編寫 uses system;。如果您嘗試手動匯入 RTLFPC 會發出錯誤。

瞭解 RTS’s 單元叫什麼名字可能很有用,因為這意味著 RTL 的所有識別符號都是一個名稱空間的一部分。這意味著,在(例如)Delphi 和 FP 中,可以使用其短名稱以及 FQI system.abs 來引用標準函式 abs。如果您在當前範圍內遮蔽了 abs 函式,但需要使用 Pascal 自身的 abs 函式,則可能需要後者。

除錯

[edit | edit source]

FPC 帶有一個名為 heapTrc(堆跟蹤)的特殊單元。此單元提供了一個記憶體管理器。它用於找出 program 是否未釋放之前為其保留的任何記憶體塊。分配記憶體而不將其返還給 OS 被稱為“記憶體洩漏”,這是一種非常糟糕的情況。由於 heapTrc 單元對 Pascal 的記憶體管理具有侵入性行為,因此它也需要在 system 單元載入後立即載入。因此,FPC 禁止您在 uses 子句中顯式包含 heapTrc 單元,但提供了 -gh 編譯器命令列開關,它將確保包含該單元。

heapTrc 單元僅在開發階段使用。它可以在 program 的最後一個 end. 後列印記憶體報告。

heapTrc 單位使用起來比較簡單,但也功能有限。我們建議使用專用的除錯和效能分析工具,例如 valgrind(1),因為了解如何使用此類工具將對您在將來切換程式語言時有很大幫助。如果您在呼叫 fpc(1) 時指定了 -gv 開關,FPC 將插入除錯資訊,以便與 valgrind(1) 一起使用。

其他模組化實現

[編輯 | 編輯原始碼]

擴充套件 Pascal 標準為 module 定義了規範。這些提供了更高階的模組化方法。但是,FPC 和 Delphi 都不支援它,只有 GPC 支援。

編寫一個在程式終止時說再見的單位,即在終端列印一條訊息。
您可以利用 finalization 部分作為鉤子來實現該行為。
unit friendly;
interface
implementation
finalization
begin
	writeLn('Goodbye!');
end;
end.
您可以利用 finalization 部分作為鉤子來實現該行為。
unit friendly;
interface
implementation
finalization
begin
	writeLn('Goodbye!');
end;
end.

備註

  1. 擴充套件 Pascal 標準中描述的 module 概念確實允許模組引數化。
  2. 截至 3.0.4 版本,FPC 不支援識別符號的傳播。
  3. PXSC 樣式的模組需要使用 use global someModuleName 來啟用識別符號的傳播。


下一頁: 面向物件 | 上一頁: 作用域
首頁: Pascal 程式設計
華夏公益教科書