tsl-devkit/docs/tsl/syntax_book/02_control_flow.md

611 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 02 控制流与异常
本章汇总流程控制、错误控制与调试相关语句。
## 目录
- [02 控制流与异常](#02-控制流与异常)
- [目录](#目录)
- [流程控制语句](#流程控制语句)
- [条件语句](#条件语句)
- [IF](#if)
- [IF 表达式](#if-表达式)
- [CASE](#case)
- [循环语句](#循环语句)
- [WHILE](#while)
- [REPEAT](#repeat)
- [FOR](#for)
- [BREAK](#break)
- [CONTINUE](#continue)
- [GOTO](#goto)
- [错误控制,以及调试语句](#错误控制以及调试语句)
- [异常处理 Try Except/Finally](#异常处理-try-exceptfinally)
- [ExceptObject 异常对象](#exceptobject-异常对象)
- [RAISE](#raise)
- [DEBUGRETURN](#debugreturn)
- [DebugRunEnv 与 DebugRunEnvDo](#debugrunenv-与-debugrunenvdo)
- [MTIC,MTOC 计算运算时间](#mticmtoc-计算运算时间)
- [SetProfiler,GetProfilerInfo 优化信息](#setprofilergetprofilerinfo-优化信息)
- [调用信息与代码行号](#调用信息与代码行号)
- [函数的返回和退出](#函数的返回和退出)
## 流程控制语句
### 条件语句
#### IF
if 语句是由一个布尔表达式和两个供选择的操作序列组成。运行时根据布尔表达式求值结果,选取其中之一的操作序列执行。有两种形式的 if 语句:
```tsl
if <布尔表达式> then <语句> ;
if <布尔表达式> then <语句1>
else <语句2> ;
```
当布尔表达式的值为真,执行 then 后面的语句;当值为假时则有两种情况:要么什么也不做,要么执行 else 后面的语句。
注意:
else 前面没有分号,因为分号是两个语句之间的分隔符,而 else 并非语句。如果在该处添了分号,则远程服务器在编译的时候就会认为 if 语句到此结束,而把 else 当作另一句的开头,这样就会输出出错信息。
语句可以是一条语句或是一组语句,如果是一组语句时,这组语句必须使用 Begin …
end 标识符来限定,写成复合语句。在用 if 语句连续嵌套时,如果你插入适量的复合语句,有利于程序的阅读和理解。
例 2求 y=f(x),当 x>0 时y=1当 x=0 时y=0当 x<0 y=-1。
```tsl
function IfExample(x);
begin
if x > 0 then
y := 1;
else if x = 0 then
y := 0;
else
y := -1;
return y;
end;
```
3 x>0 时候,计算 x*x并且输出 x*x否则输出 0。
```tsl
function IfExample2(x);
begin
if x >= 0 then
begin
x1 := x * x;
return x1;
end
else return 0;
end;
```
注意:当 if 语句嵌套时TSL 约定 else 总是和最近的一个 if 配对。
#### IF 表达式
if 表达式是一种条件表达式,它根据条件的真假来返回不同的值。它与 if 语句不同:
if 语句:是一种控制流语句,用于决定是否执行某段代码块,本身不返回值。
if 表达式:会计算一个结果,这个结果可以赋值给变量、作为函数参数或在其他表达式中使用。功能类似三元运算符,但 if 表达式更通用,可读性更高
其基本形式通常如下:
if 条件 then 值1 else 值2
例如 if a>1 then 2 else 1如果 a 大于 1整个表达式的结果就是 2否则是 1。
if 表达式必须存在 else 部分,主要是为了确保表达式始终有确定的返回值。如果没有 else当条件为假时表达式的返回值将是不确定的。
注:仅 2025-08-27 以后的语言版本支持此功能
示例:
```tsl
ret := if x > 0 then x * x else 0;
return ret;
```
当 x>0返回 x\*xx<=0 时,返回 0。
多个分支:
```tsl
ret := if x > 0 then x * x else if x < 0 then - (x * x) else 0;
return ret;
```
当 x>0返回 x*xx<0 返回-x*xx=0返回 0
#### CASE
多分支条件语句Case of
语法一普通语法
```tsl
case <Expression> of
<情况标号表 1>: 语句 1;
<情况标号表 2>: 语句 2;
...
<情况标号表 N>: 语句 N;
[else 例外语句;]
end;
```
情况标号表的语法为
```tsl
case 区间 1[, case 区间 2 .. case 区间 N]
区间开始值 [to 区间结束值]
```
如果没有 TO 语句则结束值和开始值相同
```tsl
function CaseExample(Age);
begin
case Age of
0: Writeln("婴儿");
1, 2: Writeln("婴幼儿");
3 to 6: Writeln("幼儿");
7 to 14: Writeln("少年");
15 to 17: Writeln("青少年");
else
Writeln("成年");
end;
end;
```
语法二支持 Case 表达式在该种情况下分支语句不支持语句段只能是单语句表达式
```tsl
B := case <Expression> of
<情况标号表 1> : 表达式 1;
<情况标号表 2> : 表达式 2;
```
…(其它的与普通用法一致
范例
范例一
```tsl
a := 3;
b := case a of
1, 2: "1/2";
3, 4: "3/4";
else "OTHER";
end;
return b;
```
结果3/4
范例二
```tsl
a := 3;
b := case a of
1, 2: echo "1/2";
3, 4: echo "3/4";
else "OTHER";
end;
return b;
```
结果0打印窗口3/4
范例三表达式的用法
```tsl
b := @case a of
1, 2: "1/2";
3, 4: "3/4";
else "OTHER";
end;
a := 2;
return eval(b);
```
结果1/2
### 循环语句
当需要重复执行一条或是一组语句时可以使用循环控制语句TSL 中的循环控制语句有 While 语句和 For 语句
#### WHILE
while 语句用于"当满足某一条件时重复执行语句"的情况while 语句的语法格式
while 布尔表达式 do 语句
循环结束条件在进入循环体之前测试若最初的测试值为 false则根本不进入循环体为了能使 while 重复能终止循环体中一定要有影响布尔表达式的操作否则该循就是一个死循环
说明
语句可以是一条语句或是一组语句如果是一组语句时这组语句必须使用 Begin
end 标识符来限定写成复合语句
4计算从 0 到某个数之间的和
```tsl
function sums(limit);
begin
sum := 0;
num := 0;
while num <= limit do
begin
sum := sum + num;
num++;
end;
return sum;
end;
```
#### REPEAT
repeat 语句用于重复执行语句直到满足某一条件的情况repeat 语句的语法格式
```tsl
repeat
// 语句段;
until 布尔表达式;
```
说明
repeat while 不同之处有几点
1,repeat 先做后判断是否结束while 先判断后做也就是说 repeat 至少会做一次
2,repeat 的判断条件是结束条件 while 的判定条件是开始做的条件
3,repeat util 之间可以有语句段不需要 begin
end 来限定 while 由于没有结束的特殊标识符因此当使用语句段的时候必须用 begin
end 来约束
5求第一个阶乘超过指定值的值
```tsl
function MinMultiValue(limit);
begin
multi := 1;
value := 1;
repeat
multi := multi * value;
value++;
until multi > limit;
return value;
end;
```
#### FOR
```tsl
for 语句用来描述已知重复次数的循环结构。for 语句有三种形式:
(1) for 控制变量 := 初值 to 终值 [step 步长] do 语句;
(2) for 控制变量 := 初值 downto 终值 [step 步长] do 语句;
(3) for 控制变量 1控制变量 2 in 数组 do 语句;
```
第一种形式的 for 语句是递增循环
首先将初值赋给控制变量接着判断控制变量的值是否小于或等于终值若是则执行循环体在执行了循环体之后自动将控制变量的值该为它的后继值并重新判断是否小于或等于终值当控制变量的值大于终值时退出 for 循环执行 for 语句之后的语句
可通过 step N 方式指定递增步长可省默认为 1
第二种形式的 for 语句是递减循环
首先将初值赋给控制变量接着判断控制变量的值是否大于或等于终值若是则执行循环体在执行了循环体之后自动将控制变量的值该为它的前趋值并重新判断是否大于或等于终值当控制变量的值小于终值时退出 for 循环执行 for 语句之后的语句
可通过 step N 方式指定递减步长可省默认为 1
注意for 语句中当初值终值步长确定后重复的次数就确定不变了并且控制变量在重复语句内不能施加任何赋值操作
例如计算 1+2+3+……+99+100 的值
```tsl
function PlusFor();
begin
sum := 0;
for i := 1 to 100 do sum := sum + i; // 缺省步长,默认步长为 1
return sum;
end;
```
例如计算 1+3+5+……+99 的值
```tsl
function PlusFor2();
begin
sum := 0;
for i := 1 to 100 step 2 do sum := sum + i;
return sum;
end;
```
第三种形式的 for 语句是直接对数组进行遍历
对数组中的每一行第一维进行遍历当前行的下标存放在第一个控制变量中该行对应的值存放在第二个控制变量中从第一行开始将行标与当前行的值分别赋值给控制变量 1 与控制变量 2 执行循环体在执行了循环体之后自动将 2 个控制变量的值赋值为下一行的下标及该行值当遍历完最后一行之后退出 for 循环执行 for 语句之后的语句
for in 遍历的用法说明
语法For i,v IN TArray DO 语句;说明对数据的遍历
其中i控制变量 1,获取当前循环中数组第一维的下标值 v控制变量 2对应当前循环中第一维度的值
TArray需要被遍历的数组
1二维及多维数组可当作一维处理此时的控制变量 2 的值则可能是一个数组
2在此过程中不可更改一维数组的值也不可对该数组中的任何元素进行赋值操作对在循环过程中不可对循环数组 TArray 进行变更操作
适应场景对于非数字下标的数组处理比较方便且效率高
范例一一维数组的应用
```tsl
data := array('a': 1, 'b': 5, 'c': 3, 'd': -2);
s := 0;
for i, v in data do s += v;
return s;
```
返回7
范例二二维数组的应用
```tsl
data := rand(array('a', 'b', 'c'), array('AA', 'BB', 'CC', 'DD'));
s := 0;
t := 1;
for i, v in data do // data 是二维数组,所以第一维中 v 的值是当前行的一维数组
for j, v1 in v do
begin
s += v1;
t *= v1;
end;
return array(s, t);
```
返回array(12,1)
#### BREAK
在执行 WHILE FOR 以及 REPEAT
until 循环语句时可以用 break 语句随时从当前循环的语句段中跳出来并继续执行循环语句后面的语句
注意Break 语句只是从当前的语句循环中跳出来如果要从多个嵌套的循环语句中跳出则需要通过多个对应的 Break 语句来完成
7我们用 While 语句和 Break 语句重新来例 5 中的 1+2+3+……+99+100
```tsl
function PlusWhile();
begin
sum := 0;
i := 0;
while true do
begin
i++;
if i > 100 then break;
sum := sum + i;
end;
return sum; // break 后执行的第一行语句
end;
```
#### CONTINUE
CONTINUE 语句和 BREAK 语句一样都可以改变 WHILE 循环语句和 FOR 循环语句以及 REPEAT
until 的执行顺序
BREAK 是强制地从一个循环语句中跳出来,提前结束循环 CONTINUE 语句则强制地结束当前循环开始进入下一次循环
```tsl
while true do
begin
i++;
if i = 100 then continue; // 跳过 100
if i >= 1000 then break; // 到 1000 结束
end;
```
### GOTO
几乎所有的分支流程控制语句都指令跳转有关只是绝大多数情况下是有条件跳转,GOTO 是无条件跳转语句其规则是使用 label 定义标号使用 goto 可以跳转到指定的标号
一个 GOTO 的案例
```tsl
for i := 0 to length(data) - 1 do
begin
for j := 0 to length(data[i]) - 1 do
begin
if data[i][j] = target then
begin
goto finded;
end;
end;
end;
label finded;
// 在二维数组中查找,找到即结束
```
goto 有一个特性就是只能从内层往外层跳转且不能跨越函数
### 错误控制,以及调试语句
#### 异常处理 Try Except/Finally
某些函数在执行的过程中可能会自动抛出异常或者被手动 Raise 抛出异常这个时候如果没有异常处理运行就会终止
使用异常处理则可以保护程序继续执行并可以对异常进行相应的处理异常的信息可以由 ExceptObject 对象获得异常处理使用如下模式
```tsl
try
// 被保护的程序执行段
except
// 异常处理程序段
end;
```
例如
```tsl
try
I := StrToInt(S); // 当 S 不能转换为整数时会产生异常
except
I := 0; // 当发生异常时设置 I 为 0
Writeln(ExceptObject.ErrInfo);
end;
```
对于某个程序段可能出现中途返回或者退出或者中途被异常中断而某些代码必需要在其后执行的则采用如下模式
```tsl
try
// 被保护的程序执行段
finally
// 保证执行的处理程序段即便Try Finally之间的语句有返回或者异常产生。
end;
```
try...Except...end 可以使程序在报错时继续向下运行即主程序不终止
try...finally...end 则是该报错时就会报错即发生错误时程序会报错且终止只是在中断前会执行完 finally 中的命令行
#### ExceptObject 异常对象
Except 块中可以用 ExceptObject 获得当前的异常信息
ExceptObject 是一个异常对象包括以下几个成员
ExceptObject. ErrInfo 获得错误的信息串
ExceptObject. ErrLine 错误的行号
ExceptObject. ErrNo 错误号
例如
```tsl
a := 100;
try
a := 1 + 'a';
except
echo ExceptObject.ErrInfo;
end;
return a;
```
打印结果
function:NoName501:line 11:instruction:+: Addition instruction error,operand
type error
#### RAISE
主动抛出运行时异常会引发程序出错并终止运行RAISE 后跟随一个字符串该字符串为出错返回的错误信息
```tsl
A := -1;
if a < 0 then raise “a不能小于0”;
```
运行时报错如
#### DEBUGRETURN
调试返回后面跟返回值可在任何地方直接将结果返回而不是象 RETURN 一样返回到上一级别这有助于用户调试使用
如下面示例返回为 3 而不是 4
```tsl
A := abcd(3);
return A + 1;
function abcd(bb);
begin
debugreturn
bb;
end;
```
#### DebugRunEnv 与 DebugRunEnvDo
DebugRunEnv(0) DebugRunEnv(1)
DebugRunEnv(0)可以将所有的变量内容递交到客户端的调试窗口
DebugRunEnv(1)可以将变量以及系统参数的内容递交到客户端的调试窗口
DebugRunEnvDo FunctionXX(….)
运行完指定的函数以后返回该函数最后的变量结果
#### MTIC,MTOC 计算运算时间
可以通过 MTIC MTOC 记录一段程序运行的时间
一般使用以上代码可以计算所耗费的秒数
```tsl
MTIC
;
A := 0;
for i := 0 to 99999 do &#61607; A++;
return
MTOC
;
```
扩展使用同时统计多段程序的运行时间
默认 MTOC 和上次 MTIC 匹配但是也可以指定某个 MTIC 的返回来计算时间
```tsl
T1 :=
MTIC
;
for i := 0 to 9999 do A++;
TE1 :=
MTOC(T1)
;
MTIC;
for j := 0 to 9999 do A++;
TE2 :=
MTOC(T1)
;
return array(TE1, TE2,
MTOC
);
```
返回结果中TE1 为第一段循环运行的时间TE2 为两段循环运行的时间第三个值为第二段循环运行的时间
#### SetProfiler,GetProfilerInfo 优化信息
在程序中可通过指定 SetProfiler 指定运行时计算用户函数系统函数以及运算指令的耗费时间最后通过 GetProfilerInfo()获得这些信息如果不用 GetProfilerInfo,返回时会自动新建 Profiler 窗口来显示这些信息客户端可以在运行时指定优化信息系统参数
函数具体用法及优化信息结构可参考SetProfilerGetProfilerInfo
#### 调用信息与代码行号
通过关键字\_\_stack_frame 获得调用的堆栈的函数名以及行号
通过关键字**line**获得当前所在的行号
### 函数的返回和退出
参见函数返回以及退出