1388 lines
31 KiB
Markdown
1388 lines
31 KiB
Markdown
# TSL 对象与类
|
||
|
||
文档类型:语法主线
|
||
是否可直接用于生成代码:仅部分
|
||
是否含可直接照写示例:是
|
||
是否含不可照写反例:是
|
||
遇到不确定时:先按本页候选页继续判断;[02_core_model.md](02_core_model.md)(优先)、[09_units_and_scope.md](09_units_and_scope.md)、[20_object_runtime_and_introspection.md](20_object_runtime_and_introspection.md)、[21_builtin_runtime_objects.md](21_builtin_runtime_objects.md)、[24_object_overloads_and_iteration.md](24_object_overloads_and_iteration.md)、[11_pitfalls.md](11_pitfalls.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
|
||
|
||
这一篇收拢面向对象语法,只保留语言层规则。
|
||
|
||
## 本篇职责
|
||
|
||
回答“`type Name = class`、字段、`static`、方法、`property`、析构、类类型、继承和对象创建在 TSL 里怎样写”。
|
||
|
||
本页后半段有少量依赖 `unit` 的双文件示例;如果你还没建立 `unit` / `uses` 的多文件心智模型,先看 [09_units_and_scope.md](09_units_and_scope.md)。
|
||
|
||
## 智能体对象/类判断流程
|
||
|
||
1. 先判断要写普通对象、继承、构造函数、类方法、静态字段还是运行时创建。
|
||
2. 对象声明优先使用本页明确的 `type Name = class ... end;` 骨架。
|
||
3. 普通本地类创建默认用 `new ClassName()`;只有需要字符串类名、类类型变量或跨 `unit` 路径时,才用 `createObject(...)`。
|
||
4. 类方法和静态字段优先用 `class(Name).Member` 或文档明确反射入口,不要裸写类名调用。
|
||
5. 构造函数默认保持 `public create`,不要把 `private` / `protected create` 当成会自动执行的构造函数。
|
||
6. 普通类成员默认显式写 `public` 段,不依赖隐式 public。
|
||
7. 方法体内访问当前实例成员默认直接写成员名;成员读写为了性能不加 `self` 前缀。
|
||
8. 命中对象反射 / 运行时状态、内置运行时对象、对象重载 / 迭代时,分别跳到 [20_object_runtime_and_introspection.md](20_object_runtime_and_introspection.md)、[21_builtin_runtime_objects.md](21_builtin_runtime_objects.md)、[24_object_overloads_and_iteration.md](24_object_overloads_and_iteration.md)。
|
||
9. 没有文档事实时不要发明对象/类写法。
|
||
|
||
## 核心规则
|
||
|
||
- 类定义统一按 `type Name = class ... end;` 写。
|
||
- 顶层类声明可以放在松散语句之后(单向允许)。
|
||
- 顶层类声明也可以放在顶层 `function / procedure` 之后。
|
||
- 类声明不要写在函数体内部。
|
||
- 在松散语句脚本里,可以先写松散语句再进入顶层类声明;但一旦进入顶层 `type Name = class ... end;` 声明,后面不能再回到松散语句模式。
|
||
- 类里可以直接写字段和方法。
|
||
- 字段可以写类型注解,例如 `name_: string;`。
|
||
- 类方法参数和返回值可以写类型注解,例如 `function ReadName(): string;`。
|
||
- 类的声明和实现可以分离:类内可以只声明方法签名,再用 `function ClassName.Method(...)` 在类外实现;带类型和 `overload` 时,类外实现保持同一组签名;类外实现属于声明区,后面不要再追加脚本语句。
|
||
- `static` 字段可用;可以通过类类型访问,也可以通过实例访问。
|
||
- `const` 成员可用;实例常量可通过实例读取,`static const` 可通过类类型读取,也可用于成员函数默认参数。
|
||
- 方法体里默认直接用成员名访问当前实例成员;普通成员读写为了性能不加 `self` 前缀,也不要为了“显式”访问而补这个前缀。
|
||
- `overload` 方法可用;同名不同参数个数的方法可以共存。
|
||
- 基础 `property` 形态:`property Name read fieldOrMethod write fieldOrMethod`。
|
||
- `property Name: Type ...` 这种类型注解写法可用。
|
||
- 参数化 `property` 可用;本页只写基础模式。
|
||
- 参数化 `property` 的 accessor 方法记录两种常见模式:读方法接同参数个数,写方法接“参数个数 + 赋值值”。
|
||
- 索引型 `property` 可用;调用时用圆括号 `obj.Prop(index)`。
|
||
- 固定 `index` property 可用;它可以把某个固定索引直接映射成普通属性读写。
|
||
- `class(Name)` 和 `findClass("Name")` 都可以拿到类类型。
|
||
- `findClass("ClassName", obj)` 可把对象转成指定父类视图。
|
||
- `class function` 可用;本页里的“类方法”就是指 `class function`,前者是中文描述,后者是代码写法。
|
||
- 类方法默认先拿到类类型,再在类类型上调用。
|
||
- 基础继承写法是 `type Child = class(Parent)`。
|
||
- 多重继承写法 `type Child = class(Base1, Base2)` 可用。
|
||
- 基础覆盖写法是:父类方法声明为 `virtual`,子类对应方法声明为 `override`。
|
||
- 基础祖先类调用:可以用 `Inherited;`、`Inherited MethodName(...)` 或 `class(BaseClass, ObjectName).MethodName()`。
|
||
- 创建对象有两种方式:`new ClassName()` 最常用,`createObject(...)` 作为次选。
|
||
- 普通本地类实例化默认生成 `new ClassName()`;`createObject("ClassName")`、`createObject(ClassType)` 只在字符串类名、类类型变量或跨 `unit` 路径场景生成。
|
||
- 如果类里定义了 `function create(...)`,`new`、`createObject("ClassName", ...)` 和 `createObject(ClassType, ...)` 都可以透传构造参数,也都支持默认参数和命名参数。
|
||
- 析构写法是无参 `function destroy();`;把对象引用设为 `nil` 时会触发它。
|
||
- 工厂式 `self(0)` / `self(1)` 可用;不要生成 `self()` 这种无参工厂式写法。
|
||
- 跨 `unit` 类路径创建和继承属于多文件边界;先回看 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md),不要从普通单文件 `new` / `createObject` 规则直接泛化。
|
||
- `{$ifdef parentClassInUnit}` 为真,可用于探测“继承和构造单元中的类”能力是否可用。
|
||
- `private` / `protected` / `public` 可用;类开头未写可见性时,成员默认按 `public` 处理,但生成代码默认显式写 `public`。
|
||
- 同一个可见性段里后续没有切换关键字的成员,会沿用前一个可见性。
|
||
- 外部访问 `private` / `protected` 字段或方法,会在执行时报对象成员访问错误。
|
||
- 子类可以直接访问父类 `protected` 成员;不能直接访问父类 `private` 成员。
|
||
- `create` 写成 `private` 或 `protected` 时,对象仍能创建,但 `new` 和 `createObject(...)` 不会执行该构造逻辑;构造函数应保持 `public`。
|
||
- 更复杂的多重继承边界和更复杂的对象模型不在本页展开。
|
||
|
||
## 可直接照写示例
|
||
|
||
使用这些示例时遵守:
|
||
|
||
- 普通本地类创建优先复制 `new ClassName()` 形态。
|
||
- `createObject(...)` 示例只在字符串类名、类类型变量或跨 `unit` 路径场景复制。
|
||
- `property` 类型注解只有在已有类型名证据时生成;不要为了完整性发明说明性类型名。
|
||
|
||
### 最小类与声明位置
|
||
|
||
最短类骨架:
|
||
|
||
如果任务只是要“先写出一个类”,先从这个骨架起手;后面的例子再逐步进入字段、工厂函数和声明位置边界。
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
end;
|
||
```
|
||
|
||
字段、方法和 `new`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
public
|
||
name;
|
||
function SetName(new_name);
|
||
begin
|
||
name := new_name;
|
||
end;
|
||
end;
|
||
|
||
function MakePerson();
|
||
begin
|
||
return new Person();
|
||
end;
|
||
```
|
||
|
||
类声明位置:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
obj := new MyClass();
|
||
obj.value := 5;
|
||
writeLn(obj.value);
|
||
|
||
type MyClass = class
|
||
public
|
||
value;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 输出 `5`
|
||
- 说明顶层类声明可以放在松散语句之后
|
||
- 同时也说明 `new MyClass()` 可以解析到后面才出现的类声明
|
||
|
||
代码块身份:输出片段
|
||
|
||
```text
|
||
5
|
||
```
|
||
|
||
顶层函数后面也可以继续声明类:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
function MakeObj();
|
||
begin
|
||
return 1;
|
||
end;
|
||
|
||
type MyClass = class
|
||
public
|
||
value;
|
||
end;
|
||
```
|
||
|
||
结果说明:
|
||
|
||
- 上述声明顺序可以编译通过
|
||
|
||
类声明位置的反例:
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
// 函数体内部声明类
|
||
function Demo();
|
||
begin
|
||
type InnerClass = class
|
||
value;
|
||
end;
|
||
return 1;
|
||
end;
|
||
|
||
// 松散语句在 type 之后继续出现
|
||
a := 1;
|
||
type MyClass = class
|
||
public
|
||
value;
|
||
end;
|
||
writeLn(a);
|
||
```
|
||
|
||
结果说明:
|
||
|
||
- 函数体内部声明类会报 `invalid statement`
|
||
- 在松散语句脚本里,`type MyClass = class ... end;` 之后继续写 `writeLn(a);` 也会报 `invalid statement`
|
||
|
||
### 字段、静态成员、常量与可见性
|
||
|
||
`static` 字段:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
class(THuman).mCount := 100;
|
||
writeLn(class(THuman).mCount);
|
||
h := new THuman();
|
||
writeLn(class(THuman).mCount);
|
||
writeLn(h.mCount);
|
||
|
||
type THuman = class
|
||
public
|
||
static mCount;
|
||
function create();
|
||
begin
|
||
mCount := (mCount ?: 0) + 1;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `class(THuman).mCount := 100` 可以直接写静态字段
|
||
- `class(THuman).mCount` 先输出 `100`
|
||
- 创建对象后,`class(THuman).mCount` 输出 `101`
|
||
- 通过实例读取 `h.mCount` 也输出 `101`
|
||
- 单个静态字段默认写成 `static mCount;`
|
||
|
||
`const` 成员:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
o := new C();
|
||
writeLn(o.TestConst());
|
||
writeLn(o.TestConstInParam());
|
||
writeLn(o.mA);
|
||
writeLn(class(C).mB);
|
||
|
||
type C = class
|
||
public
|
||
const mA = 1;
|
||
static const mB = mA + 10;
|
||
function TestConst();
|
||
begin
|
||
return mA + mB;
|
||
end;
|
||
function TestConstInParam(b = mB);
|
||
begin
|
||
return b;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 依次输出 `12`、`11`、`1`、`11`
|
||
- 说明类里可以定义实例常量和 `static const`
|
||
- 说明实例常量可通过实例读取,`static const` 可通过 `class(C)` 读取
|
||
- 说明成员常量也可用于成员函数默认参数
|
||
|
||
成员方法内直接访问成员:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
c := new Counter(10);
|
||
writeLn(c.Inc());
|
||
|
||
type Counter = class
|
||
public
|
||
value;
|
||
function create(v);
|
||
begin
|
||
value := v;
|
||
end;
|
||
function Inc();
|
||
begin
|
||
value := value + 1;
|
||
return value;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `c.Inc()` 输出 `11`
|
||
|
||
可见性 `private` / `protected` / `public`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
a := new A();
|
||
a.value := 9;
|
||
writeLn(a.value);
|
||
writeLn(a.ReadSecret());
|
||
|
||
type A = class
|
||
public
|
||
value;
|
||
private
|
||
secret;
|
||
backup;
|
||
public
|
||
function create();
|
||
begin
|
||
secret := 2;
|
||
backup := 3;
|
||
end;
|
||
function ReadSecret();
|
||
begin
|
||
return secret + backup;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `value` 写在显式 `public` 段里,因此 `a.value := 9` 可以直接写。
|
||
- `backup` 跟在 `private` 段后面,没有重新切换关键字,因此仍按 `private` 处理。
|
||
- 上述例子依次输出 `9`、`5`。
|
||
|
||
`protected` 可供子类直接访问:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
b := new B();
|
||
b.SetValue(66);
|
||
writeLn(b.GetValue());
|
||
writeLn(b.TryCall());
|
||
|
||
type A = class
|
||
protected
|
||
value;
|
||
function Hidden();
|
||
begin
|
||
return 88;
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function SetValue(v);
|
||
begin
|
||
value := v;
|
||
end;
|
||
function GetValue();
|
||
begin
|
||
return value;
|
||
end;
|
||
function TryCall();
|
||
begin
|
||
return Hidden();
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 子类里可以直接访问父类 `protected` 字段。
|
||
- 子类里也可以直接调用父类 `protected` 方法。
|
||
- 上述例子依次输出 `66`、`88`。
|
||
|
||
`private` 的失败场景,这里用 `text` 展示反例:
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
// 外部访问 private 字段
|
||
a := new A();
|
||
writeLn(a.secret);
|
||
|
||
type A = class
|
||
private
|
||
secret;
|
||
public
|
||
function create();
|
||
begin
|
||
secret := 7;
|
||
end;
|
||
end;
|
||
|
||
// 子类调用父类 private 方法
|
||
b := new B();
|
||
writeLn(b.TryCall());
|
||
|
||
type A = class
|
||
private
|
||
function Hidden();
|
||
begin
|
||
return 77;
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function TryCall();
|
||
begin
|
||
return Hidden();
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 外部访问 `a.secret` 会在执行时报对象成员访问错误。
|
||
- 子类里直接调用父类 `private` 方法也会在执行时报错。
|
||
- `private` / `protected` 方法访问也遵循同样边界:外部不能调 `private` / `protected` 方法,子类只能调 `protected` 方法,不能调 `private` 方法。
|
||
|
||
### 构造函数边界
|
||
|
||
`create` 建议保持 `public`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
a := new A(111);
|
||
writeLn(a.value);
|
||
|
||
type A = class
|
||
public
|
||
value;
|
||
private
|
||
function create(v);
|
||
begin
|
||
value := v;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `a := new A(111)` 可以创建对象。
|
||
- 上述例子里的 `a.value` 输出 `<NIL>`,说明 `private create` 没有执行。
|
||
- `protected create`、`createObject("A", ...)` 和 `createObject(class(A), ...)` 也按同一规则处理;构造函数应保持 `public`。
|
||
|
||
### 属性、类型注解与类外实现
|
||
|
||
基础 `property`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
b := new MyBox();
|
||
b.Value := 7;
|
||
writeLn(b.Value);
|
||
b.Value := -1;
|
||
writeLn(b.Value);
|
||
|
||
type MyBox = class
|
||
public
|
||
_value;
|
||
function SetValue(v);
|
||
begin
|
||
if v > 0 then
|
||
_value := v;
|
||
end;
|
||
property Value read _value write SetValue;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `b.Value := 7` 后,`b.Value` 输出 `7`
|
||
- `b.Value := -1` 后,`b.Value` 仍输出 `7`
|
||
|
||
带类型注解的 `property`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
b := new MyBox();
|
||
b.Value := 9;
|
||
writeLn(b.Value);
|
||
|
||
type MyBox = class
|
||
public
|
||
_value;
|
||
function SetValue(v);
|
||
begin
|
||
_value := v;
|
||
end;
|
||
property Value: integer read _value write SetValue;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `property Value: integer ...` 这种类型注解写法可以通过
|
||
- 上述例子中的 `b.Value` 输出 `9`
|
||
|
||
字段和类方法类型注解:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
box := new TypedBox("abc", 7);
|
||
writeLn(box.Name);
|
||
writeLn(box.Value);
|
||
writeLn(box.ReadName());
|
||
|
||
type TypedBox = class
|
||
public
|
||
function create(_name: string; _value: any);
|
||
begin
|
||
name_ := _name;
|
||
value_ := _value;
|
||
end;
|
||
function ReadName(): string;
|
||
begin
|
||
return name_;
|
||
end;
|
||
property Name: string read name_ write name_;
|
||
property Value: any read value_ write value_;
|
||
private
|
||
name_: string;
|
||
value_: any;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `name_: string;` 和 `value_: any;` 可以作为类字段类型注解。
|
||
- `function create(_name: string; _value: any);` 可以作为类方法参数类型注解。
|
||
- `function ReadName(): string;` 可以作为类方法返回值类型注解。
|
||
- 上述例子依次输出 `abc`、`7`、`abc`。
|
||
|
||
代码块身份:输出片段
|
||
|
||
```text
|
||
abc
|
||
7
|
||
abc
|
||
```
|
||
|
||
类内声明、类外实现的带类型重载方法:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
a := new PairBox("left");
|
||
writeLn(a.Left);
|
||
writeLn(a.Right);
|
||
b := new PairBox("left", 2);
|
||
writeLn(b.Left);
|
||
writeLn(b.Right);
|
||
writeLn(b.ReadLeft());
|
||
|
||
type PairBox = class
|
||
public
|
||
function create(_left: string); overload;
|
||
function create(_left: string; _right: any); overload;
|
||
function ReadLeft(): string;
|
||
property Left: string read left_ write left_;
|
||
property Right: any read right_ write right_;
|
||
private
|
||
left_: string;
|
||
right_: any;
|
||
end;
|
||
|
||
function PairBox.create(_left: string); overload;
|
||
begin
|
||
create(_left, nil);
|
||
end;
|
||
|
||
function PairBox.create(_left: string; _right: any); overload;
|
||
begin
|
||
left_ := _left;
|
||
right_ := _right;
|
||
end;
|
||
|
||
function PairBox.ReadLeft(): string;
|
||
begin
|
||
return left_;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 类内可以只声明带类型的重载方法签名。
|
||
- 类外实现写成 `function PairBox.create(...); overload;`,并保持同样的参数类型和 `overload` 标记。
|
||
- 类外实现属于声明区;写完后不要再追加脚本语句。
|
||
- 从一个构造函数转调另一个构造函数时,直接写 `create(_left, nil);`,不要加 `self` 前缀。
|
||
- 上述例子依次输出 `left`、`<NIL>`、`left`、`2`、`left`。
|
||
|
||
代码块身份:输出片段
|
||
|
||
```text
|
||
left
|
||
<NIL>
|
||
left
|
||
2
|
||
left
|
||
```
|
||
|
||
索引型 `property`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
aa := new A();
|
||
aa.idx(0) := "abc";
|
||
writeLn(aa.idx(0));
|
||
|
||
type A = class
|
||
public
|
||
arr;
|
||
function create();
|
||
begin
|
||
arr := array();
|
||
end;
|
||
function rIndex(i);
|
||
begin
|
||
return arr[i];
|
||
end;
|
||
function wIndex(i, value);
|
||
begin
|
||
arr[i] := value;
|
||
end;
|
||
property idx read rIndex write wIndex;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `aa.idx(0) := "abc"` 可以写入索引 property
|
||
- `aa.idx(0)` 输出 `abc`
|
||
|
||
固定 `index` property:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
aa := new A();
|
||
aa.idx0 := "abc";
|
||
writeLn(aa.idx0);
|
||
writeLn(aa.idx(0));
|
||
|
||
type A = class
|
||
public
|
||
arr;
|
||
function create();
|
||
begin
|
||
arr := array();
|
||
end;
|
||
function rIndex(i);
|
||
begin
|
||
return arr[i];
|
||
end;
|
||
function wIndex(i, value);
|
||
begin
|
||
arr[i] := value;
|
||
end;
|
||
property idx read rIndex write wIndex;
|
||
property idx0 index 0 read rIndex write wIndex;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `aa.idx0 := "abc"` 可以写入固定整数索引 property
|
||
- `aa.idx0` 输出 `abc`
|
||
- `aa.idx(0)` 也输出 `abc`
|
||
|
||
固定字符串索引:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
aa := new A();
|
||
aa.school := "math";
|
||
writeLn(aa.school);
|
||
writeLn(aa.idx("High school"));
|
||
|
||
type A = class
|
||
public
|
||
arr;
|
||
function create();
|
||
begin
|
||
arr := array();
|
||
end;
|
||
function rIndex(i);
|
||
begin
|
||
return arr[i];
|
||
end;
|
||
function wIndex(i, value);
|
||
begin
|
||
arr[i] := value;
|
||
end;
|
||
property idx read rIndex write wIndex;
|
||
property school index "High school" read rIndex write wIndex;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `property school index "High school"` 这种固定字符串索引写法可以通过
|
||
- `aa.school` 输出 `math`
|
||
- `aa.idx("High school")` 也输出 `math`
|
||
|
||
参数化 `property`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
d := new MyDate();
|
||
d.DateV(2025, 8) := 10;
|
||
writeLn(d.DateV());
|
||
|
||
type MyDate = class
|
||
public
|
||
_year;
|
||
_month;
|
||
_day;
|
||
function getDateV();
|
||
begin
|
||
return _year * 10000 + _month * 100 + _day;
|
||
end;
|
||
function setDateV(y, m, d);
|
||
begin
|
||
_year := y;
|
||
_month := m;
|
||
_day := d;
|
||
end;
|
||
property DateV(y, m) read getDateV write setDateV;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `d.DateV(2025, 8) := 10` 可以给参数化 property 赋值
|
||
- `d.DateV()` 输出 `20250810`
|
||
|
||
参数化 `property` 的 accessor 方法参数:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
aa := new A();
|
||
aa.Item(2) := "x";
|
||
writeLn(aa.Item(2));
|
||
|
||
type A = class
|
||
public
|
||
arr;
|
||
function create();
|
||
begin
|
||
arr := array();
|
||
end;
|
||
function getItem(i);
|
||
begin
|
||
return arr[i];
|
||
end;
|
||
function setItem(i, value);
|
||
begin
|
||
arr[i] := value;
|
||
end;
|
||
property Item(i): string read getItem write setItem;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `read getItem` 这种“读方法接同参数个数”的写法可以通过
|
||
- `write setItem` 这种“写方法接参数个数 + 赋值值”的写法可以通过
|
||
- 上述例子中的 `aa.Item(2)` 输出 `x`
|
||
|
||
### 对象创建与类类型
|
||
|
||
`new` 关键字:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
end;
|
||
|
||
function MakePerson();
|
||
begin
|
||
return new Person();
|
||
end;
|
||
```
|
||
|
||
通过类类型创建对象:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
cls := findClass("Person");
|
||
obj := createObject(cls);
|
||
obj.value := 42;
|
||
writeLn(obj.value);
|
||
|
||
type Person = class
|
||
public
|
||
value;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `createObject(cls)` 可以通过类类型创建对象
|
||
- 上述例子中的 `obj.value` 输出 `42`
|
||
|
||
也可以用 `class(Name)` 取得类类型:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
cls := class(Person);
|
||
obj := createObject(cls, 44);
|
||
writeLn(obj.value);
|
||
|
||
type Person = class
|
||
public
|
||
value;
|
||
function create(v);
|
||
begin
|
||
value := v;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `class(Person)` 可以拿到 `Person` 的类类型
|
||
- `createObject(cls, 44)` 输出 `44`
|
||
|
||
构造参数、默认值和命名参数:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
a := new Person(22, c:99);
|
||
writeLn(a.x);
|
||
writeLn(a.y);
|
||
writeLn(a.z);
|
||
b := createObject("Person", 11);
|
||
writeLn(b.x);
|
||
writeLn(b.y);
|
||
writeLn(b.z);
|
||
|
||
type Person = class
|
||
public
|
||
x;
|
||
y;
|
||
z;
|
||
function create(a, b = 20, c = 30);
|
||
begin
|
||
x := a;
|
||
y := b;
|
||
z := c;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `new Person(22, c:99)` 依次输出 `22`、`20`、`99`
|
||
- `createObject("Person", 11)` 依次输出 `11`、`20`、`30`
|
||
- 说明构造函数默认参数和命名参数不只适用于普通函数调用,也适用于对象创建
|
||
|
||
类类型也同样支持默认参数和命名参数:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
cls := findClass("Person");
|
||
obj := createObject(cls, 33, c:77);
|
||
writeLn(obj.x);
|
||
writeLn(obj.y);
|
||
writeLn(obj.z);
|
||
|
||
type Person = class
|
||
public
|
||
x;
|
||
y;
|
||
z;
|
||
function create(a, b = 20, c = 30);
|
||
begin
|
||
x := a;
|
||
y := b;
|
||
z := c;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `createObject(cls, 33, c:77)` 依次输出 `33`、`20`、`77`
|
||
- 因此类类型入口下的 `createObject(cls, ...)` 也遵循同样的构造参数规则
|
||
|
||
`class function`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
cls1 := class(MathBox);
|
||
writeLn(cls1.Add(3, 4));
|
||
cls2 := findClass("MathBox");
|
||
writeLn(cls2.Add(5, 6));
|
||
|
||
type MathBox = class
|
||
public
|
||
class function Add(x, y);
|
||
begin
|
||
return x + y;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `class(MathBox).Add(...)` 可以调用类方法
|
||
- `findClass("MathBox").Add(...)` 可以调用类方法
|
||
- 上述例子依次输出 `7`、`11`
|
||
|
||
### 类方法、重载、继承与析构
|
||
|
||
`overload` 方法:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
t := new TestClass();
|
||
writeLn(t.fun(1, 2));
|
||
writeLn(t.fun(5));
|
||
|
||
type TestClass = class
|
||
public
|
||
function fun(p1, p2); overload;
|
||
begin
|
||
return p1 + p2;
|
||
end;
|
||
function fun(p1); overload;
|
||
begin
|
||
return p1 + 10;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 依次输出 `3`、`15`
|
||
- 说明同名不同参数个数的方法可以共存
|
||
|
||
基础继承:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
d := new Dog();
|
||
writeLn(d.Speak());
|
||
|
||
type Animal = class
|
||
public
|
||
function Speak();
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type Dog = class(Animal)
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `type Dog = class(Animal)` 可以继承父类实例方法
|
||
- 上述例子中的 `d.Speak()` 输出 `1`
|
||
|
||
多重继承:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
obj := new C();
|
||
writeLn(obj.Fa());
|
||
writeLn(obj.Fb());
|
||
|
||
type A = class
|
||
public
|
||
function Fa();
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type B = class
|
||
public
|
||
function Fb();
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
type C = class(A, B)
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `type C = class(A, B)` 可以同时继承 `A` 和 `B` 的方法
|
||
- 上述例子依次输出 `1`、`2`
|
||
|
||
多重继承下的同名方法优先级:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
obj := new C();
|
||
writeLn(obj.Speak());
|
||
|
||
type A = class
|
||
public
|
||
function Speak();
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type B = class
|
||
public
|
||
function Speak();
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
type C = class(A, B)
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 当多个父类存在同名方法时,本例优先命中第一个父类 `A`
|
||
- 上述例子中的 `obj.Speak()` 输出 `1`
|
||
|
||
基础 `virtual` / `override`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
d := new Dog();
|
||
writeLn(d.Speak());
|
||
|
||
type Animal = class
|
||
public
|
||
function Speak(); virtual;
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type Dog = class(Animal)
|
||
public
|
||
function Speak(); override;
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 父类 `virtual` + 子类 `override` 组合可以通过
|
||
- 上述例子中的 `d.Speak()` 输出 `2`
|
||
|
||
`class(BaseClass, ObjectName).MethodName()`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
obj := new B();
|
||
writeLn(obj.Speak());
|
||
writeLn(class(A, obj).Speak());
|
||
|
||
type A = class
|
||
public
|
||
function Speak(); virtual;
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function Speak(); override;
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `obj.Speak()` 输出 `2`
|
||
- `class(A, obj).Speak()` 会定向调用祖先类方法,因此输出 `1`
|
||
|
||
`findClass("ClassName", obj)` 强制转型:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
objb := new ClassB();
|
||
obj := findClass("ClassA", objb);
|
||
writeLn(obj.Fuc());
|
||
writeLn(obj is class(ClassA));
|
||
writeLn(obj is class(ClassB));
|
||
|
||
type ClassA = class
|
||
public
|
||
function Fuc(); virtual;
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type ClassB = class(ClassA)
|
||
public
|
||
function Fuc(); override;
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `findClass("ClassA", objb)` 会把 `objb` 转成 `ClassA` 视图
|
||
- 上述例子中的 `obj.Fuc()` 输出 `1`
|
||
- 转型后的 `obj is class(ClassA)` 输出 `1`
|
||
- 转型后的 `obj is class(ClassB)` 输出 `0`
|
||
|
||
`Inherited;`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
obj := new B();
|
||
obj.Speak();
|
||
|
||
type A = class
|
||
public
|
||
function Speak(); virtual;
|
||
begin
|
||
writeLn(1);
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function Speak(); override;
|
||
begin
|
||
Inherited;
|
||
writeLn(2);
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `Inherited;` 会先执行父类同名同参数方法
|
||
- 上述例子依次输出 `1`、`2`
|
||
|
||
`Inherited MethodName(...)`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
obj := new B();
|
||
obj.Run();
|
||
|
||
type A = class
|
||
public
|
||
function BaseValue(x);
|
||
begin
|
||
writeLn(x + 1);
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function Run();
|
||
begin
|
||
Inherited BaseValue(5);
|
||
writeLn(9);
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `Inherited BaseValue(5)` 可以显式调用父类指定方法
|
||
- 上述例子依次输出 `6`、`9`
|
||
|
||
析构函数 `destroy`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
h := new THuman();
|
||
writeLn(class(THuman).GetCount());
|
||
h := nil;
|
||
writeLn(class(THuman).GetCount());
|
||
|
||
type THuman = class
|
||
public
|
||
static mCount;
|
||
function create();
|
||
begin
|
||
mCount := (mCount ?: 0) + 1;
|
||
end;
|
||
function destroy();
|
||
begin
|
||
mCount--;
|
||
writeLn(mCount);
|
||
end;
|
||
class function GetCount();
|
||
begin
|
||
return mCount;
|
||
end;
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 创建对象后,`class(THuman).GetCount()` 输出 `1`
|
||
- `h := nil` 时会触发 `destroy()`,中途输出 `0`
|
||
- 释放后再次读取 `class(THuman).GetCount()` 也输出 `0`
|
||
|
||
`self(0)` / `self(1)`:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
c := new ChildBox();
|
||
a := c.MakeOwner();
|
||
b := c.MakeBase();
|
||
writeLn(a is class(ChildBox));
|
||
writeLn(b is class(ChildBox));
|
||
|
||
type BaseBox = class
|
||
public
|
||
function MakeOwner();
|
||
begin
|
||
return self(1);
|
||
end;
|
||
function MakeBase();
|
||
begin
|
||
return self(0);
|
||
end;
|
||
end;
|
||
type ChildBox = class(BaseBox)
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `self(1)` 返回的对象在这个例子里是 `ChildBox`,因此输出 `1`
|
||
- `self(0)` 返回的对象在这个例子里不是 `ChildBox`,因此输出 `0`
|
||
|
||
### 跨 unit 类路径
|
||
|
||
`unit` 中的嵌套类路径创建属于跨 `unit` 边界,这里用 `text` 展示骨架:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:跨 `unit` 类路径骨架;依赖 `unit` 查找路径和目标环境对象模型,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// Unit1.tsf
|
||
unit Unit1;
|
||
|
||
interface
|
||
|
||
type Class1 = class
|
||
public
|
||
type Class2 = class
|
||
public
|
||
value;
|
||
end;
|
||
end;
|
||
|
||
implementation
|
||
|
||
end.
|
||
|
||
// main.tsl
|
||
|
||
uses Unit1;
|
||
obj := createObject("Unit1.Class1.Class2");
|
||
obj.value := 42;
|
||
writeLn(obj.value);
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 这个骨架只说明字符串路径创建需要按跨 `unit` 边界处理。
|
||
- 生成实际代码前先回看 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
|
||
- 不要把它直接改写成普通本地类的 `new ClassName()` 模式。
|
||
|
||
继承单元中的嵌套类路径属于跨 `unit` 边界,这里也用 `text` 展示骨架:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:跨 `unit` 父类路径骨架;依赖 `unit` 查找路径和目标环境对象模型,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// Unit1.tsf
|
||
unit Unit1;
|
||
|
||
interface
|
||
|
||
type Class1 = class
|
||
public
|
||
type Class2 = class
|
||
public
|
||
value;
|
||
end;
|
||
end;
|
||
|
||
implementation
|
||
|
||
end.
|
||
|
||
// main.tsl
|
||
|
||
uses Unit1;
|
||
|
||
type MyNewClass = class(Unit1.Class1.Class2)
|
||
end;
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- 这个骨架只说明继承声明里的父类路径形态属于跨 `unit` 边界。
|
||
- 不要把它泛化成普通表达式 `class(Unit1.Class1.Class2)`。
|
||
- 对象创建方式不要从普通本地类 `new ClassName()` 规则直接外推。
|
||
|
||
能力探测:
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
{$ifdef parentClassInUnit}
|
||
writeLn(1);
|
||
{$else}
|
||
writeLn(0);
|
||
{$endif}
|
||
```
|
||
|
||
输出说明:
|
||
|
||
- `{$ifdef parentClassInUnit}` 输出 `1`
|
||
|
||
## 默认生成模板
|
||
|
||
最短类骨架直接复用本页开头的“最短类骨架”。
|
||
|
||
代码块身份:可直接照写示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
end;
|
||
```
|
||
|
||
## 禁止项
|
||
|
||
- 以下误写不可照写;本节只收容易被智能体从相近语言或相邻 TSL 写法外推出来的边界,不重复列已经在核心规则里明确的普通规则。
|
||
- 不把裸类名成员访问当成文档事实;类方法和静态字段默认用 `class(Name).Member` 或文档明确反射入口。
|
||
- 把 `self()` 当成本页明确的工厂式写法。
|
||
- 把 `private` / `protected create` 当成一定会自动执行的构造函数。
|
||
- 在子类里把父类 `private` 成员当成 `protected` 成员来访问。
|
||
- 把带命名空间的 `new a.b.c` 直接当成文档事实。
|
||
- 把 `class(Unit1.Class1.Class2)` 这种表达式写法直接当成文档事实。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
obj := new Unit1.Class1.Class2();
|
||
```
|
||
|
||
上面这种写法不作为可写事实;本页只把 `createObject("Unit1.Class1.Class2")` 这种路径创建写成可用事实。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
cls := class(Unit1.Class1.Class2);
|
||
```
|
||
|
||
上面这种表达式写法不作为可写事实;本页只把继承声明里的 `class(Unit1.Class1.Class2)` 和字符串路径创建写成可用事实。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
MathBox.Add(1, 2);
|
||
THuman.mCount := 7;
|
||
```
|
||
|
||
上面这种裸类名成员访问不作为可写事实;类方法可用 `class(MathBox).Add(...)` 或 `findClass("MathBox").Add(...)` 调用,静态字段可用 `class(THuman).mCount` 访问。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
self();
|
||
```
|
||
|
||
上面这种无参工厂式 `self()` 不作为可写事实;本页只把 `self(0)` / `self(1)` 这种工厂式写成可用事实。普通成员访问仍直接写成员名,不加 `self` 前缀。
|