1321 lines
29 KiB
Markdown
1321 lines
29 KiB
Markdown
# Objects And Classes
|
||
|
||
文档类型:语法主线
|
||
是否可直接用于生成代码:仅部分
|
||
是否含已验证可执行示例:是
|
||
是否含已验证反例:是
|
||
遇到不确定时跳转到:[10_units_and_scope.md](10_units_and_scope.md)(优先)、[23_object_runtime_and_introspection.md](23_object_runtime_and_introspection.md)、[12_pitfalls.md](12_pitfalls.md)
|
||
|
||
手册位置:第 9 篇,共 32 篇。上一篇:[08_control_flow.md](08_control_flow.md)。下一篇:[10_units_and_scope.md](10_units_and_scope.md)。
|
||
|
||
这一篇收拢面向对象语法,只保留语言层规则。
|
||
|
||
## 这一篇解决什么问题
|
||
|
||
回答“`type Name = class`、字段、`static`、方法、`property`、析构、类类型、继承和对象创建在 TSL 里怎样写”。
|
||
|
||
本页后半段有少量依赖 `unit` 的双文件示例;如果你还没建立 `unit` / `uses` 的多文件心智模型,先看 [10_units_and_scope.md](10_units_and_scope.md)。
|
||
|
||
## 必须记住的规则
|
||
|
||
- 类定义统一按 `type Name = class ... end;` 写。
|
||
- 顶层类声明当前已验证可以放在松散语句之后(单向允许)。
|
||
- 顶层类声明当前也已验证可以放在顶层 `function / procedure` 之后。
|
||
- 类声明当前没有通过写在函数体内部。
|
||
- 在松散语句脚本里,可以先写松散语句再进入顶层类声明;但一旦进入顶层 `type Name = class ... end;` 声明,后面不能再回到松散语句模式。
|
||
- 类里可以直接写字段和方法。
|
||
- `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()`。
|
||
- 已验证的对象创建写法至少有三种:`CreateObject("ClassName")`、`CreateObject(ClassType)` 和 `new ClassName()`。
|
||
- 如果没有特殊需求,默认优先用 `new ClassName()`;只有需要字符串路径、类类型变量或跨 `unit` 路径时,再改用 `CreateObject(...)`。
|
||
- 如果类里定义了 `function create(...)`,`new`、`CreateObject("ClassName", ...)` 和 `CreateObject(ClassType, ...)` 都已验证可以透传构造参数,也都已验证支持默认参数和命名参数。
|
||
- 当前已验证的析构写法是无参 `function destroy();`;把对象引用设为 `nil` 时会触发它。
|
||
- 工厂式 `self(0)` / `self(1)` 已验证可用;当前解释器没有通过 `self()` 这种无参工厂式写法。
|
||
- 已做双文件运行验证:如果类定义在 `unit` 的嵌套路径里,可以用 `CreateObject("Unit1.Class1.Class2")` 这种字符串路径创建对象。
|
||
- 已做双文件运行验证:可以写 `type MyNewClass = class(Unit1.Class1.Class2)` 直接继承单元里的嵌套类路径。
|
||
- 当前环境里 `{$IFDEF ParentClassInUnit}` 为真,可用于探测“继承和构造单元中的类”能力是否可用。
|
||
- `private` / `protected` / `public` 已验证可用;类开头未写可见性时,成员默认按 `public` 处理。
|
||
- 同一个可见性段里后续没有切换关键字的成员,会沿用前一个可见性。
|
||
- 外部访问 `private` / `protected` 字段或方法,会在执行时报对象成员访问错误。
|
||
- 子类可以通过 `self` 访问父类 `protected` 成员;不能通过 `self` 访问父类 `private` 成员。
|
||
- `create` 写成 `private` 或 `protected` 时,对象仍能创建,但 `new` 和 `CreateObject(...)` 不会执行该构造逻辑;构造函数应保持 `public`。
|
||
- 更复杂的多重继承边界和更复杂的对象模型还没有在这篇里完整展开。
|
||
|
||
## 已验证语法
|
||
|
||
最短类骨架:
|
||
|
||
如果你现在只是要“先写出一个类”,先从这个骨架起手;后面的例子再逐步进入字段、工厂函数和声明位置边界。
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
end;
|
||
```
|
||
|
||
字段、方法和 `CreateObject`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
name;
|
||
function SetName(new_name);
|
||
begin
|
||
name := new_name;
|
||
end;
|
||
end;
|
||
|
||
function MakePerson();
|
||
begin
|
||
return CreateObject("Person");
|
||
end;
|
||
```
|
||
|
||
类声明位置:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
obj := CreateObject("MyClass");
|
||
obj.value := 5;
|
||
WriteLn(obj.value);
|
||
|
||
type MyClass = class
|
||
value;
|
||
end;
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `5`
|
||
- 说明在当前解释器里,顶层类声明可以放在松散语句之后
|
||
- 同时也说明 `CreateObject("MyClass")` 可以解析到后面才出现的类声明
|
||
|
||
顶层函数后面也可以继续声明类:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
function MakeObj();
|
||
begin
|
||
return 1;
|
||
end;
|
||
|
||
type MyClass = class
|
||
value;
|
||
end;
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- 上述 `.tsf` 例子可以编译通过
|
||
|
||
类声明位置的反例:
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
// 函数体内部声明类
|
||
function Demo();
|
||
begin
|
||
type InnerClass = class
|
||
value;
|
||
end;
|
||
return 1;
|
||
end;
|
||
|
||
// 松散语句在 type 之后继续出现
|
||
a := 1;
|
||
type MyClass = class
|
||
value;
|
||
end;
|
||
WriteLn(a);
|
||
```
|
||
|
||
已验证结果:
|
||
|
||
- 函数体内部声明类会报 `invalid statement`
|
||
- 在松散语句脚本里,`type MyClass = class ... end;` 之后继续写 `WriteLn(a);` 也会报 `invalid statement`
|
||
|
||
`static` 字段:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type THuman = class
|
||
static
|
||
mCount;
|
||
function create();
|
||
begin
|
||
mCount := (mCount ?: 0) + 1;
|
||
end;
|
||
end;
|
||
begin
|
||
class(THuman).mCount := 100;
|
||
WriteLn(class(THuman).mCount);
|
||
h := new THuman();
|
||
WriteLn(class(THuman).mCount);
|
||
WriteLn(h.mCount);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `class(THuman).mCount := 100` 可以直接写静态字段
|
||
- `class(THuman).mCount` 先输出 `100`
|
||
- 创建对象后,`class(THuman).mCount` 输出 `101`
|
||
- 通过实例读取 `h.mCount` 也输出 `101`
|
||
|
||
同一字段也接受内联声明:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type THuman = class
|
||
static mCount;
|
||
function create();
|
||
begin
|
||
mCount := (mCount ?: 0) + 1;
|
||
end;
|
||
end;
|
||
begin
|
||
class(THuman).mCount := 100;
|
||
h := new THuman();
|
||
WriteLn(class(THuman).mCount);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `static mCount;` 这种内联写法也可以通过
|
||
- 上述例子中的 `class(THuman).mCount` 输出 `101`
|
||
|
||
`const` 成员:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
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;
|
||
begin
|
||
o := new C();
|
||
WriteLn(o.TestConst());
|
||
WriteLn(o.TestConstInParam());
|
||
WriteLn(o.mA);
|
||
WriteLn(class(C).mB);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `12`、`11`、`1`、`11`
|
||
- 说明类里可以定义实例常量和 `static const`
|
||
- 说明实例常量可通过实例读取,`static const` 可通过 `class(C)` 读取
|
||
- 说明成员常量当前也可用于成员函数默认参数
|
||
|
||
`self` 指向当前实例:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Counter = class
|
||
value;
|
||
function create(v);
|
||
begin
|
||
self.value := v;
|
||
end;
|
||
function Inc();
|
||
begin
|
||
self.value := self.value + 1;
|
||
return self.value;
|
||
end;
|
||
end;
|
||
begin
|
||
c := new Counter(10);
|
||
WriteLn(c.Inc());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `c.Inc()` 输出 `11`
|
||
|
||
可见性 `private` / `protected` / `public`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
value;
|
||
private
|
||
secret;
|
||
backup;
|
||
public
|
||
function create();
|
||
begin
|
||
self.secret := 2;
|
||
self.backup := 3;
|
||
end;
|
||
function ReadSecret();
|
||
begin
|
||
return self.secret + self.backup;
|
||
end;
|
||
end;
|
||
begin
|
||
a := new A();
|
||
a.value := 9;
|
||
WriteLn(a.value);
|
||
WriteLn(a.ReadSecret());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 类开头没有显式可见性时,`value` 默认按 `public` 处理,因此 `a.value := 9` 可以直接写。
|
||
- `backup` 跟在 `private` 段后面,没有重新切换关键字,因此仍按 `private` 处理。
|
||
- 上述例子依次输出 `9`、`5`。
|
||
|
||
`protected` 可供子类通过 `self` 访问:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
protected
|
||
value;
|
||
function Hidden();
|
||
begin
|
||
return 88;
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function SetValue(v);
|
||
begin
|
||
self.value := v;
|
||
end;
|
||
function GetValue();
|
||
begin
|
||
return self.value;
|
||
end;
|
||
function TryCall();
|
||
begin
|
||
return self.Hidden();
|
||
end;
|
||
end;
|
||
begin
|
||
b := new B();
|
||
b.SetValue(66);
|
||
WriteLn(b.GetValue());
|
||
WriteLn(b.TryCall());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 子类里可以通过 `self.value` 访问父类 `protected` 字段。
|
||
- 子类里也可以通过 `self.Hidden()` 调用父类 `protected` 方法。
|
||
- 上述例子依次输出 `66`、`88`。
|
||
|
||
`private` 的失败场景,因为是已验证反例,这里用 `text` 展示:
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
// 外部访问 private 字段
|
||
program test;
|
||
type A = class
|
||
private
|
||
secret;
|
||
public
|
||
function create();
|
||
begin
|
||
self.secret := 7;
|
||
end;
|
||
end;
|
||
begin
|
||
a := new A();
|
||
WriteLn(a.secret);
|
||
end.
|
||
|
||
// 子类访问父类 private 字段
|
||
program test;
|
||
type A = class
|
||
private
|
||
secret;
|
||
public
|
||
function ReadSecret();
|
||
begin
|
||
return self.secret;
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
public
|
||
function SetSecret(v);
|
||
begin
|
||
self.secret := v;
|
||
end;
|
||
end;
|
||
begin
|
||
b := new B();
|
||
b.SetSecret(77);
|
||
WriteLn(b.ReadSecret());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 外部访问 `a.secret` 会在执行时报对象成员访问错误。
|
||
- 子类里通过 `self.secret` 访问父类 `private` 字段也会在执行时报错。
|
||
- 同样的规则也已单独用 `private` / `protected` 方法访问做过复核:外部不能调 `private` / `protected` 方法,子类只能调 `protected` 方法,不能调 `private` 方法。
|
||
|
||
`create` 建议保持 `public`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
public
|
||
value;
|
||
private
|
||
function create(v);
|
||
begin
|
||
self.value := v;
|
||
end;
|
||
end;
|
||
begin
|
||
a := new A(111);
|
||
WriteLn(a.value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `a := new A(111)` 可以创建对象。
|
||
- 上述例子里的 `a.value` 输出 `<NIL>`,说明 `private create` 没有执行。
|
||
- 同样的结果已单独用 `protected create`、`CreateObject("A", ...)` 和 `CreateObject(class(A), ...)` 复核;当前解释器里构造函数应保持 `public`。
|
||
|
||
基础 `property`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type MyBox = class
|
||
_value;
|
||
function SetValue(v);
|
||
begin
|
||
if v > 0 then
|
||
_value := v;
|
||
end;
|
||
property Value read _value write SetValue;
|
||
end;
|
||
begin
|
||
b := new MyBox();
|
||
b.Value := 7;
|
||
WriteLn(b.Value);
|
||
b.Value := -1;
|
||
WriteLn(b.Value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `b.Value := 7` 后,`b.Value` 输出 `7`
|
||
- `b.Value := -1` 后,`b.Value` 仍输出 `7`
|
||
|
||
带类型注解的 `property`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type MyBox = class
|
||
_value;
|
||
function SetValue(v);
|
||
begin
|
||
_value := v;
|
||
end;
|
||
property Value: score_value read _value write SetValue;
|
||
end;
|
||
begin
|
||
b := new MyBox();
|
||
b.Value := 9;
|
||
WriteLn(b.Value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `property Value: score_value ...` 这种类型注解写法可以通过
|
||
- 上述例子中的 `b.Value` 输出 `9`
|
||
|
||
索引型 `property`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
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;
|
||
begin
|
||
aa := new A();
|
||
aa.idx(0) := "abc";
|
||
WriteLn(aa.idx(0));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `aa.idx(0) := "abc"` 可以写入索引 property
|
||
- `aa.idx(0)` 输出 `abc`
|
||
|
||
固定 `index` property:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
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;
|
||
begin
|
||
aa := new A();
|
||
aa.idx0 := "abc";
|
||
WriteLn(aa.idx0);
|
||
WriteLn(aa.idx(0));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `aa.idx0 := "abc"` 可以写入固定整数索引 property
|
||
- `aa.idx0` 输出 `abc`
|
||
- `aa.idx(0)` 也输出 `abc`
|
||
|
||
固定字符串索引:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
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;
|
||
begin
|
||
aa := new A();
|
||
aa.school := "math";
|
||
WriteLn(aa.school);
|
||
WriteLn(aa.idx("High school"));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `property school index "High school"` 这种固定字符串索引写法可以通过
|
||
- `aa.school` 输出 `math`
|
||
- `aa.idx("High school")` 也输出 `math`
|
||
|
||
参数化 `property`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type MyDate = class
|
||
_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;
|
||
begin
|
||
d := new MyDate();
|
||
d.DateV(2025, 8) := 10;
|
||
WriteLn(d.DateV());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `d.DateV(2025, 8) := 10` 可以给参数化 property 赋值
|
||
- `d.DateV()` 输出 `20250810`
|
||
|
||
参数化 `property` 的 accessor 方法参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
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): slot_type read getItem write setItem;
|
||
end;
|
||
begin
|
||
aa := new A();
|
||
aa.Item(2) := "x";
|
||
WriteLn(aa.Item(2));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `read getItem` 这种“读方法接同参数个数”的写法可以通过
|
||
- `write setItem` 这种“写方法接参数个数 + 赋值值”的写法可以通过
|
||
- 上述例子中的 `aa.Item(2)` 输出 `x`
|
||
|
||
`new` 关键字:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
end;
|
||
|
||
function MakePerson();
|
||
begin
|
||
return new Person();
|
||
end;
|
||
```
|
||
|
||
通过类类型创建对象:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Person = class
|
||
value;
|
||
end;
|
||
begin
|
||
cls := FindClass("Person");
|
||
obj := CreateObject(cls);
|
||
obj.value := 42;
|
||
WriteLn(obj.value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `CreateObject(cls)` 可以通过类类型创建对象
|
||
- 上述例子中的 `obj.value` 输出 `42`
|
||
|
||
也可以用 `class(Name)` 取得类类型:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Person = class
|
||
value;
|
||
function create(v);
|
||
begin
|
||
value := v;
|
||
end;
|
||
end;
|
||
begin
|
||
cls := class(Person);
|
||
obj := CreateObject(cls, 44);
|
||
WriteLn(obj.value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `class(Person)` 可以拿到 `Person` 的类类型
|
||
- `CreateObject(cls, 44)` 输出 `44`
|
||
|
||
构造参数、默认值和命名参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Person = class
|
||
a;
|
||
b;
|
||
c;
|
||
function create(a, b = 20, c = 30);
|
||
begin
|
||
self.a := a;
|
||
self.b := b;
|
||
self.c := c;
|
||
end;
|
||
end;
|
||
begin
|
||
a := CreateObject("Person", 11);
|
||
WriteLn(a.a);
|
||
WriteLn(a.b);
|
||
WriteLn(a.c);
|
||
b := new Person(22, c:99);
|
||
WriteLn(b.a);
|
||
WriteLn(b.b);
|
||
WriteLn(b.c);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `CreateObject("Person", 11)` 依次输出 `11`、`20`、`30`
|
||
- `new Person(22, c:99)` 依次输出 `22`、`20`、`99`
|
||
- 说明构造函数默认参数和命名参数不只适用于普通函数调用,也适用于对象创建
|
||
|
||
类类型也同样支持默认参数和命名参数:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Person = class
|
||
a;
|
||
b;
|
||
c;
|
||
function create(a, b = 20, c = 30);
|
||
begin
|
||
self.a := a;
|
||
self.b := b;
|
||
self.c := c;
|
||
end;
|
||
end;
|
||
begin
|
||
cls := FindClass("Person");
|
||
obj := CreateObject(cls, 33, c:77);
|
||
WriteLn(obj.a);
|
||
WriteLn(obj.b);
|
||
WriteLn(obj.c);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `CreateObject(cls, 33, c:77)` 依次输出 `33`、`20`、`77`
|
||
- 因此类类型入口下的 `CreateObject(cls, ...)` 也遵循同样的构造参数规则
|
||
|
||
`class function`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type MathBox = class
|
||
class function Add(x, y);
|
||
begin
|
||
return x + y;
|
||
end;
|
||
end;
|
||
begin
|
||
cls1 := class(MathBox);
|
||
WriteLn(cls1.Add(3, 4));
|
||
cls2 := FindClass("MathBox");
|
||
WriteLn(cls2.Add(5, 6));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `class(MathBox).Add(...)` 可以调用类方法
|
||
- `FindClass("MathBox").Add(...)` 可以调用类方法
|
||
- 上述例子依次输出 `7`、`11`
|
||
|
||
`overload` 方法:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type TestClass = class
|
||
function fun(p1, p2); overload;
|
||
begin
|
||
return p1 + p2;
|
||
end;
|
||
function fun(p1); overload;
|
||
begin
|
||
return p1 + 10;
|
||
end;
|
||
end;
|
||
begin
|
||
t := new TestClass();
|
||
WriteLn(t.fun(1, 2));
|
||
WriteLn(t.fun(5));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `3`、`15`
|
||
- 说明同名不同参数个数的方法当前可以共存
|
||
|
||
基础继承:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Animal = class
|
||
function Speak();
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type Dog = class(Animal)
|
||
end;
|
||
begin
|
||
d := new Dog();
|
||
WriteLn(d.Speak());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `type Dog = class(Animal)` 可以继承父类实例方法
|
||
- 上述例子中的 `d.Speak()` 输出 `1`
|
||
|
||
多重继承:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
function Fa();
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type B = class
|
||
function Fb();
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
type C = class(A, B)
|
||
end;
|
||
begin
|
||
obj := new C();
|
||
WriteLn(obj.Fa());
|
||
WriteLn(obj.Fb());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `type C = class(A, B)` 可以同时继承 `A` 和 `B` 的方法
|
||
- 上述例子依次输出 `1`、`2`
|
||
|
||
多重继承下的同名方法优先级:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
function Speak();
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type B = class
|
||
function Speak();
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
type C = class(A, B)
|
||
end;
|
||
begin
|
||
obj := new C();
|
||
WriteLn(obj.Speak());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 当多个父类存在同名方法时,当前例子优先命中第一个父类 `A`
|
||
- 上述例子中的 `obj.Speak()` 输出 `1`
|
||
|
||
基础 `virtual` / `override`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type Animal = class
|
||
function Speak(); virtual;
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type Dog = class(Animal)
|
||
function Speak(); override;
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
begin
|
||
d := new Dog();
|
||
WriteLn(d.Speak());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 父类 `virtual` + 子类 `override` 组合可以通过
|
||
- 上述例子中的 `d.Speak()` 输出 `2`
|
||
|
||
`class(BaseClass, ObjectName).MethodName()`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
function Speak(); virtual;
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
function Speak(); override;
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
begin
|
||
obj := new B();
|
||
WriteLn(obj.Speak());
|
||
WriteLn(class(A, obj).Speak());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `obj.Speak()` 输出 `2`
|
||
- `class(A, obj).Speak()` 会定向调用祖先类方法,因此输出 `1`
|
||
|
||
`FindClass("ClassName", obj)` 强制转型:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type ClassA = class
|
||
function Fuc(); virtual;
|
||
begin
|
||
return 1;
|
||
end;
|
||
end;
|
||
type ClassB = class(ClassA)
|
||
function Fuc(); override;
|
||
begin
|
||
return 2;
|
||
end;
|
||
end;
|
||
begin
|
||
objb := new ClassB();
|
||
obj := FindClass("ClassA", objb);
|
||
WriteLn(obj.Fuc());
|
||
WriteLn(obj is class(ClassA));
|
||
WriteLn(obj is class(ClassB));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `FindClass("ClassA", objb)` 会把 `objb` 转成 `ClassA` 视图
|
||
- 上述例子中的 `obj.Fuc()` 输出 `1`
|
||
- 转型后的 `obj is class(ClassA)` 输出 `1`
|
||
- 转型后的 `obj is class(ClassB)` 输出 `0`
|
||
|
||
`Inherited;`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
function Speak(); virtual;
|
||
begin
|
||
WriteLn(1);
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
function Speak(); override;
|
||
begin
|
||
Inherited;
|
||
WriteLn(2);
|
||
end;
|
||
end;
|
||
begin
|
||
obj := new B();
|
||
obj.Speak();
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `Inherited;` 会先执行父类同名同参数方法
|
||
- 上述例子依次输出 `1`、`2`
|
||
|
||
`Inherited MethodName(...)`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type A = class
|
||
function BaseValue(x);
|
||
begin
|
||
WriteLn(x + 1);
|
||
end;
|
||
end;
|
||
type B = class(A)
|
||
function Run();
|
||
begin
|
||
Inherited BaseValue(5);
|
||
WriteLn(9);
|
||
end;
|
||
end;
|
||
begin
|
||
obj := new B();
|
||
obj.Run();
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `Inherited BaseValue(5)` 可以显式调用父类指定方法
|
||
- 上述例子依次输出 `6`、`9`
|
||
|
||
析构函数 `destroy`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type THuman = class
|
||
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;
|
||
begin
|
||
h := new THuman();
|
||
WriteLn(class(THuman).GetCount());
|
||
h := nil;
|
||
WriteLn(class(THuman).GetCount());
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 创建对象后,`class(THuman).GetCount()` 输出 `1`
|
||
- `h := nil` 时会触发 `destroy()`,中途输出 `0`
|
||
- 释放后再次读取 `class(THuman).GetCount()` 也输出 `0`
|
||
|
||
`self(0)` / `self(1)`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
type BaseBox = class
|
||
function MakeOwner();
|
||
begin
|
||
return self(1);
|
||
end;
|
||
function MakeBase();
|
||
begin
|
||
return self(0);
|
||
end;
|
||
end;
|
||
type ChildBox = class(BaseBox)
|
||
end;
|
||
begin
|
||
c := new ChildBox();
|
||
a := c.MakeOwner();
|
||
b := c.MakeBase();
|
||
WriteLn(a is class(ChildBox));
|
||
WriteLn(b is class(ChildBox));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `self(1)` 返回的对象在这个例子里是 `ChildBox`,因此输出 `1`
|
||
- `self(0)` 返回的对象在这个例子里不是 `ChildBox`,因此输出 `0`
|
||
|
||
`unit` 中的嵌套类路径创建,因为依赖多文件环境,这里用 `text` 展示:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// Unit1.tsf
|
||
unit Unit1;
|
||
interface
|
||
type Class1 = class
|
||
type Class2 = class
|
||
value;
|
||
end;
|
||
end;
|
||
implementation
|
||
end.
|
||
|
||
// main.tsl
|
||
program test;
|
||
begin
|
||
obj := CreateObject("Unit1.Class1.Class2");
|
||
obj.value := 42;
|
||
WriteLn(obj.value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `CreateObject("Unit1.Class1.Class2")` 可以创建对象
|
||
- 上述例子中的 `obj.value` 输出 `42`
|
||
|
||
继承单元中的嵌套类路径,因为依赖多文件环境,这里也用 `text` 展示:
|
||
|
||
代码块身份:配置片段 / 概念骨架
|
||
代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。
|
||
|
||
```text
|
||
// Unit1.tsf
|
||
unit Unit1;
|
||
interface
|
||
type Class1 = class
|
||
type Class2 = class
|
||
value;
|
||
end;
|
||
end;
|
||
implementation
|
||
end.
|
||
|
||
// main.tsl
|
||
program test;
|
||
type MyNewClass = class(Unit1.Class1.Class2)
|
||
end;
|
||
begin
|
||
obj := CreateObject("MyNewClass");
|
||
obj.value := 42;
|
||
WriteLn(obj.value);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- `type MyNewClass = class(Unit1.Class1.Class2)` 可以通过并继承该路径类
|
||
- 上述例子中的 `obj.value` 输出 `42`
|
||
|
||
能力探测:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
{$IFDEF ParentClassInUnit}
|
||
WriteLn(1);
|
||
{$ELSE}
|
||
WriteLn(0);
|
||
{$ENDIF}
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 当前环境里 `{$IFDEF ParentClassInUnit}` 输出 `1`
|
||
|
||
## 最小可编译示例
|
||
|
||
如果你只是想先起一个最短类骨架,直接复用本页开头的“最短类骨架”。
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
type Person = class
|
||
end;
|
||
```
|
||
|
||
## 常见误写
|
||
|
||
- 以下误写中,有代码块的条目都已经单独验证;没有单独代码块展开的文字条目,属于已知语义陷阱或错误泛化,不要把它们当成可写事实。
|
||
- 把类写成裸 `class Person`。
|
||
- 还没建立类定义就直接写 `CreateObject("Person")` 或 `new Person()`。
|
||
- 把 `MathBox.Add(...)` 这种裸类名调用直接当成当前解释器已验证的类方法调用写法。
|
||
- 把 `THuman.mCount` 这种裸类名静态字段访问直接当成当前解释器已验证事实。
|
||
- 把 `self()` 当成当前解释器已验证的工厂式写法。
|
||
- 把 `private` / `protected create` 当成当前解释器一定会自动执行的构造函数。
|
||
- 在子类里把父类 `private` 成员当成 `protected` 成员来访问。
|
||
- 把带命名空间的 `new a.b.c` 直接当成当前解释器已支持事实。
|
||
- 把 `class(Unit1.Class1.Class2)` 这种表达式写法直接当成当前解释器已验证事实。
|
||
- 以为类声明既然能放在松散语句后面,就还能在 `type` 后面继续回到松散语句模式。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```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);
|
||
```
|
||
|
||
上面这种裸类名调用在当前解释器里没有通过;当前已验证可用的是先拿到类类型,再用 `class(MathBox).Add(...)` 或 `FindClass("MathBox").Add(...)` 调用。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
THuman.mCount := 7;
|
||
```
|
||
|
||
上面这种裸类名静态字段访问在当前解释器里没有通过;当前已验证可用的是 `class(THuman).mCount`。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
self();
|
||
```
|
||
|
||
上面这种无参工厂式 `self()` 在当前解释器里没有通过;当前只把 `self` 实例引用,以及 `self(0)` / `self(1)` 写成已验证事实。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
a := 1;
|
||
type MyClass = class
|
||
value;
|
||
end;
|
||
WriteLn(a);
|
||
```
|
||
|
||
上面这种写法在当前解释器里没有通过,会报 `invalid statement`。当前只把“松散语句后面可以接顶层类声明”写成已验证事实,不把它泛化成“`type` 之后还能继续写松散语句”。
|
||
|
||
## 跳转指引
|
||
|
||
- 回看 unit 与作用域:见 [10_units_and_scope.md](10_units_and_scope.md)
|
||
- 最后统一扫坑:见 [12_pitfalls.md](12_pitfalls.md)
|
||
- 继续看对象运行时和更深对象能力:见 [23_object_runtime_and_introspection.md](23_object_runtime_and_introspection.md) 和 [32_object_overloads_and_iteration.md](32_object_overloads_and_iteration.md)
|