# TSL 对象重载与迭代

这一篇只讲对象直接参与语言级操作的文档能力：基础算符重载、`[]` 重载、`for in` 重载，以及 `mrows` / `mcols` / `msize` 这类矩阵关键字重载。

## 本篇职责

<!-- section-id: syntax-23-001 -->

回答“当类不只是普通对象，而要直接参与 `obj + x`、`obj[index]`、`for v in obj`、`mrows(obj)` 这类语言级操作时，支持哪些文档明确写法”。

## 核心规则

<!-- section-id: syntax-23-002 -->

- 对象二元算符重载的最小可靠形态是成员方法 `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` 的重载本页已给出可照写形态，照本页示例写即可。

## 可直接照写示例

<!-- section-id: syntax-23-003 -->

### 二元算符重载

<!-- section-id: syntax-23-004 -->

<!-- tags: 让对象支持加号, 自定义加减, 对象相加, 运算符重载 -->

代码块身份：可直接照写示例

```tsl
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 暴露（命名风格本身不属于本页事实）

代码块身份：输出片段

```text
20
0
1
1
```

### `[]` 重载：`operator[]` / `operator[1]`

<!-- section-id: syntax-23-005 -->

<!-- tags: 中括号取值, 方括号访问, 对象当数组用, 下标读写 -->

代码块身份：可直接照写示例

```tsl
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]`

<!-- section-id: syntax-23-006 -->

<!-- tags: 中括号读写分离, 取值和赋值签名 -->

读取签名也可以写成 `function operator[0](index, s1);`（与上一段 `operator[]` 等价的另一组写法）：

代码块身份：可直接照写示例

```tsl
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` 重载

<!-- section-id: syntax-23-007 -->

<!-- tags: 让对象可遍历, 自定义迭代, 支持 for in -->

代码块身份：可直接照写示例

```tsl
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` 重载（带参形态，支持下标列表）

<!-- section-id: syntax-23-008 -->

<!-- tags: 对象当矩阵, 自定义行列数, 带下标查询 -->

代码块身份：可直接照写示例

```tsl
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)` 也可继续取得列下标列表

### `++` / `+=` / `--` / `-=` 自增自减重载

<!-- section-id: syntax-23-009 -->

<!-- tags: 自增自减, 加等于, 对象累加 -->

代码块身份：可直接照写示例

```tsl
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`

`--` 与 `-=` 与之对称：

代码块身份：可直接照写示例

```tsl
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`

<!-- section-id: syntax-23-010 -->

<!-- tags: 重载具名函数, 让内置函数认识我的对象 -->

除了符号算符，`operator` 还能重载具名的全局二进制函数（如 `DateToStr`、`TryStrToInt` 等）。定义写成 `[class] function operator funcName(...)`：`class` 关键字可选，加上表示类方法，不加表示成员函数。

成员函数重载（参数比原函数少 1 个，用第一个参数的对象实例调用）：

代码块身份：可直接照写示例

```tsl
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`，参数与原函数一致）：

代码块身份：可直接照写示例

```tsl
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)` 作为类方法接管调用，参数个数与原二进制函数一致

类内用 `::` 调同名全局函数（避免重载递归）：

代码块身份：可直接照写示例

```tsl
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`

<!-- section-id: syntax-23-011 -->

<!-- tags: 对象支持矩阵遍历, 自定义单元访问 -->

重载 `::`（二维遍历）或 `:.`（深度遍历）后，对象就能像矩阵一样被 `obj::begin ... end` 遍历。遍历体里用到的 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex(n)` 也各自重载，返回当前单元的值、行下标、列下标、维度数和第 `n` 维下标。`operator ::(flag)` 的 `flag` 为 `0` 表示第一次循环、`1` 表示后续循环，返回 `0` 或 `nil` 结束遍历、返回非零数字继续：

代码块身份：可直接照写示例

```tsl
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` 重载（无参形态，只取数量且免 `::`）

<!-- section-id: syntax-23-012 -->

<!-- tags: 只取数量, 无参形态, 免遍历取行列数 -->

`msize` / `mrows` / `mcols` 这类关键字函数也能重载，形态同二进制函数重载 `[class] function operator KeyWord(...)`，但**关键字重载不需要 `::` 指定全局**：

代码块身份：可直接照写示例

```tsl
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` 的列数
- 关键字函数重载与二进制函数重载写法一致，但类内调用同名关键字函数不需要 `::` 前缀

## 本页不生成的范围

<!-- section-id: syntax-23-013 -->

- 多级 `[]` 下标重载
- 右侧算术如 `value + obj`

这些名称只作为边界提示，不作为本页可生成模板。

## 禁止项

<!-- section-id: syntax-23-014 -->

- 不要从本页 `operator` 示例外推未写入文档的重载族。
- 重载 `::` / `:.` 遍历时，不要漏掉配套的 `mcell` / `mrow` / `mcol` / `mIndexCount` / `mIndex` 重载，否则遍历体会报 `override function not found`。
- 不要把多级 `[]` 下标重载或 `value + obj` 这类右侧算术写成文档事实。
- 不要在本页发明普通类语法；基础对象模型事实见 [08_objects_and_classes.md](08_objects_and_classes.md)。
