# 08 高级语言(新一代) 本章汇总新一代语法与相关机制。 ## 目录 - [高级语言(新一代)](#高级语言新一代) - [复数](#复数) - [定义](#定义) - [数据类型](#数据类型) - [复数运算](#复数运算) - [相关模型](#相关模型) - [WeakRef 弱引用(新一代)](#weakref-弱引用新一代) - [产生弱引用的背景](#产生弱引用的背景) - [弱引用](#弱引用) - [自动弱引用](#自动弱引用) - [当前版本对弱引用支持的判定](#当前版本对弱引用支持的判定) - [对象对基础算符与基础函数的重载](#对象对基础算符与基础函数的重载) - [TSL 对象支持对二进制函数的重载](#tsl-对象支持对二进制函数的重载) - [TSL 对象对遍历算符的重载](#tsl-对象对遍历算符的重载) ## 高级语言(新一代) ### 复数 形如 a+bi(a、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) 可以生成复数及复数矩阵。 ```tsl z := 4 + 3j; z := complex(4, 3); ``` 即表示 z 是一个实部为 4,虚部为 3 的复数。 复数在客户端中的显示如下: 复数矩阵示例: ```tsl z := complex(array((1, 2, 3), (4, 5, 6)), array((-1, -2, -3), (1, 2, 3))); ``` 生成一个 2\*3 的矩阵,显示如下: 也可借助矩阵初始化函数: ```tsl z := complex(ones(2, 3), ones(2, 3)); ``` 生成一个 2\*3 的实部与虚部都为 1 的复数矩阵,显示如下: 随机矩阵: ```tsl z := complex(rand(3, 2), rand(3, 2)); ``` #### 数据类型 在 TSL 语言中, datatype(v)可以判断任意数据的数据类型,而复数的数据类型对应的是分类 41。也可以通过 ifcomplex(z)进行判断是否是复数。 注意,虽然实数也属于复数,但是 ifcomplex 会判定实数(没有用 j 表示的数)为假,而 datatype 也同理。 例如: ```tsl z := 4 + 3j; return datatype(z); ``` 返回整型 41。 在解释器中直接运行如下: #### 复数运算 支持复数的基础运算,如四则运算、矩阵运算、集合运算、赋值运算等,具体可查看列表。 在使用上与实数一致,例如,有复数 z1 与 z2,分别如下: ```tsl z1 := 4 + 3j; z2 := 5 + 12j; ``` 加法: ```tsl return z1 + z2; ``` 返回:9.0+15.0j 减法: ```tsl return z2 - z1; ``` 返回:1.0+9.0j 乘法: ```tsl return z1 * z2; ``` 返回:-16.0+63.0j 除法: ```tsl 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:生成一个复数 ```tsl return complex(3, 4); ``` 3.0+4.0j 范例 02:生成一个复数矩阵 ```tsl 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: ```tsl z := 4 + 3j; z1 := 4; z2 := 3j; echo imag(z); echo imag(z1); echo imag(z2); ``` 打印结果为: 3 0 3 范例 02:数组的应用 ```tsl z := 4 + 3j; z1 := 4; z2 := 3j; t := array(z, z1, z2); return imag(t); ``` 返回:array(3.0,0.0,3.0) ##### 共轭复数 ###### conj 范例 范例 01: ```tsl z := 4 + 3j; return conj(z); // 复数z的共轭复数 ``` 返回:4-3j 范例 02:数组的应用 ```tsl t := array(4 + 3j, 4, 3j);return conj(t); ``` 返回:array(4.0-3.0j,4.0,0.0-3.0j) ##### 复数的判定 ###### IfComplex 范例 ```tsl // 当参数为复数类型变量时,返回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,如: ```tsl 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 | 求数组平方和的平方根 | 在复数的计算中,a*conj(a)会替代平方,因此Norm的算法为: sqrt(sum(a*conj(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为0,wr为复数特征值 | | | | 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) 而在复数序列中,复数的计算是 a*conj(a)替代平方,在求 norm 的时候,所以标准差的计算也是如此。即复数中算法为 sqrt(sum(a*conj(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 即可。 在实际应用中,当数组中既有实数,又有复数时,我们可以将数组中的实数批量转换成复数,方法如下: 第一种:循环遍历方式 ```tsl t := array(1, 1 + 2j); t: := ifComplex(mcell)?mcell:complex(mcell, 0); return t; ``` 第二种:复数重构 ```tsl t := array(1, 1 + 2j); return complex(real(t), imag(t)); ``` 转换后结果如下: 转换完成后,就可以进行 sum(t)的操作了。 ### WeakRef 弱引用(新一代) 为解决对象的循环引用问题,我们引入的弱引用这个概念,本章节中主要介绍它的产生背景、解决的问题及详细使用说明。 特别说明:弱引用目前只在下一代全新测试服务器中支持。 #### 产生弱引用的背景 TSL 对象的生命周期中,采用的是引用计数模式,依赖对象的引用计数。 在对对象进行赋值等操作时,不是新增一个对象,而是指向该对象,并用引用数来记录该对象的生命周期。其过程为,当创建或引用对象时会使引用计数加 1,失去引用时将使得引用计数减 1,当引用计数为 0 时,会对该对象进行释放,这就是对象的一个完整的生命周期。 为了诠释这个过程,我们可以看下面这段代码: ```tsl 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 的区别是,即便是对对象进行写入,也不会产生新的对象,而是直接与入当前对象。 采用引用计数在方便应用的同时,也带来了循环引用的问题。那么,什么是循环引用?看下面的例子: ```tsl 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; ``` 输出: ```tsl Set A to NIL Run end ``` 从输出可以看到,`Destroy()` 没有被调用,因为循环引用导致引用计数无法归零。 原因是循环引用会让自身拥有多一个引用计数,循环引用后,引用计数因为自身的循环引用导致永远无法被减到 0。 也就是说循环引用会导致对象无法被释放,这类对象我们称之为无主对象,即垃圾对象。这种情况会导致原对象的析构函数无法被执行以及当产生很多垃圾对象时会导致占用许多内存,影响内存开销。 循环引用的主要产生场景 可以看出,在上面这些场景下,循环引用是必然会发生的事情,在这种情况下,用户就需要主动对循环引用对象在不需要的时候提前进行解耦,显然,这种方式下应用起来是比较麻烦的,因此,天软引入弱引用,能够比较好地解决该问题。我们本文中重点就是介绍,如何使用弱引用解决在对象中存在的循环引用问题。 #### 弱引用 ##### 弱引用的定义 前面讲到,在 TSL 对象的生命周期中,依赖对象的引用计数。为了区别,当对对象进行引用时,被引用对象的引用数会加 1 的引用,我们称之为强引用,相对应的,对对象进行引用时,被引用对象的引用数不增加,但依然引用该对象,我们称之为弱引用。 弱引用的特点: 1 弱引用自身拥有引用计数。 2 弱引用不会增加被引用对象的引用数,不会参与到被引用对象的生周期中。 3 在探测到对象被释放时,不会引发异常。 弱引用支持范围:对象、带类信息的对象、对象成员方法。 注:弱引用是基于强引用产生的,弱引用的使用应该是针对需要弱引用的场景使用,没必要时不需要使用。 ##### 弱引用支持的类型 1 对象类型 2 含有当前类信息的对象类型,如 self 返回的对象 3 对象的成员函数 即,包含有对象的类型均需要支持弱引用 ##### MakeWeakref 范例 范例 01:弱引用的创建与对被引用对象引用数的影响 ```tsl 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 ```tsl // 解析:B.FA 返回”A”,说明 B 引用 A 指向的对象;当 A 设置为 nil 时,A 对应的对象被成功释放,即 B:=makeweakref(A);没有导致 A 指向的对象的引用数加 1,B 是一个弱引用。 ``` ##### weakref_get:生成强引用定义 2 范例 范例 02:weakref_get 定义二,错误引用不报错 ```tsl B := weakref_get (10, r); // 类型不支持。 return r; // 返回0 ``` ##### Weakref 范例 范例 01:WeakRef 定义一,创建弱引用 ```tsl A := new TStringList(); // 创建一个字符串对象 A.Append("A"); B := weakref(A); // 创建一个弱引用 B.Append("B"); return B.CommaText; // 返回逗号分割的字符串 ``` ##### WeakRef:创建弱引用定义 2 范例 范例 02:WeakRef 定义二,错误引用不报错 ```tsl B := WeakRef (10, r); // 类型不支持。 return r; // 返回0 ``` ##### MakeStrongref 范例 范例 01:通过弱引用对象创建一个强引用 ```tsl 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 范例 范例 02:MakestrongRef 定义二,错误引用不报错 ```tsl B := MakestrongRef (10, r); // 类型不支持。 return r; // 返回0 ``` ##### weakref_get 范例 范例 01:通过弱引用对象创建一个强引用 ```tsl 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 范例 范例 02:MakeWeakRef 定义二,错误引用不报错 ```tsl B := makeweakref(10, r); // 类型不支持。 return r; // 返回0 ``` ##### CheckWeakRef 范例 ```tsl A := new TStringList(); B := weakref(A); // 弱引用 D := B; return checkweakref(B); ``` 返回结果:2 ##### weakref_check 范例 ```tsl A := new TStringList(); B := weakref(A); // 弱引用 return weakref_check(B); ``` 返回结果:1 ##### 采用弱引用解决循环引用的问题 ```tsl 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; ``` 输出: ```tsl Set A to NIL Destroy Run end ``` 这种方式可以解决循环引用的问题,但是需要显式在创建时做弱引用处理。 #### 自动弱引用 对设定标的赋值操作进行自动弱引用。主要约定的是类的成员变量。 ##### 自动弱引用关键字 Weakref:自动弱引用 Autoref:取消自动弱引用 引发循环引用的来源均来自于类成员变量,即 1 类成员变量直接引用 2 类成员数组间接引用。 所以自动弱引用的作用范围:类成员变量,默认为 Autoref。 ###### Weakref 自动弱引用,即启动后,后面定义的成员变量都为弱引用。 ###### Autoref 不自动弱引用,即启动后,后面定义的成员变量不再自动为弱引用,一般用于取消弱引用。 ##### 设置当前环境下的变量为自动弱引用 可通过设置关键字 WeakRef 启动弱引用,使其下面定义的变量自动设置为弱引用,AutoRef 为关闭弱引用,即恢复默认的强引用。 同时定义多个弱引用变量,实现代码如下: ```tsl 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 是约定是否自动弱引用,且仅对成员变量起效。可视域和自动弱引用的约定互相不干扰。 ##### 设定指定变量为自动弱引用 语法: ```tsl [WeakRef] x1[, x2, …]; ``` 指定成员 x1(或 x2 等,多个变量用,隔开)为自动弱引用 ```tsl [AutoRef]x1[, x2, …]; ``` 指定成员 x1(或 x2 等,多个变量用,隔开)为强引用,一般在弱引用设定环境下使用。 即带[ ]只对当前语句中的变量有效,不带[]就是对段落有效。 范例: ```tsl 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 ```tsl B 中,B 是弱引用或者 B(v)方法中存在对弱引用的成员变量进行赋值,则在对 A 进行写入时(A := obj),B 此时产生的引用也是弱引用。 ``` 3 将成员变量作为参数送入 TSL 开发的函数,在函数内对参数赋值 即,在类的方法中将成员变量作为参数送入到被调用的函数中,在函数内对参数赋值 4 对数组成员变量进行下标设置 `ArrayData[下标] := Obj` 即,成员变量 ArrayData 是弱引用,且 ArrayData 是一个数组,在对这个数组的指定元素进行赋值操作时,也是弱引用。 应用场景说明: 1 目前 C++等开发而成的二进制函数和方法不支持自动弱引用规则,需要支持的函数要用新的接口进行改写。 2 弱引用规则主要为了实现 TSL 语言层的对象循环引用。 3 二进制函数基本不存在和 TSL 语言对象之间的循环引用问题。 4 如果存在其他情况需要用户使用 MakeWeakRef 来实现。 ##### 自动弱引用解决循环引用的问题 ```tsl 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; ``` 输出: ```tsl Set A to NIL Destroy Run end ``` 其中,`FOwner` 是自动弱引用成员变量,赋值时会自动生成弱引用。 #### 当前版本对弱引用支持的判定 弱引用功能目前只在最新版的解释器中支持,用户可以通过条件编译的方式判断当前版本的解释器中是否支持弱引用及自动弱引用功能。 1、条件编译判定是否支持弱引用 {$IFDEF weakptr} {$ENDIF} 2、条件编译支持判定是否支持自动弱引用 {$IFDEF AutoWeak} {$ENDIF} 范例: ```tsl // 判断当前解释器是否支持弱引用 {$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 对象中的重载 ```tsl // 定义:[class] function operator funcNmane([,p1[,p2[,…]]]); ``` 说明:其中 class 关键字表示定义为类方法,为可选关键字。 Operator:为重载关键字 funcName:为被重载的二进制函数名 p1,p2,…:函数参数列表。支持通过参数传出返回值。 当重载函数被定义为类方法时,应该保持与原函数参数个数一致。 当被定义为对象成员函数时,参数个数应该比原函数参数少 1 个;会以第一个参数的对象实例调用该成员函数。 在 TSL 对象中对二进制函数进行重载,需要注意以下几点: 1、方法定义时,需在方法名前加上 operator 关键字 2、在类中,如需调用全局函数,可在函数调用前加上::。 全局函数:在任何位置都可以被调用到的函数,如系统函数、公用函数、用户函数等。 局部函数:只在指定范围内可被调用到的函数,比如子函数,及类方法等。 即,当存在同名函数时,在需要调用全局函数时可使用::进行指定。 3、使用此种方式重载的函数仅支持二进制函数,不支持使用 TSL 代码实现的函数 4、关键字(关键字函数)的重载不需要使用::。 ###### 1、使用类方法实现二进制函数重载的示例 现有整数类 IntDate,代码如下: ```tsl 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 的重载。 在调用时,可以使用如下两种方式: ```tsl d := new IntDate(20240329); return DateToStr(d); // 方式一 return class(IntDate).DateToStr(20240329); // 方式二 ``` 结果:返回字符串"2024-03-29" ###### 2、使用成员函数实现二进制函数重载的示例 现有类 IntDate,代码如下: ```tsl 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 的重载。 调用方式如下: ```tsl d := new IntDate(20240329); return DateToStr(d); ``` 结果:返回字符串"2024-03-29" ###### 3、重载函数支持通过参数传出返回值的示例 以成员函数重载为例,现有类 classA,代码如下: ```tsl type classA = class value; function operator TryStrToInt(msg); begin return ::TryStrToInt(value, msg); // 使用::调用全局方法 end; end; ``` 其中,创建了成员函数 TryStrToInt,实现了对二进制函数 TryStrToInt 的重载。 当运行如下调用代码时: ```tsl c := new classA(); c.value := "314"; ret := TryStrToInt(c, msg); return array(ret, msg); ``` 可以看到,字符串” 314”成功被转换成整数类型,并且结果被赋值给了参数 msg。 ##### 对象中对二进制函数重载的应用 ###### 1、范例:类 TsDate-对整数日期的扩展 如下所示,类 TsDate 对天软日期的一些常用方法进行了重载,使得整数日期可直接调用这些方法。 ```tsl 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 实现代码如下: ```tsl 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 循环在对象中的重载 ```tsl // 定义: 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 属性进行循环操作。 ```tsl 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 在对象中的重载 ```tsl // 定义:[class] function operator KeyWord([p1[,p2[,…]]]); ``` 说明:其中 class 关键字表示定义为类方法,为可选关键字。 Operator:为重载关键字 KeyWord:表示关键字函数,如 msiz,mrows,mcols 等 p1,p2,…:函数参数列表。支持通过参数传出返回值。 当重载函数被定义为类方法时,应该保持与原函数参数个数一致。 当被定义为对象成员函数时,参数个数应该比原函数参数少 1 个;会以第一个参数的对象实例调用该成员函数。用法同二进制函数的重载。 注:关键字不需要::进行指定全局函数。 ###### 关键字函数在对象中重载的示例 以重载关键字函数 mcols(data,n)以及二进制函数 length 为示例。 ```tsl 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 等算符在对象中的重载 ```tsl // 定义:function operator KeyWord([p1[,p2[,…]]]); ``` 说明: Operator:为重载关键字 KeyWord:表示关键字函数,如::,:.,mcell,mrow,mcol,mIndexCount,mIndex 等 p1,p2,…:函数参数列表。 其中,在::与:.遍历过程中,有一个参数,该参数为 0 时表示第一次循环,为 1 时表示非第一次循环。 循环过程中,返回 0 或 nil 表示循环结束,非 0 数字则表示循环继续。 重载示例(模拟相关算符与关键字在数组中的功能): ```tsl 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; ``` 调用示例: ```tsl 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