playbook/docs/tsl/syntax/08_objects_and_classes.md

1388 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 前缀。