Pascal 编程/单元
在原始的标准 Pascal 中,除了 Pascal 本身定义的标准函数之外,程序的所有功能都必须在一个文件中定义,即 program
源代码文件。虽然在教学的背景下,源代码仍然比较短,但整个应用程序很快就变得杂乱无章,尽管使用各种注释来结构化文本。
很快,就出现了各种将程序模块化的尝试。最值得注意的实现,至今仍在使用,是 UCSD Pascal 的单元概念。
一个 UCSD Pascal 单元就像一个 program
,除了它不能独立运行,而是应该被 program
使用。一个 unit
可以像任何 program
一样定义常量、类型、变量和例程,但它没有可以独立运行的可执行部分。使用一个单元意味着该单元成为程序的一部分;这类似于将单元的整个源代码复制到 program
中,但并不完全相同。
通常,单元存储在单独的文件中,从而极大地清理了 program
的源代码文件。然而,这不是一个硬性要求,因为在模块的 end.
之后,该模块被认为是完整的,可以跟随另一个模块。
从现在开始,作为另一层抽象,模块是指 program 或 unit 。(在 FP 中,library 也是另一种模块类型。) |
一个 unit
定义与普通的 program
非常相似,但具有许多额外的功能。
一个 unit
的第一行看起来像这样
unit myGreatUnit;
与 program
不同,它没有参数列表。一个 unit
是一个包含特定功能的自包含单元,因此无法以任何方式进行参数化。[fn 1]
这一行还声明了一个新的标识符,在这个例子中是 myGreatUnit
。 MyGreatUnit
成为所谓完全限定标识符的第一部分。稍后将详细介绍。
unit
概念提供了封装其定义的方法,因此使用该 unit
的程序员不需要了解特定功能的实现方式。
这是通过将 unit
分成两部分来实现的
interface
部分,以及implementation
部分。
使用另一个单元的程序员只需要知道如何使用该单元:这在 interface
部分中概述。另一方面,编程该单元的程序员需要在 implementation
部分中实现该单元的功能。因此,一个最基本的单元看起来像这样
unit myGreatUnit;
interface
implementation
end.
interface
部分必须在 implementation
部分之前。还要注意, unit
s 使用 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
中,因此它可用于其他使用该模块的模块。
每个 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),其中包含作为语言的一部分定义的所有标准例程(例如 writeLn
和 ord
)。在 Delphi 和 FPC 中,该单元称为 system
,而 GPC 则带有一个 GPC
单元。这些单元有时被称为运行时库,简称为 RTL。
由于这些单元提供了 Pascal 的标准例程,因此它们必须作为第一个单元导入。但是,由于人们容易犯错,编译器将负责确保 RTS 首先加载。因此,禁止编写 uses system; 。如果您尝试手动导入 RTL,FPC 会发出错误。 |
了解 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.
备注