16 KiB
TSL 对象重载与迭代
这一篇只讲对象直接参与语言级操作的文档能力:基础算符重载、[] 重载、for in 重载,以及 mrows / mcols / msize 这类矩阵关键字重载。
本篇职责
回答“当类不只是普通对象,而要直接参与 obj + x、obj[index]、for v in obj、mrows(obj) 这类语言级操作时,支持哪些文档明确写法”。
智能体对象重载/迭代判断流程
- 先判断要重载二元算符、下标、
for in,还是矩阵尺寸函数。 - 对象重载只照本页明确的
operator签名写,不要从未写入文档资料扩展未知重载。 - 普通对象模型以
08_objects_and_classes.md的事实为准,不要在重载页发明类基础语法。 - 未列入本页主干的重载族不要写成语法事实。
- 没有对应代码块时不要发明对象重载/迭代写法。
核心规则
- 对象二元算符重载的最小可靠形态是成员方法
function operator + (other);这一类写法。 - 比较算符可写成
function operator < (other, is_left);,用is_left区分对象在左边还是右边。 - 对象
[]读取有两种文档明确写法:function operator[](index);和function operator[0](index, s1);。 - 对象
[]写入的文档明确写法是function operator[1](index, v);。 function operator for(flag);可以重载for in。- 在
operator for(flag)里,flag .& 2可用来区分“一个循环变量”还是“两个循环变量”,flag .& 1可用来区分“第一次进入”还是“继续迭代”。 mrows/mcols/msize可以在类里先声明function operator mrows(n);这类签名,再在类外实现function operator ClassName.mrows(n);。- 可用形态包括
mrows(obj)、mcols(obj)、msize(obj)这类关键字调用,以及obj.mcols(1)这类对象方法式调用。 function operator++(v);和function operator += (v);也可用。- 不要把未写入文档资料里的裸
function operator;/function operator1;直接当成语法事实。::/:./mcell/mrow/mcol/mIndexCount/mIndex的重载本页已给出可照写形态,照本页示例写即可。
可直接照写示例
二元算符重载
代码块身份:可直接照写示例
c1 := new Complex();
c1.RealPart := 10;
c1.ImaginaryPart := 100;
c2 := c1 + 10;
writeLn(c2.RealPart);
writeLn(c1 < 5);
writeLn(c1 < 300);
writeLn(5 < c1);
type Complex = class
public
property RealPart read real_part_ write real_part_;
property ImaginaryPart read imaginary_part_ write imaginary_part_;
function operator + (other);
begin
sum := new Complex();
if ifNumber(other) then
begin
sum.RealPart := real_part_ + other;
end
else
begin
sum.RealPart := real_part_ + other.RealPart;
sum.ImaginaryPart := imaginary_part_ + other.ImaginaryPart;
end
return sum;
end;
function operator < (other, is_left);
begin
if ifNumber(other) then
begin
less := real_part_ < other;
end
else
begin
less := (real_part_ ^ 2 + imaginary_part_ ^ 2) < other.RealPart ^ 2 + other.ImaginaryPart ^ 2;
end
if not is_left then less := not less;
return less;
end;
private
real_part_;
imaginary_part_;
end;
结果说明:
- 依次输出
20、0、1、1 - 说明
obj + value可以通过成员operator +接管 - 说明带
is_left的比较算符可以同时处理obj < value和value < obj - 本例私有成员是
real_part_/imaginary_part_,对外通过 property 暴露(命名风格本身不属于本页事实)
代码块身份:输出片段
20
0
1
1
[] 重载:operator[] / operator[1]
代码块身份:可直接照写示例
t := array(1, 2, 3, 4, 5);
b := new IndexableBox(t);
writeLn(b[2]);
b[3] := 999;
writeLn(b.data[3]);
type IndexableBox = class
public
data;
function create(v);
begin
data := v;
end;
function operator[](index);
begin
return data[index];
end;
function operator[1](index, v);
begin
data[index] := v;
end;
end;
结果说明:
- 依次输出
3、999 - 说明
operator[]和operator[1]可以完成单层下标读取和写入
[] 重载:operator[0] / operator[1]
读取签名也可以写成 function operator[0](index, s1);(与上一段 operator[] 等价的另一组写法):
代码块身份:可直接照写示例
t := array(1, 2, 3, 4, 5);
b := new IndexableBox(t);
writeLn(b[2]);
b[3] := 999;
writeLn(b.data[3]);
type IndexableBox = class
public
data;
function create(v);
begin
data := v;
end;
function operator[0](index, s1);
begin
return data[index];
end;
function operator[1](index, v);
begin
data[index] := v;
end;
end;
结果说明:
- 依次输出
3、999 - 说明
operator[0]/operator[1]这组写法在单层下标场景同样可用
for in 重载
代码块身份:可直接照写示例
box := new Box(array("A", "B", "C"));
for v in box do
writeLn(v);
for i, v in box do
writeLn(i$":"$v);
type Box = class
public
data;
findex;
function create(v);
begin
data := v;
end;
function operator for(flag);
begin
use_pair := flag .& 2;
again := flag .& 1;
if not again then
begin
findex := 0;
end
else if findex < length(data) - 1 then
begin
findex++;
end
else
begin
return nil;
end
if use_pair then
return array(findex, data[findex]);
return data[findex];
end;
end;
结果说明:
- 单变量循环依次输出
A、B、C - 双变量循环依次输出
0:A、1:B、2:C - 说明
operator for(flag)可以重载for in - 也说明同一个对象可以按返回值形态同时支持“单变量遍历”和“索引 + 值遍历”
mrows / mcols / msize 重载(带参形态,支持下标列表)
代码块身份:可直接照写示例
g := new GridWrap();
writeLn(mrows(g));
writeLn(mcols(g));
sz := msize(g);
writeLn(sz[0]);
writeLn(sz[1]);
cols := g.mcols(1);
writeLn(cols[0]);
writeLn(cols[1]);
writeLn(cols[2]);
type GridWrap = class
public
data;
function create();
begin
data := array(
("A": 1, "B": 2, "C": 3),
("A": 4, "B": 5, "C": 6)
);
end;
function operator mrows(n);
function operator mcols(n);
function operator msize(n);
end;
function operator GridWrap.mrows(n);
begin
_n := ifNil(n) ? 0 : n;
return mrows(data, _n);
end;
function operator GridWrap.mcols(n);
begin
_n := ifNil(n) ? 0 : n;
return mcols(data, _n);
end;
function operator GridWrap.msize(n);
begin
_n := ifNil(n) ? 0 : n;
return msize(data, _n);
end;
结果说明:
- 依次输出
2、3、2、3、A、B、C - 说明
mrows(obj)、mcols(obj)、msize(obj)都可由对象重载接管 - 说明对象方法式调用
obj.mcols(1)也可继续取得列下标列表
++ / += / -- / -= 自增自减重载
代码块身份:可直接照写示例
counter := new Counter(10);
++counter;
writeLn(counter.data);
c := counter++;
writeLn(c.data);
writeLn(counter.data);
counter += 5;
writeLn(counter.data);
type Counter = class
public
data;
function create(v);
begin
data := v;
end;
function operator++(v);
begin
if v = 0 then
begin
r := new Counter();
r.data := data;
r.data++;
return r;
end
else
data++;
end;
function operator += (v);
begin
data += v;
end;
end;
结果说明:
- 依次输出
11、11、12、17 - 前置
++counter走v非0分支,直接原地修改对象状态 - 后置
counter++走v = 0分支:分支构造并返回递增后的新对象,运行时用它更新counter本身,而后置表达式的取值是递增前的原对象(所以c.data是11、counter.data是12) - 说明
operator += (v)可以接管counter += 5
-- 与 -= 与之对称:
代码块身份:可直接照写示例
counter := new Counter(10);
counter -= 3;
writeLn(counter.data);
type Counter = class
public
data;
function create(v);
begin
data := v;
end;
function operator--(v);
begin
if v = 0 then
begin
r := new Counter();
r.data := data;
r.data--;
return r;
end
else
data--;
end;
function operator -= (v);
begin
data -= v;
end;
end;
结果说明:
- 输出
7 - 说明
operator -= (v)可以接管counter -= 3 operator--(v)与operator++(v)结构对称:v = 0(后置)分支构造并返回递减后的新对象供运行时更新变量本身,后置表达式的取值是递减前的原对象;v非0(前置)时原地递减
二进制函数重载:operator funcName
除了符号算符,operator 还能重载具名的全局二进制函数(如 DateToStr、TryStrToInt 等)。定义写成 [class] function operator funcName(...):class 关键字可选,加上表示类方法,不加表示成员函数。
成员函数重载(参数比原函数少 1 个,用第一个参数的对象实例调用):
代码块身份:可直接照写示例
d := new IntDate(20240329);
writeLn(DateToStr(d));
type IntDate = class
public
value;
function create(v);
begin
value := v;
end;
function operator DateToStr();
begin
v := IntToDate(value);
return DateToStr(v);
end;
end;
结果说明:
- 输出
2024-03-29 - 说明
DateToStr(d)被对象的成员operator DateToStr接管 - 成员函数重载时参数个数比原二进制函数少 1 个,第一个实参(对象本身)用于定位方法
类方法重载(class function,参数与原函数一致):
代码块身份:可直接照写示例
d := new IntDate2(20240329);
writeLn(DateToStr(d));
type IntDate2 = class
public
value;
function create(v);
begin
value := v;
end;
class function operator DateToStr(t);
begin
t := ifObj(t) ? t.value : t;
return DateToStr(IntToDate(t));
end;
end;
结果说明:
- 输出
2024-03-29 - 说明
class function operator DateToStr(t)作为类方法接管调用,参数个数与原二进制函数一致
类内用 :: 调同名全局函数(避免重载递归):
代码块身份:可直接照写示例
c := new ClassA();
c.value := "314";
ret := TryStrToInt(c, msg);
writeLn(tostn(array(ret, msg)));
type ClassA = class
public
value;
function operator TryStrToInt(msg);
begin
return ::TryStrToInt(value, msg);
end;
end;
结果说明:
- 输出
array(1,314):ret为1(转换成功),出参msg为314 - 重载的分派按实参类型决定:本类对象走重载版本,非对象实参(如
value是字符串)走全局版本 - 因此当被重载的实参已经不是本类对象(如上面
DateToStr两例把对象拆成基础类型再调用)时,不加::也不会递归;只有当传给同名函数的实参仍是本类对象时才会递归回自己,这时用::前缀强制指定全局版本 - 重载函数支持通过参数传出返回值(
msg作为出参被赋值)
:: / :. 遍历重载与 mcell / mrow / mcol / mIndexCount / mIndex
重载 ::(二维遍历)或 :.(深度遍历)后,对象就能像矩阵一样被 obj::begin ... end 遍历。遍历体里用到的 mcell / mrow / mcol / mIndexCount / mIndex(n) 也各自重载,返回当前单元的值、行下标、列下标、维度数和第 n 维下标。operator ::(flag) 的 flag 为 0 表示第一次循环、1 表示后续循环,返回 0 或 nil 结束遍历、返回非零数字继续:
代码块身份:可直接照写示例
t := array("A": 0 -> 3, "B": 10 -> 2, "C": 20 -> 21);
traversable := new TraversableMatrix(t);
traversable::begin
echo "mcell:", mcell, " mrow:", mrow, " mcol:", mcol, " mIndexCount:", mIndexCount, "\r\n";
end
type TraversableMatrix = class
public
data;
Rdata;
findex;
lengtD;
function create(v);
begin
data := v;
end;
function operator ::(flag);
begin
if not flag then
begin
Rdata := array();
k := 0;
data::begin
Rdata[k] := array(mcell, mIndexCount, mrow, mcol);
if mIndexCount > 2 then for i := 2 to mIndexCount - 1 do Rdata[k, i + 2] := mIndex(i);
k++;
end
lengtD := length(Rdata);
findex := 0;
end
else if findex < lengtD - 1 then findex++;
else return nil;
return 1;
end;
function operator mcell();
begin
return Rdata[findex][0];
end;
function operator mIndexCount();
begin
return Rdata[findex][1];
end;
function operator mrow();
begin
return Rdata[findex][2];
end;
function operator mcol();
begin
return Rdata[findex][3];
end;
function operator mIndex(n);
begin
if n < Rdata[findex][1] then
return Rdata[findex][n + 2];
else raise "指定的维度超出最大维度数";
end;
end;
结果说明:
- 逐行输出每个单元的
mcell/mrow/mcol/mIndexCount,遍历顺序与被代理的data一致 operator ::(flag)里flag=0时初始化把data的遍历结果缓存进Rdata,之后每次推进findex- 返回
1表示继续、返回nil表示结束 :.(深度遍历)重载方式与::相同,把内部data::begin ... end换成data:.begin ... end即可- 遍历体里用到的
mcell/mrow/mcol/mIndexCount/mIndex必须各自重载,否则报override function not found
mrows / mcols / msize 重载(无参形态,只取数量且免 ::)
msize / mrows / mcols 这类关键字函数也能重载,形态同二进制函数重载 [class] function operator KeyWord(...),但关键字重载不需要 :: 指定全局:
代码块身份:可直接照写示例
grid := new GridData();
writeLn(mcols(grid));
type GridData = class
public
fa;
function create();
begin
fa := array(("A": 1, "B": 2, "C": 3), ("A": 5, "B": 5, "C": 5));
end;
function operator mcols();
begin
return mcols(fa);
end;
end;
结果说明:
- 输出
3:mcols(grid)被对象的operator mcols()接管,返回内部fa的列数 - 关键字函数重载与二进制函数重载写法一致,但类内调用同名关键字函数不需要
::前缀
本页不生成的范围
- 多级
[]下标重载 - 右侧算术如
value + obj
这些名称只作为边界提示,不作为本页可生成模板。
禁止项
- 不要从本页
operator示例外推未写入文档的重载族。 - 重载
::/:.遍历时,不要漏掉配套的mcell/mrow/mcol/mIndexCount/mIndex重载,否则遍历体会报override function not found。 - 不要把多级
[]下标重载或value + obj这类右侧算术写成文档事实。 - 不要在本页发明普通类语法;基础对象模型事实见 08_objects_and_classes.md。