13 KiB
TSL unit 与作用域
文档类型:语法主线 是否可直接用于生成代码:仅部分 是否含可直接照写示例:是 是否含不可照写反例:是 遇到不确定时:先按本页候选页继续判断;02_core_model.md(优先)、19_namespace_libpath_and_unit_runtime.md、11_pitfalls.md;仍不命中时回到语法路由中心 index.md;如果问题已经超出语法层,回到 TSL 总入口 ../index.md
这一篇只讲“怎么写和怎么调用 unit”。namespace、查找路径、unit 生命周期、tsl.conf 与运行时入口统一放到 19_namespace_libpath_and_unit_runtime.md。
本篇职责
回答下面几类最常见的问题:
- 什么时候该把一组函数写成
unit uses到底能放在哪里interface段uses和implementation段uses的可见性差别是什么- 多个
unit里有同名接口时,未限定调用到底命中谁 - 怎样显式指定要调用哪个
unit的接口
智能体 unit/作用域判断流程
- 先判断当前交付是
.tsl可执行脚本、.tsf扩展模块,还是unit文件。 unit文件只描述可复用单元;脚本入口仍放在.tsl。uses必须放在普通语句之前;普通语句后不要再追加顶层uses。- 默认参数、接口段、实现段和作用域边界只照本页文档明确形态写。
- 命中
unit生命周期、命名空间、查找路径、tsl.conf或命令行查找路径参数时,跳到 19_namespace_libpath_and_unit_runtime.md。 - 没有对应代码块时不要发明 unit/作用域写法。
核心规则
unit是完整的顶层主体;常见完整形态是unit Name; interface ... implementation ... end.。unit也可以省略interface/implementation写成简写形态;这种简写里定义的函数对外可调用。- 如果没有特殊需求,默认优先用完整形态;简写形态只在不需要显式区分
interface/implementation时再用。 - 完整
unit示例中,implementation前保留空行,避免接口段和实现段挤在一起。 unit允许接口声明与实现段分离:interface段可以只声明函数签名或类方法签名,函数体和类方法体放到implementation段。- 类方法放在
implementation段实现时,使用function ClassName.Method(...)形态。 - 顶层
uses可以放在文件最前面,让后续整个文件直接看到被引入unit的接口。 - 顶层导入多个
unit时,只生成单条逗号合并写法:uses UnitA, UnitB;;不要生成连续多条顶层uses。真正会失败的是前面已经出现普通语句后,再写顶层uses。 - 函数体里的
uses必须是函数体第一条语句,而且一个函数体里只能写一次。 - 类定义体里的
uses必须是类定义体第一条语句,而且一个类定义体里只能写一次。 - 如果函数体或类定义体需要多个
unit,默认把这些uses提升到顶层并合并成单条uses UnitA, UnitB;;不要在函数体或类定义体里连写两条uses。 - 写在
interface段的uses对整个unit可见;写在implementation段的uses只对实现段和在实现段里定义的方法体可见。 - 只写在
implementation里的函数,对unit外部不可见。 - 出现重名接口时,未限定调用按
uses从后往前解析;最后一个uses的同名接口优先。 - 要显式指定某个
unit的接口,可以用UnitA.Ping()、unit(UnitA).Ping()或call("UnitA.Ping")。 unit生命周期、接口状态、命名空间和查找路径细节,转去看 19_namespace_libpath_and_unit_runtime.md。
可直接照写示例
最小 unit
代码块身份:可直接照写示例
unit DemoUnit;
interface
function Ping();
implementation
function Ping();
begin
return 1;
end;
end.
结果说明:
- 上面这就是最小完整
unit骨架。 unit文件结尾要用end.,不是普通函数或类的end;。
如果该 unit 被脚本 uses DemoUnit; 后调用 writeLn(Ping());,可观察输出为:
代码块身份:输出片段
1
简写 unit
代码块身份:可直接照写示例
unit SpecialUnit;
function Abcd();
begin
return 10;
end;
function Abcd2();
begin
return 20;
end;
initialization
writeLn("INIT");
finalization
writeLn("FINAL");
end.
结果说明:
unit SpecialUnit;后可以省略interface/implementation。- 这种简写形态里定义的
Abcd()、Abcd2()外部可以直接调用。 - 简写
unit里同样可以写initialization和finalization。
顶层、函数体、类体里的 uses
沿用同一个 DemoUnit.tsf,分别看顶层、函数体、类体三种 uses 位置:
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 unit 查找路径,不是可直接复制的单文件最小示例。
// DemoUnit.tsf
unit DemoUnit;
interface
function Ping();
implementation
function Ping();
begin
return 11;
end;
end.
// main.tsl
uses DemoUnit;
obj := new Worker();
writeLn(Ping());
writeLn(RunInFunction());
writeLn(obj.Run());
function RunInFunction();
begin
uses DemoUnit;
return Ping();
end;
type Worker = class
uses DemoUnit;
public
function Run();
begin
return Ping();
end;
end;
结果说明:
- 顶层
uses DemoUnit;后,脚本主体可以直接调用Ping()。 - 函数体第一行写
uses DemoUnit;后,RunInFunction()输出11。 - 类定义体第一行写
uses DemoUnit;后,obj.Run()也输出11。 - 这一类多文件场景依赖真实查找路径;如果
DemoUnit.tsf所在目录不在查找路径里,脚本仍会找不到对应接口。
uses 的位置限制
代码块身份:反例 / 不可照写
// 顶层前面已经有普通语句
a := 1;
uses DemoUnit;
// 函数里不是第一行
function Run();
begin
a := 1;
uses DemoUnit;
return Ping();
end;
// 函数里写两次
function Run();
begin
uses UnitA;
uses UnitB;
return Ping();
end;
// 类里不是第一行
type Worker = class
public
value;
uses DemoUnit;
end;
// 类里写两次
type Worker = class
uses UnitA;
uses UnitB;
end;
结果说明:
- 顶层前面如果已经出现
a := 1;这类普通语句,再写顶层uses会报invalid statement。 - 函数体里,
uses不是第一行或者重复出现,都会失败。 - 类定义体里,
uses不是第一行或者重复出现,都会失败。 - 函数体或类定义体里重复
uses的失败点是“第二条uses”,不是顶层导入多个unit。 - 顶层导入多个
unit时只生成uses UnitA, UnitB;这种逗号合并写法;连续多条顶层uses不再作为文档明确生成形态。
interface 段 uses 和 implementation 段 uses
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 unit 查找路径,不是可直接复制的单文件最小示例。
// UnitB.tsf
unit UnitB;
interface
function FB();
implementation
function FB();
begin
return 100;
end;
end.
// UnitC.tsf
// 与 UnitB.tsf 同形,只把 FB / 100 改成 FC / 200
// UnitA.tsf
unit UnitA;
interface
uses UnitB;
type TBox = class
public
function FromInterface();
begin
return FB();
end;
function FromImplementation();
end;
function CallB();
function CallC();
implementation
uses UnitC;
function TBox.FromImplementation();
begin
return FC();
end;
function CallB();
begin
return FB();
end;
function CallC();
begin
return FC();
end;
end.
// main.tsl
uses UnitA;
obj := new TBox();
writeLn(CallB());
writeLn(CallC());
writeLn(obj.FromInterface());
writeLn(obj.FromImplementation());
结果说明:
- 在
interface段写uses UnitB;后,FB()对整个unit都可见,CallB()输出100。 - 在
implementation段写uses UnitC;后,FC()对实现段函数和在实现段定义的方法体可见,CallC()与obj.FromImplementation()都输出200。 - 接口段里的内联方法
FromInterface()能访问interface段uses引入的接口,因此输出100。 - 接口段里的内联方法不能看到只在
implementation段uses引入的接口。
接口声明与实现段分离
代码块身份:可直接照写示例
unit SplitDemo;
interface
function MakeValue();
type UnitBox = class
public
value;
function create(_value);
function ReadValue();
end;
implementation
function MakeValue();
begin
return 10;
end;
function UnitBox.create(_value);
begin
value := _value;
end;
function UnitBox.ReadValue();
begin
return value;
end;
end.
结果说明:
- 普通函数可以在
interface段只声明function MakeValue();,再在implementation段写函数体。 - 类方法可以在类体里只声明签名,再在
implementation段写function UnitBox.Method(...)实现。 - 类方法实现放在
implementation段时,仍属于该unit的实现段。
实现段私有函数
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 unit 查找路径,不是可直接复制的单文件最小示例。
// PrivateDemo.tsf
unit PrivateDemo;
interface
function PublicFunc();
implementation
function PublicFunc();
begin
return PrivateFunc();
end;
function PrivateFunc();
begin
return 77;
end;
end.
// main.tsl
uses PrivateDemo;
writeLn(PublicFunc());
结果说明:
PublicFunc()输出77,说明接口函数可以调用实现段私有函数。- 外部直接调用
PrivateFunc()时,会报function:PrivateFunc compile error or not found。
代码块身份:反例 / 不可照写
uses PrivateDemo;
writeLn(PrivateFunc());
上面这种写法不可照写;PrivateFunc() 只写在 implementation 段,对 unit 外部不可见。
重名解析与限定调用
重名函数最小对照:
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 unit 查找路径,不是可直接复制的单文件最小示例。
// UnitA.tsf
unit UnitA;
interface
function Ping();
function Hello();
implementation
function Ping();
begin
return 101;
end;
function Hello();
begin
return 111;
end;
end.
// UnitB.tsf
// 与 UnitA.tsf 同形,只把 101 / 111 改成 202 / 222
// main.tsl
uses UnitA, UnitB;
writeLn(Ping());
writeLn(UnitA.Hello());
writeLn(UnitB.Hello());
writeLn(unit(UnitA).Ping());
writeLn(call("UnitB.Ping"));
结果说明:
- 未限定的
Ping()输出202,说明解析顺序是uses从后往前。 UnitA.Hello()输出111,UnitB.Hello()输出222。unit(UnitA).Ping()输出101。call("UnitB.Ping")输出202。
重名类最小对照:
代码块身份:配置片段 / 概念骨架
代码块说明:多文件结构骨架;依赖 unit 查找路径,不是可直接复制的单文件最小示例。
// UnitA.tsf
unit UnitA;
interface
type Box = class
public
value;
function create();
end;
implementation
function Box.create();
begin
value := 101;
end;
end.
// UnitB.tsf
// 与 UnitA.tsf 同形,只把 Box.create() 里写入的 101 改成 202
// main.tsl
uses UnitA, UnitB;
obj := new Box();
writeLn(obj.value);
objA := createObject("UnitA.Box");
writeLn(objA.value);
objB := createObject("UnitB.Box");
writeLn(objB.value);
结果说明:
- 未限定的
new Box()输出202。 createObject("UnitA.Box")输出101。createObject("UnitB.Box")输出202。- 重名类的未限定解析顺序和重名函数一致,也是按
uses从后往前。
默认生成模板
如果你只是要先写出一个最小、可被其他文件引用的 unit,从下面这个骨架起步:
代码块身份:可直接照写示例
unit DemoUnit;
interface
function Ping();
implementation
function Ping();
begin
return 1;
end;
end.
后面的多文件片段只在需要 uses、可见性或重名解析时再回来看。
禁止项
- 把依赖外部
unit的uses例子误当成单文件最小示例。 - 在还没把目标
unit放进查找路径前,就把“找不到接口”误判成uses语法错。 - 把函数体或类定义体里的
uses写在第一条语句之后。 - 在同一个函数体或类定义体里重复写第二个
uses。 - 生成连续多条顶层
uses;多个unit默认写成uses UnitA, UnitB;。 - 把重名接口的未限定解析顺序理解成“从前到后”。
- 忘了显式指定
UnitA.Ping()、unit(UnitA).Ping()或call("UnitA.Ping"),结果误调用到最后一个uses的同名接口。