理解
单元之间可以互相依赖引用。一个单元引用另一个单元时,被引用单元既可写在interface接口段,也可仅写在implementation实现段,二者区别如下:
写在 interface(接口段引用) 可以基于被引用单元的内容,对外定义新的公开元素(过程、自定义类型等),整个项目其他单元都能访问。
仅写在 implementation(实现段引用) 使用范围受限:被引用单元里的标识符,只能在当前单元的实现代码内使用,外部无法访问。
例子
下面只是演示,读者还没学类class,所以先理解概念就可以。
添加一个新的单元文件 Classes.pas
unit Classes;
{$mode objfpc}{$H+}
interface
type
TObject=class;
TComponent=class(TObject)
constructor Create(AOwner:TComponent);virtual;
end;
TObject=class
destructor Destroy;virtual;
end;
implementation
constructor TComponent.Create(AOwner:TComponent);begin end;
destructor TObject.Destroy;begin end;
end.
然后在单元AnotherUnit.pas中引用
unit AnotherUnit;
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
interface
uses
Classes;
// TComponent 类定义在 Classes 单元,因此接口段必须引用 Classes
procedure DoSomethingWithComponent(var C: TComponent);
implementation
uses SysUtils; // 仅实现段引用 SysUtils
procedure DoSomethingWithComponent(var C: TComponent);
begin
// FreeAndNil 来自 SysUtils,仅在实现代码中调用,只需在 implementation 引用即可
FreeAndNil(C);
end;
end.Code language: JavaScript (javascript)
注意
1 接口段禁止双向循环依赖
不允许两个单元同时在各自的 interface 段互相引用对方。
原理:编译器解析单元接口时,必须先完整解析该单元uses接口段列出的所有依赖单元。Pascal 严格遵守该规则,带来两大优势:
编译速度更快; 编译器可自动判断哪些单元需要重新编译,无需手动维护复杂的 Makefile 编译脚本; 修改代码后不必全项目重编译,即可保证所有依赖同步更新。
2 实现段允许循环依赖
只要至少一方仅在implementation实现段引用对方,循环依赖就是合法的。
例:单元 A 在接口段引用单元 B,单元 B 仅在实现段引用单元 A,这种双向循环结构编译器完全支持。
Previous: Initialization和finalization
Next: 单元限定标识符