playbook/docs/tsl/syntax_book/08_new_generation.md

57 KiB
Raw Blame History

08 高级语言(新一代)

本章汇总新一代语法与相关机制。

目录

高级语言(新一代)

复数

形如 a+bia、b 均为实数的数为复数其中a 被称为实部b 被称为虚部i 为虚数单位。

复数通常用 z 表示,即 z=a+bi

当 z 的虚部 b 0 时,则 z 为实数;

当 z 的虚部 b≠0 时,实部 a 0 时,常称 z 为纯虚数。

TSL 语言中支持复数的表达,虚数单位用 j 表示,记复数 z 为 a+bj,也支持复数的运算以及基础函数的应用。

目前仅新一代客户端与新一代服务器支持复数的相关功能。

定义

在 TSL 语言中,虚数单位用 j 表示,记复数 z 为 a+bj。

通过常量定义方式或函数 complex(a, b) 可以生成复数及复数矩阵。

z := 4 + 3j;
z := complex(4, 3);

即表示 z 是一个实部为 4虚部为 3 的复数。

复数在客户端中的显示如下:

复数矩阵示例:

z := complex(array((1, 2, 3), (4, 5, 6)), array((-1, -2, -3), (1, 2, 3)));

生成一个 2*3 的矩阵,显示如下:

也可借助矩阵初始化函数:

z := complex(ones(2, 3), ones(2, 3));

生成一个 2*3 的实部与虚部都为 1 的复数矩阵,显示如下:

随机矩阵:

z := complex(rand(3, 2), rand(3, 2));

数据类型

在 TSL 语言中, datatype(v)可以判断任意数据的数据类型,而复数的数据类型对应的是分类 41。也可以通过 ifcomplex(z)进行判断是否是复数。

注意,虽然实数也属于复数,但是 ifcomplex 会判定实数(没有用 j 表示的数)为假,而 datatype 也同理。

例如:

z := 4 + 3j;
return datatype(z);

返回整型 41。

在解释器中直接运行如下:

复数运算

支持复数的基础运算,如四则运算、矩阵运算、集合运算、赋值运算等,具体可查看列表。

在使用上与实数一致,例如,有复数 z1 与 z2分别如下

z1 := 4 + 3j;
z2 := 5 + 12j;

加法:

return z1 + z2;

返回9.0+15.0j

减法:

return z2 - z1;

返回1.0+9.0j

乘法:

return z1 * z2;

返回:-16.0+63.0j

除法:

return z2 / z1;

返回2.24+1.32j

复数支持的算符列表

基础运算符(与实数一致):

运算符 说明 示例
+ 1+2j + (2+3j)
- 减,负号 2+3j - (3+1j)
* (2+3j) * (3+1j)
/ (2+3j) / (3+1j)
\ 左除 (3+1j) \\ (2+3j)
~ 求对数 (8+6j) ~ (3+1j)
^ 求幂 (3+1j)^2
! 求倒数 !(3+1j)
++ 自增 (a := 3+1j, a++, a)
-- 自减 (a := 3+1j, a--, a)
= 等于 (0+2j) = 2j
<> 不等 (0+2j) <> 2j
$ 字符串转换与拼接 (3+2j)$"A"
like 相似判定 3.55+4.16j like 3.55

赋值运算符:

运算符 说明 示例
+= 加等于 (a := 3+1j, a += 2+3j, a)
-= 减等于 (a := 3+1j, a -= 2+3j, a)
*= 乘等于 (a := 3+1j, a *= 2+3j, a)
/= 除等于 (a := 2+3j, a /= 3+1j, a)
\= 左除等于 (a := 3+1j, a \\= 2+3j, a)
^= 幂等于 (a := 3+1j, a ^= 2, a)
~= 对数等于 (a := 8+6j, a ~= 3+1j, a)

矩阵与集合相关的运算符与示例见 06 扩展语法(矩阵/集合)章节。

相关模型

复数生成
Complex

范例

范例 01生成一个复数

return complex(3, 4);

3.0+4.0j

范例 02生成一个复数矩阵

return complex(array(1, 2, 3), 5.5); // 一维复数array
return complex(array((1, 2), (3, 4)), array((1, 2), (3, 4))); // 二维复数array
return omplex(fmarray[1, 2, 3], 5.5); // 一维复数FMarray
return complex(fmarray[[1, 2], [3, 4]], fmarray[[1, 2], [3, 4]]); // 二维复数FMarray
// 生成随机复数矩阵
return complex(rand(3), rand(5));
复数的虚部
Imag

范例

范例 01

z := 4 + 3j;
z1 := 4;
z2 := 3j;
echo imag(z);
echo imag(z1);
echo imag(z2);

打印结果为:

3

0

3

范例 02数组的应用

z := 4 + 3j;
z1 := 4;
z2 := 3j;
t := array(z, z1, z2);
return imag(t);

返回:array(3.0,0.0,3.0)

共轭复数
conj

范例

范例 01

z := 4 + 3j;
return conj(z); // 复数z的共轭复数

返回:4-3j 范例 02数组的应用

t := array(4 + 3j, 4, 3j);return conj(t);

返回:array(4.0-3.0j,4.0,0.0-3.0j)

复数的判定
IfComplex

范例

// 当参数为复数类型变量时返回true。
return IfComplex(1 + 2j); // 返回1
return IfComplex(1); // 返回0
// 判断数组中的元素时通过N控制判定的单位
return ifComplex(array(1, 1 + 2j), 1); // 对数组中的每个元素进行判定返回array(0,1)
基础函数对复数的支持

除了支持复数特定的功能模型外,已有的部分基础函数也支持复数的运算。

比如 abs 函数可以获取复数的模,如 abs(4+3j)的结果为 5。

Real 函数可以获取复数的实部,如 real(4+3j)的结果为 4。

这类函数也支持复数数组操作,包括 Array 及 FMArray

如 Real(array(1+2j,2.5+3j))的结果为 array(1.0,2.5)。

当个别函数不支持数组操作时,也可通过循环遍历运算符快速实现,此种方式只适合 Array 数组,不适合 FMArray

A := array(1 + 2j, 2.5 + 3j);
A: := real(mcell);
return A;

返回的结果为array(1.0,2.5)

复数支持的常用函数列表
No 函数名 功能 举例
1 Complex 构建复数或复数矩阵 Complex(1,2)返回1+2j
2 IfComplex 判断是否为复数 IfComplex(1+2j)返回1
3 Datatype 判断数据类型 复数的编号为41 Datatype(1+2j)返回41
4 real 取实部 如real(2.5+3.2j)返回实数2.5
5 Imag 取虚部 如Imag(2.5+3.2j)返回实数3.2
6 Conj 共轭复数 如Conj (2.5+3.2j)返回2.5-3.2j
7 Abs 取模 如Abs(3+4j)返回复数的模5
8 Minit 初始化FMArray 如Minit(3,1+2j)
9 Minitdiag 对角矩阵初始化FMArray 如MinitDiag(3,3,1+0j);
10 Sqr 求平方 如Sqr(3+1j)
11 Sqrt 求平方根 如sqrt(5+12j)
12 dupValue 复制值 如dupValue(2+1j)
13 Integer 强制转换为整数 如Integer(3.33+2.5j)结果为3
14 Int64 对实部进行取整 int64(2.5+1.6j)返回2
15 log10 以10为底的对数 如log10(2+1j)
16 Log2 以2为底的对数 如log2(2+1j)
17 LogN 以指定N为底的对数 如LogN(3,2+1j)
18 Int 将实部与虚部保留整数部分 int(2.5+1.6j) 返回2.0+1.0j
19 Frac 对实部与虚部进取小数部分 Frac(6.4-3.2j)返回0.4-0.2j
20 Ceil 对实部与虚部进行向上取整 Ceil(6.1+3.5j)结果为7.0+4.0j
21 Ceil32 对实部与虚部进行向上取整 Ceil32(6.1+3.5j)结果为7.0+4.0j
22 Ceil64 对实部与虚部进行向上取整 Ceil64(6.1+3.5j) 结果为7.0+4.0j
23 Floor 对实部与虚部进行向下取整 Floor(6.1+3.5j)结果为6.0+3.0j
24 Floor32 对实部与虚部进行向下取整 Floor32(6.1+3.5j)结果为6.0+3.0j
25 Floor64 对实部与虚部进行向下取整 Floor64(6.1+3.5j)结果为6.0+3.0j
26 Trunc 对实部与虚部进行近0取整 Trunc(6.1-3.2j)结果为6.0-3.0j
27 Trunc32 对实部与虚部进行近0取整 Trunc32(6.1-3.2j)结果为6.0-3.0j
28 Trunc64 对实部与虚部进行近0取整 Trunc64(6.1-3.2j)结果为6.0-3.0j
精度相关函数,对实部与虚部分别进行处理
29 FloatN 四舍五入 FloatN(2145.3456-1234.3446j,2) 返回2145.35-1234.34j
30 RoundTo5 .5处理 RoundTo5(2.45-21.05j) 返回2.5-21.0j
31 RoundTo 四舍五入,银行家算法 roundto(123.3446-123.3446j,-2) 返回123.34-123.34j
32 SimpleRoundTo 四舍五入 simpleroundto(2154.3456-1246.3456j,2) 返回2200.0-1200.0j
33 Round 四舍五入后输出整数,银行家算法 round(2145.3456-1234.3456j) 返回2145.0-1234.0j
34 round32 四舍五入后输出整数,银行家算法 round32(6.5+3.5j) 返回6.0+4.0j
35 round64 四舍五入后输出整数,银行家算法 Round64(6.5+3.5j) 返回6.0+4.0j
36 simpleround 四舍五入后输出整数 simpleround(6.5+3.5j) 返回7.0+4.0j
37 simpleround32 四舍五入后输出整数 simpleround32(6.5+3.5j) 返回7.0+4.0j
38 simpleround64 四舍五入后输出整数 Simpleround64(6.5+3.5j) 返回7.0+4.0j
其它基础函数
39 IsZero 在指定精度下是否等于0可用于判断两个值是否相等 IsZero(0.000001+0.0001j,0.00001)结果为0
40 ToStn 转换成STN字符串 如tostn(2.2+3.5j)返回字符串的” 2.2+3.5j”
41 Stn 将串转成任意类型 如stn(“1+2j”)返回复数1.0+2.0j
42 Format 将数值转换成指定格式字符串 如Format("%.3f",2.55+4.1j)返回字符串” 2.550+4.100j”
43 DivValue 除运算 如DivValue(2+3j,3+4j)等同于(2+3j)/( 3+4j)
44 Eval 执行表达式 如eval(&"2+3.22+3.14j")返回5.22+3.14j
45 call 调用函数 如call("abs",3-4j) 返回5
46 CallInarray 调用函数 如callInarray("abs",array(3-4j))返回5
支持复数矩阵的函数列表

除基础函数支持对复数矩阵的操作外,如统计函数、分解函数等也支持复数矩阵的运算,具体列表如下:

No 函数名 功能 与实数处理的差异
1 IfComplex 判断每个元素是否为复数,如 ifComplex(array(2,2j),1)
2 Datatype 返回每个元素的数据类型的编号,如 Datatype(array(2,2j),1)
3 arraytofm 将Array数组转成FMarray矩阵 如arraytofm(t,2j)
4 ExportCsv 将数组转成csv字符串如 ExportCsv(array((2+3j,1+2j),(2,3)),s)
….其它数据类型的判定函数
统计函数 具体用法请查看官方函数说明
1 Mean 求算术平均值
2 Sum 求和
3 sumInt 对实部和虚部分别求整数部分和 算法: sumint(real(a))+sumint(imag(a))*1j
4 SumOfSquares 求数组平方和
5 Norm 求数组平方和的平方根 在复数的计算中aconj(a)会替代平方因此Norm的算法为 sqrt(sum(aconj(a))) 虚部恒为0所以norm仅返回实部的值
6 SumsAndSquares 计算总和以及平方和
7 StdDev 样本标准差 算法中计算平方由a*conj(a)替代,其它不变
8 PopnStdDev 总体标准差 算法中计算平方由a*conj(a)替代,其它不变
9 Variance 样本方差 算法中计算平方由a*conj(a)替代,其它不变
10 PopnVariance 总体方差 算法中计算平方由a*conj(a)替代,其它不变
11 TotalVariance 总体偏差 算法中计算平方由a*conj(a)替代,其它不变
12 MeanAndStdDev 计算平均值和标准差 标准差算法同StdDev
13 Geomean 几何平均数
14 Harmean 调和平均数
15 Mode 众数
16 AveDev 均值绝对偏差
17 DevSq 样本平均值偏差的平方和 算法中计算平方由a*conj(a)替代,其它不变
18 Product 累乘值
19 Randomfrom 从一组数据中随机抽取一个样本
20 VariationCoefficient 变异系数 算法中计算平方由a*conj(a)替代,其它不变,需等升级
双序列统计函数
1 Cov 协方差 E[X-ux*Conj(Y-uy)]
2 Correl 相关系数 Cov与PopnStdDev符合复数运算
3 Slopeandintercept 回归斜率和截距
4 Slope 回归斜率
5 Intercept 回归截距
6 RSQ 乘积矩相关系数平方
7 Steyx 相对标准偏差
8 BetaAndAlpha 斜率和截距
9 MeanAndPopnStdDevWithRate 带权重总体标准差以及平均值
矩阵运算及分解
1 mt_Transposition 矩阵转置
2 mt_Multiplication 矩阵乘
3 mt_Addition 矩阵和
4 mt_Subtraction 矩阵差
5 Mt_decompose_lu 进行lu分解 函数输出中vi为0wr为复数特征值
6 Mt_decompose_qr 对矩阵进行QR分解
7 Mt_decompose_eig 特征值及特征向量
8 Mt_decompose_chol cholesky分解
9 Mt_decompose_svd 进行SVD分解 实矩阵验证方式: U :* (eye(n,m)*s) :_ v 复矩阵验证方式: U :_ (eye(n,m)_s) :_ conj(v)
10 Mt_decompose_ldl ldl分解
复数支持的相关说明
复数与实数的等于判定

如:实数 3.15 与复数 3.15+0j。

在值上两者相等,即 3.15=3.15+0j 为真

在集合运算中,由于两者值虽等,但数据类型不一致,所以不能判定为同一个数,因此,在做去重合并及交集差集时,它两不会被认为是同一个元素。

而在 FMArray 中,由于数据类型必段保持一致,所以在进行集合运算时,当既存在 3.15 又存在 3.15+0j 时,会将 3.15 强制转换成 3.15+0j最后再进行合并处理。与 Array 的结果会存在差异。

关于 Norm 的算法说明

Norm一般定义计算一组数据的平方和的平方根也就是 Sqrt(SumOfSquares)

而在复数序列中,复数的计算是 aconj(a)替代平方,在求 norm 的时候,所以标准差的计算也是如此。即复数中算法为 sqrt(sum(aconj(a))),而由于其虚部恒为 0所以 norm 仅返回实数部的值。

由此,在其它统计类函数方法中,也有类似的处理,如标准差等,在上面列表中有特别提示说明。

统计函数的支持复数的范围说明

数组统计类函数对复数的支持(包括 FMARRAY 以及 ARRAY包含单序列以及双序列。包括如 cov,correl,斜率截距stddev,popnstddev...等等统计类函数,也包括简单的 sum,mean 之类的都支持,但任何和大小相关的都不支持,即如 median,max,min 类的统计函数、分位数及频度统计相关函数,牵涉到大小的,另一个是定义存在问题的偏度和峰度未做支持(因为统计意义我们没有找到其定义)。

双序列统计函数中不支持 FMarray 与其它数组 Array 混合运算

如 cov\correl 等

不支持如第一个参数为 fmarray 序列,而第二个参数为 Array 序列。

在统计函数中,目前复数版本和实数版本是分开的,不支持混合运算

原因是,全支持的代价较大,且意义不大,因为复数本身使用少,不能因复数影响实数计算。因此,用户在使用时,应该将数据处理成全实数或全复数。目前的假设,实数当复数可用,复数当实数需要满足虚部为 0。

即,不支持运算如 sum(array(1,1+2j)) 这种操作,会报错。用户在使用时,应该将所有值转成复数后再处理,如将 1 转成 1+0j 即可。

在实际应用中,当数组中既有实数,又有复数时,我们可以将数组中的实数批量转换成复数,方法如下:

第一种:循环遍历方式

t := array(1, 1 + 2j);
t: := ifComplex(mcell)?mcell:complex(mcell, 0);
return t;

第二种:复数重构

t := array(1, 1 + 2j);
return complex(real(t), imag(t));

转换后结果如下:

转换完成后,就可以进行 sum(t)的操作了。

WeakRef 弱引用(新一代)

为解决对象的循环引用问题,我们引入的弱引用这个概念,本章节中主要介绍它的产生背景、解决的问题及详细使用说明。

特别说明:弱引用目前只在下一代全新测试服务器中支持。

产生弱引用的背景

TSL 对象的生命周期中,采用的是引用计数模式,依赖对象的引用计数。

在对对象进行赋值等操作时,不是新增一个对象,而是指向该对象,并用引用数来记录该对象的生命周期。其过程为,当创建或引用对象时会使引用计数加 1失去引用时将使得引用计数减 1当引用计数为 0 时,会对该对象进行释放,这就是对象的一个完整的生命周期。

为了诠释这个过程,我们可以看下面这段代码:

A := new TA(); // 创建一个实例对象,增加一个引用数(创建对象yN=1)
B := A; // B指向A的实例对象增加一个引用数(yN=2)
echo "Set A to NIL\r\n";
A := nil; // 注销对象,引用数减一(yN=1)
echo "Set B to NIL\r\n";
B := nil; // 注销对象,引用数减一(yN=0此时对象被完全释放)
echo "Run end\r\n";
type TA = class
public
function Destroy();
begin
    echo "Destroy\r\n";
end;
end;

执行后打印结果为:

Set A to NIL

Set B to NIL

Destroy

Run end

从这个打印结果可以看出,创建对象 A 并被赋值给 B 后,当设置 A 的值为 nil 时(即注释原 A 创建的对象),该对象并没有直接被析构,而是等到 B 也被赋值为 nil 时,才被析构释放。

这种采用对象引用计数的设计它的优势有易于使用,不会非法引用,生命周期可以自动管理,性能高,低内存开销等。有些类似于 CopyOnWrite 模式,当将对象赋值给另一个变量时不会对内存进行拷贝,而只是产生引用计数。与 CopyOnWrite 的区别是,即便是对对象进行写入,也不会产生新的对象,而是直接与入当前对象。

采用引用计数在方便应用的同时,也带来了循环引用的问题。那么,什么是循环引用?看下面的例子:

A := new TA();
echo "Set A to NIL\r\n";
A := nil;
echo "Run end\r\n";

type TA = class
public
    FB;
    function Create();
    function Destroy();
end;

function TA.Create();
begin
    FB := new TB(self);
end;

function TA.Destroy();
begin
    echo "Destroy\r\n";
end;

type TB = class
public
    FOwner;
    function Create(owner);
end;

function TB.Create(owner);
begin
    FOwner := owner;
end;

输出:

Set A to NIL
Run end

从输出可以看到,Destroy() 没有被调用,因为循环引用导致引用计数无法归零。 原因是循环引用会让自身拥有多一个引用计数,循环引用后,引用计数因为自身的循环引用导致永远无法被减到 0。

也就是说循环引用会导致对象无法被释放,这类对象我们称之为无主对象,即垃圾对象。这种情况会导致原对象的析构函数无法被执行以及当产生很多垃圾对象时会导致占用许多内存,影响内存开销。

循环引用的主要产生场景

可以看出,在上面这些场景下,循环引用是必然会发生的事情,在这种情况下,用户就需要主动对循环引用对象在不需要的时候提前进行解耦,显然,这种方式下应用起来是比较麻烦的,因此,天软引入弱引用,能够比较好地解决该问题。我们本文中重点就是介绍,如何使用弱引用解决在对象中存在的循环引用问题。

弱引用

弱引用的定义

前面讲到,在 TSL 对象的生命周期中,依赖对象的引用计数。为了区别,当对对象进行引用时,被引用对象的引用数会加 1 的引用,我们称之为强引用,相对应的,对对象进行引用时,被引用对象的引用数不增加,但依然引用该对象,我们称之为弱引用。

弱引用的特点:

1 弱引用自身拥有引用计数。

2 弱引用不会增加被引用对象的引用数,不会参与到被引用对象的生周期中。

3 在探测到对象被释放时,不会引发异常。

弱引用支持范围:对象、带类信息的对象、对象成员方法。

注:弱引用是基于强引用产生的,弱引用的使用应该是针对需要弱引用的场景使用,没必要时不需要使用。

弱引用支持的类型

1 对象类型

2 含有当前类信息的对象类型,如 self 返回的对象

3 对象的成员函数

即,包含有对象的类型均需要支持弱引用

MakeWeakref

范例

范例 01弱引用的创建与对被引用对象引用数的影响

A := new TA("A");
B := makeweakref(A);
echo "B.FA--", B.FA;
echo "Set A to nil";
A := nil;
echo "A---End";
return 1;
type TA = class
public
FA;
function Create(v);
begin
    echo "Create--", v;
    FA := v;
end;
function Destroy();
begin
    echo FA, "-Destroy!";
end;
end;

打印:

Create--A

B.FA--A

Set A to nil

A-Destroy!

A---End

// 解析B.FA 返回”A”说明 B 引用 A 指向的对象;当 A 设置为 nil 时A 对应的对象被成功释放,即 B:=makeweakref(A);没有导致 A 指向的对象的引用数加 1B 是一个弱引用。
weakref_get生成强引用定义 2

范例

范例 02weakref_get 定义二,错误引用不报错

B := weakref_get (10, r); // 类型不支持。
return r; // 返回0
Weakref

范例

范例 01WeakRef 定义一,创建弱引用

A := new TStringList(); // 创建一个字符串对象
A.Append("A");
B := weakref(A); // 创建一个弱引用
B.Append("B");
return B.CommaText; // 返回逗号分割的字符串
WeakRef创建弱引用定义 2

范例

范例 02WeakRef 定义二,错误引用不报错

B := WeakRef (10, r); // 类型不支持。
return r; // 返回0
MakeStrongref

范例

范例 01通过弱引用对象创建一个强引用

A := new TStringList();
A.Append("A");
B := weakref(A); // 创建弱引用
B.Append("B");
C := MakestrongRef(B); // 通过弱引用对象产生一个强引用
C.Append("C");
return C.CommaText;

返回结果: A,B,C

MakeStrongref生成强引用定义 2

范例

范例 02MakestrongRef 定义二,错误引用不报错

B := MakestrongRef (10, r); // 类型不支持。
return r; // 返回0
weakref_get

范例

范例 01通过弱引用对象创建一个强引用

A := new TStringList();
A.Append("A");
B := weakref(A);
B.Append("B");
C := weakref_get (B); // 通过弱引用对象产生一个强引用
C.Append("C");
return C.CommaText;

结果: A,B,C

MakeWeakref创建弱引用定义 2

范例

范例 02MakeWeakRef 定义二,错误引用不报错

B := makeweakref(10, r); // 类型不支持。
return r; // 返回0
CheckWeakRef

范例

A := new TStringList();
B := weakref(A); // 弱引用
D := B;
return checkweakref(B);

返回结果2

weakref_check

范例

A := new TStringList();
B := weakref(A); // 弱引用
return weakref_check(B);

返回结果1

采用弱引用解决循环引用的问题
A := new TA();
echo "Set A to NIL\r\n";
A := nil;
echo "Run end\r\n";

type TA = class
public
    FB;
    function Create();
    function Destroy();
end;

function TA.Create();
begin
    FB := new TB(self);
end;

function TA.Destroy();
begin
    echo "Destroy\r\n";
end;

type TB = class
public
    FOwner;
    function Create(owner);
end;

function TB.Create(owner);
begin
    FOwner := MakeWeakRef(owner);
end;

输出:

Set A to NIL
Destroy
Run end

这种方式可以解决循环引用的问题,但是需要显式在创建时做弱引用处理。

自动弱引用

对设定标的赋值操作进行自动弱引用。主要约定的是类的成员变量。

自动弱引用关键字

Weakref自动弱引用

Autoref取消自动弱引用

引发循环引用的来源均来自于类成员变量,即

1 类成员变量直接引用

2 类成员数组间接引用。

所以自动弱引用的作用范围:类成员变量,默认为 Autoref。

Weakref

自动弱引用,即启动后,后面定义的成员变量都为弱引用。

Autoref

不自动弱引用,即启动后,后面定义的成员变量不再自动为弱引用,一般用于取消弱引用。

设置当前环境下的变量为自动弱引用

可通过设置关键字 WeakRef 启动弱引用使其下面定义的变量自动设置为弱引用AutoRef 为关闭弱引用,即恢复默认的强引用。

同时定义多个弱引用变量,实现代码如下:

type AutoWeakTest = class
    FA;
    WeakRef; // 定义下方的成员变量为弱引用
    FOnClick;
    FOnDBLClick;
    FOnMouseMove;
    FOnMouseOver;
    AutoRef; // 解除弱引用的定义,即下方成员变量为默认的强引用
    FB;
    FC;
end;

解读:其中,

FA 是初始模式,所以未自动弱引用。

WeakRef 打开 AutoWeakTest 类的成员变量自动弱引用开关。

FOnClick,FOnDBLClick,FOnMouseMove,FOnMouseOver; 为自动弱引用

AutoRef 关闭 AutoWeakTest 类的成员变量自动弱引用开关,

FB,FC 为非自动弱引用。

这种方式类似 Public,Private,Protected但 WeakRef,AutoRef 是约定是否自动弱引用,且仅对成员变量起效。可视域和自动弱引用的约定互相不干扰。

设定指定变量为自动弱引用

语法:

[WeakRef] x1[, x2, …];

指定成员 x1(或 x2 等,多个变量用,隔开)为自动弱引用

[AutoRef]x1[, x2, …];

指定成员 x1(或 x2 等,多个变量用,隔开)为强引用,一般在弱引用设定环境下使用。

即带[ ]只对当前语句中的变量有效,不带[]就是对段落有效。

范例:

type AutoWeakTest2 = class
    FA;
    [WeakRef] FB, FB2, FB3; // 在强引用环境下,定义弱引用
    FC;
    WeakRef; // 指定当前环境下定义的成员为弱引用
    FOnClick;
    [AutoRef] FD;
    FOnMouseMove;
    FOnMouseOver;
end;

上面的强引用成员变量有FA、FC、FD。 弱引用成员变量有FB,FB2,FB3,FonClick, FonMouseMove, FonMouseOver。

自动弱引用规则的起效规则

1 直接对自动弱引用的成员变量进行赋值

2 对 property write 指定的成员变量赋值

即,若 property A write

B 中B 是弱引用或者 B(v)方法中存在对弱引用的成员变量进行赋值,则在对 A 进行写入时(A := obj)B 此时产生的引用也是弱引用。

3 将成员变量作为参数送入 TSL 开发的函数,在函数内对参数赋值

即,在类的方法中将成员变量作为参数送入到被调用的函数中,在函数内对参数赋值

4 对数组成员变量进行下标设置 ArrayData[下标] := Obj 即,成员变量 ArrayData 是弱引用,且 ArrayData 是一个数组,在对这个数组的指定元素进行赋值操作时,也是弱引用。

应用场景说明:

1 目前 C++等开发而成的二进制函数和方法不支持自动弱引用规则,需要支持的函数要用新的接口进行改写。

2 弱引用规则主要为了实现 TSL 语言层的对象循环引用。

3 二进制函数基本不存在和 TSL 语言对象之间的循环引用问题。

4 如果存在其他情况需要用户使用 MakeWeakRef 来实现。

自动弱引用解决循环引用的问题
A := new TA();
echo "Set A to NIL\r\n";
A := nil;
echo "Run end\r\n";

type TA = class
public
    FB;
    function Create();
    function Destroy();
end;

function TA.Create();
begin
    FB := new TB(self);
end;

function TA.Destroy();
begin
    echo "Destroy\r\n";
end;

type TB = class
public
    [WeakRef] FOwner;
    function Create(owner);
end;

function TB.Create(owner);
begin
    FOwner := owner; // 弱引用
end;

输出:

Set A to NIL
Destroy
Run end

其中,FOwner 是自动弱引用成员变量,赋值时会自动生成弱引用。

当前版本对弱引用支持的判定

弱引用功能目前只在最新版的解释器中支持,用户可以通过条件编译的方式判断当前版本的解释器中是否支持弱引用及自动弱引用功能。

1、条件编译判定是否支持弱引用

{$IFDEF weakptr}

{$ENDIF}

2、条件编译支持判定是否支持自动弱引用

{$IFDEF AutoWeak}

{$ENDIF}

范例:

// 判断当前解释器是否支持弱引用
{$IFDEF weakptr}
echo "Support weakptr";
{$else}
echo "Unsupport weakptr";
{$ENDIF}
// 判断当前解释器是否支持自动弱引用
{$IFDEF AutoWeak}
echo 'Support AutoWeak';
{$else}
echo "Unsupport AutoWeak";
{$ENDIF}
return 1;

当前版本支持弱引用时打印:

Support weakptr

Support AutoWeak

当前版本不支持弱引用时打印:

Unsupport weakptr

Unsupport AutoWeak

对象对基础算符与基础函数的重载

TSL 对象支持对二进制函数的重载

为了更好地支持天软新一代节点网状关系数据库 TSNETDB 及符号计算等特殊应用,并对 GPU、NPU 等扩展计算提供基础支撑,天软进行了如下功能的新增:

1、在原有算符重载以及 TS-SQL 重载的基础之上,新增了 TSL 对象支持对二进制函数的重载,例如二进制函数 abs/sin/cos 等。

即既支持二进制类用 C 等语言扩展重载对象的函数(为 GPU/NPU 等计算提供支撑),也支持 TSL 开发的类重载二进制函数。

对于 TSL 对象,支持使用如下方式进行重载二进制函数:

2、使用类方法实现

3、使用对象成员函数实现

此外,进行重载时还支持具有变参返回的函数,如 TryStrToInt。

注:二进制函数指的是天软客户端中无法查看其源代码的系统或公用函数,其往往是使用更加底层的语言如 C、C++等实现的。下图是函数 RoundTo 在客户端中的展示。

二进制函数在 TSL 对象中的重载
// 定义:[class] function operator funcNmane([,p1[,p2[,…]]]);

说明:其中 class 关键字表示定义为类方法,为可选关键字。

Operator为重载关键字

funcName为被重载的二进制函数名

p1,p2函数参数列表。支持通过参数传出返回值。

当重载函数被定义为类方法时,应该保持与原函数参数个数一致。

当被定义为对象成员函数时,参数个数应该比原函数参数少 1 个;会以第一个参数的对象实例调用该成员函数。

在 TSL 对象中对二进制函数进行重载,需要注意以下几点:

1、方法定义时需在方法名前加上 operator 关键字

2、在类中如需调用全局函数可在函数调用前加上::。

全局函数:在任何位置都可以被调用到的函数,如系统函数、公用函数、用户函数等。

局部函数:只在指定范围内可被调用到的函数,比如子函数,及类方法等。

即,当存在同名函数时,在需要调用全局函数时可使用::进行指定。

3、使用此种方式重载的函数仅支持二进制函数不支持使用 TSL 代码实现的函数

4、关键字关键字函数的重载不需要使用::。

1、使用类方法实现二进制函数重载的示例

现有整数类 IntDate代码如下

type IntDate = class
value;
function create(v)
begin
    value := v;
end;
class function operator DateToStr(t)
begin
    t := ifObj(t)?t.value:t;
    endt := IntToDate(t);
    return DateToStr(endt);
end;
end;

其中,创建了类方法 DateToStr实现了对二进制函数 DateToStr 的重载。

在调用时,可以使用如下两种方式:

d := new IntDate(20240329);
return DateToStr(d); // 方式一
return class(IntDate).DateToStr(20240329); // 方式二

结果:返回字符串"2024-03-29"

2、使用成员函数实现二进制函数重载的示例

现有类 IntDate代码如下

type IntDate = class
value;
function create(v)
begin
    value := v;
end;
function operator DateToStr()
begin
    v := IntToDate(value);
    return DateToStr(v);
end;
end;

其中,创建了成员函数 DateToStr实现了对二进制函数 DateToStr 的重载。

调用方式如下:

d := new IntDate(20240329);
return DateToStr(d);

结果:返回字符串"2024-03-29"

3、重载函数支持通过参数传出返回值的示例

以成员函数重载为例,现有类 classA代码如下

type classA = class
value;
function operator TryStrToInt(msg);
begin
    return ::TryStrToInt(value, msg); // 使用::调用全局方法
end;
end;

其中,创建了成员函数 TryStrToInt实现了对二进制函数 TryStrToInt 的重载。

当运行如下调用代码时:

c := new classA();
c.value := "314";
ret := TryStrToInt(c, msg);
return array(ret, msg);

可以看到,字符串” 314”成功被转换成整数类型并且结果被赋值给了参数 msg。

对象中对二进制函数重载的应用
1、范例类 TsDate-对整数日期的扩展

如下所示,类 TsDate 对天软日期的一些常用方法进行了重载,使得整数日期可直接调用这些方法。

td := new TsDate(20240329);
echo "字符串日期", "---->", DateToStr(td);
echo "整数日期", "---->", DateToInt(td);
DecodeDate(td, year, month, day);
echo "指定日期的年月日(成员方法)", "---->", "year:"$year, " ", "month:"$month, " ", "day:"$day;
class(TsDate).DecodeDate(20240329, y, m, d);
echo "指定日期的年月日(类方法)", "---->", "year:"$y, " ", "month:"$m, " ", "day:"$d;
echo "所在年份", "---->", YearOf(td);
echo "所在月份", "---->", MonthOf(td);
echo "所在日", "---->", DayOf(td);
echo "所在年份的第一天", "---->", StartOfTheYear(td);
echo "所在年份的最后一天", "---->", EndOfTheYear(td);
echo "所在月份的第一天", "---->", StartOfTheMonth(td);
echo "所在月份的最后一天", "---->", EndOfTheMonth(td);
echo "所在周的第一天", "---->", StartOfTheWeek(td);
echo "所在周的最后一天", "---->", EndOfTheWeek(td);
echo "向前推移5年", "---->", IncYear(td, -5);
echo "向前推移5月", "---->", IncMonth(td, -5);
echo "向前推移5天", "---->", IncDay(td, -5);

其中,类 TsDate 实现代码如下:

type TsDate = class
Ftd;
function create(td)
begin
    Ftd := Init(td);
end;
function operator DateToStr()
begin
    return ::DateToStr(Ftd);
end;
function operator DateToInt()
begin
    return ::DateToInt(Ftd);
end;
function operator YearOf()
begin
    return ::YearOf(Ftd);
end;
function operator MonthOf()
begin
    return ::MonthOf(Ftd);
end;
function operator DayOf()
begin
    return ::DayOf(Ftd);
end;
class function operator DecodeDate(td, year, month, day)
begin
    v := ifObj(td)?td.Ftd:Init(td);
    return ::DecodeDate(v, year, month, day);
end;
function operator StartOfTheYear()
begin
    return ::DateToInt(::StartOfTheYear(Ftd));
end;
function operator EndOfTheYear()
begin
    return ::DateToInt(::EndOfTheYear(Ftd));
end;
function operator StartOfTheMonth()
begin
    return ::DateToInt(::StartOfTheMonth(Ftd));
end;
function operator EndOfTheMonth()
begin
    return ::DateToInt(::EndOfTheMonth(Ftd));
end;
function operator StartOfTheWeek()
begin
    return ::DateToInt(::StartOfTheWeek(Ftd));
end;
function operator EndOfTheWeek()
begin
    return ::DateToInt(::EndOfTheWeek(Ftd));
end;
function operator IncYear(y)
begin
    return ::DateToInt(::IncYear(Ftd, y));
end;
function operator IncMonth(m)
begin
    return ::DateToInt(::IncMonth(Ftd, m));
end;
function operator IncDay(d)
begin
    return ::DateToInt(::IncDay(Ftd, d));
end;
class function Init(td)
begin
    return ifstring(td)?StrToDate(td):((td >= 0 and td <= 99991231T)?td:IntToDate(td));
end;
end;

运行结果如下:

TSL 对象对遍历算符的重载

for in 循环在对象中的重载
// 定义: function operator for(flag:integer);

说明:

Operator为重载关键字

flag整型运行标识由两位二进制数字组成低位表示是否不是初始化状态

高位表示 for in 中是否有两个循环变量。因为我们普通的 for in 有 for a in t do 与 for a,b in t do 两种语法。

如 0b10十进制 2表示第一次循环且有两个循环变量0b11(十进制 3)表示非第一次循环且有两循环变量;

循环过程中,若返回 nil 则表示循环结束,若为数值则表示循环继续。

For in 循环在对象中重载的示例

实际对对象进行 for in 循环时,对对象的 data 属性进行循环操作。

a := array("A", "B", "C");
echo "For in table---------\r\n";
for n, v in a do
begin
    echo n, "->", v, "\r\n";
end;
echo "For in obj-一个变量---------\r\n";
    c1 := new C(a);
for n in c1 do
begin
    echo n, "\r\n";
end;
echo "For in obj-两个变量---------\r\n";
for n, v in c1 do
begin
    echo n, "->", v, "\r\n";
end;
return;
type C = class
public
data;
length_d;
findex;
function Create(v);
begin
    data := v;
    length_d := length(v);
end;
function operator for(flag); // flag 控制参数个数n,v
begin
    n := flag .& 2; // n 即为高位数值1 表示两个变量0 表示只有一个变量
    b := flag .& 1; // b 即为低位数值1 表示不是第一次循环0 表示第一次循环
    echo "--for--:", flag, " flag高位", n, " flag低位", b, "\r\n";
    if not b then
        findex := 0;
    else if findex < length_d - 1 then
        findex++;
    else
        return nil; // 循环结束
    if n then
        return array(findex, data[findex]); // for i, v in
    return findex; // for i in
end;
end;

打印结果:

for in table---------

0->A

1->B

2->C

for in obj-一个变量---------

--for--:0 flag 高位0 flag 低位0

0

--for--:1 flag 高位0 flag 低位1

1

--for--:1 flag 高位0 flag 低位1

2

--for--:1 flag 高位0 flag 低位1

for in obj-两个变量---------

--for--:2 flag 高位2 flag 低位0

0->A

--for--:3 flag 高位2 flag 低位1

1->B

--for--:3 flag 高位2 flag 低位1

2->C

--for--:3 flag 高位2 flag 低位1

解析:通过上面的过程可以看出,我们可以在对象中对 for in 操作进行重载实现,通过对 flag 的控制,可以实现每次循环过程中对对象的操作。

打印结果中,两个循环变量的循环中,高位值为 2 是因为代表二进制数值 0b10即二进制中高位为 1。

关键字 msize,mrows,mcols 在对象中的重载
// 定义:[class] function operator KeyWord([p1[,p2[,…]]]);

说明:其中 class 关键字表示定义为类方法,为可选关键字。

Operator为重载关键字

KeyWord表示关键字函数如 msiz,mrows,mcols 等

p1,p2函数参数列表。支持通过参数传出返回值。

当重载函数被定义为类方法时,应该保持与原函数参数个数一致。

当被定义为对象成员函数时,参数个数应该比原函数参数少 1 个;会以第一个参数的对象实例调用该成员函数。用法同二进制函数的重载。

注:关键字不需要::进行指定全局函数。

关键字函数在对象中重载的示例

以重载关键字函数 mcols(data,n)以及二进制函数 length 为示例。

c1 := new c();
echo c1.length(), " ", length(c1), "\r\n";
echo mcols(c1), "\r\n"; // 返回列数
echo tostn(c1.mcols(1)), "\r\n"; // 返回列下标
return;
type c = class
fa;
function create();
begin
    fa := array(("A":1, "B":2, "C":3), ("A":5, "B":5, "C":5));
end;
function operator mcols(n);
function operator length();
begin
    return ::length(fa);
end;
end;
function operator c.mcols(n);
begin
    _n := ifnil(n)?0:n;
    return mcols(fa, _n);
end;

打印结果:

2 2

3

array("A","B","C")

解析:通过上面的示例,可看出关键字的重载与调用跟对二进制函数的操作非常相似。

::,:.,mcell,mrow,mcol,mIndexCount,mIndex 等算符在对象中的重载
// 定义function operator KeyWord([p1[,p2[,…]]]);

说明:

Operator为重载关键字

KeyWord表示关键字函数如::,:.,mcell,mrow,mcol,mIndexCount,mIndex 等

p1,p2函数参数列表。

其中,在::与:.遍历过程中,有一个参数,该参数为 0 时表示第一次循环,为 1 时表示非第一次循环。

循环过程中,返回 0 或 nil 表示循环结束,非 0 数字则表示循环继续。

重载示例(模拟相关算符与关键字在数组中的功能):

type C1 = class
public
    data;
    rdata;
    findex;
    length_d;
    function Create(v);
    begin
        data := v;
    end;
    function operator ::(flag); // flag: 0 表示第一次循环1 表示非第一次循环(最多支持二维)
    begin
        // echo "::flag ", flag;
        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;
            length_d := length(rdata);
            findex := 0;
        end
        else if findex < length_d - 1 then
            findex++;
        else
            return nil; // 循环结束
        return 1; // 返回 0 或 nil 循环结束,其它数值则循环继续
    end;
    function operator :.(flag); // flag: 0 表示第一次循环1 表示非第一次循环(深度遍历)
    begin
        // echo ":.flag ", flag;
        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;
            length_d := length(rdata);
            findex := 0;
        end
        else if findex < length_d - 1 then
            findex++;
        else
            return nil; // 循环结束
        return 1; // 返回 0 或 nil 循环结束,其它数值则循环继续
    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;

调用示例:

t := array("A":0 -  > 3, "B":10 -  > 2, "C":20 -  > 21, "D":30 -  > 23); // 不完全矩阵
t["B", 1] := array(801, 802, 803); // 多维矩阵
obj := new c1(t);
echo "::遍历";
obj::begin
    s := "mcell:"$mcell$" mrow:"$mrow$" mcol:"$mcol$" mIndexCount:"$mIndexCount;
    for i := 0 to mIndexCount - 1 do s += " mIndex("$i$"):"$mIndex(0);
    echo s;
end;
echo ":.深度遍历";
obj:.begin
    s := "mcell:"$mcell$" mrow:"$mrow$" mcol:"$mcol$" mIndexCount:"$mIndexCount;
    for i := 0 to mIndexCount - 1 do s += " mIndex("$i$"):"$mIndex(0);
    echo s;
end;
return 1;

打印结果:

::遍历

mcell:0 mrow:A mcol:0 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:1 mrow:A mcol:1 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:2 mrow:A mcol:2 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:3 mrow:A mcol:3 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell: mrow:B mcol:1 mIndexCount:2 mIndex(0):B mIndex(1):B

mcell:20 mrow:C mcol:0 mIndexCount:2 mIndex(0):C mIndex(1):C

mcell:21 mrow:C mcol:1 mIndexCount:2 mIndex(0):C mIndex(1):C

:.深度遍历

mcell:0 mrow:A mcol:0 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:1 mrow:A mcol:1 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:2 mrow:A mcol:2 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:3 mrow:A mcol:3 mIndexCount:2 mIndex(0):A mIndex(1):A

mcell:801 mrow:B mcol:1 mIndexCount:3 mIndex(0):B mIndex(1):B mIndex(2):B

mcell:802 mrow:B mcol:1 mIndexCount:3 mIndex(0):B mIndex(1):B mIndex(2):B

mcell:803 mrow:B mcol:1 mIndexCount:3 mIndex(0):B mIndex(1):B mIndex(2):B

mcell:20 mrow:C mcol:0 mIndexCount:2 mIndex(0):C mIndex(1):C

mcell:21 mrow:C mcol:1 mIndexCount:2 mIndex(0):C mIndex(1):C