# 02 控制流与异常 本章汇总流程控制、错误控制与调试相关语句。 ## 目录 - [02 控制流与异常](#02-控制流与异常) - [目录](#目录) - [流程控制语句](#流程控制语句) - [内容](#内容) - [条件语句](#条件语句) - [内容](#内容-1) - [IF](#if) - [IF 表达式](#if-表达式) - [CASE](#case) - [循环语句](#循环语句) - [内容](#内容-2) - [WHILE](#while) - [REPEAT](#repeat) - [FOR](#for) - [BREAK](#break) - [CONTINUE](#continue) - [GOTO](#goto) - [错误控制,以及调试语句](#错误控制以及调试语句) - [内容](#内容-3) - [异常处理 Try Except/Finally](#异常处理-try-exceptfinally) - [ExceptObject 异常对象](#exceptobject-异常对象) - [RAISE](#raise) - [DEBUGRETURN](#debugreturn) - [DebugRunEnv 与 DebugRunEnvDo](#debugrunenv-与-debugrunenvdo) - [MTIC,MTOC 计算运算时间](#mticmtoc-计算运算时间) - [SetProfiler,GetProfilerInfo 优化信息](#setprofilergetprofilerinfo-优化信息) - [调用信息与代码行号](#调用信息与代码行号) - [函数的返回和退出](#函数的返回和退出) ## 流程控制语句 ### 内容 - 条件语句 - 循环语句 - GOTO - 错误控制,以及调试语句 - 函数的返回和退出 ### 条件语句 #### 内容 - IF - IF 表达式 - CASE #### IF IF 语句是由一个布尔表达式和两个供选择的操作序列组成。运行时根据布尔表达式求值结果,选取其中之一的操作序列执行。有两种形式的 IF 语句: ```text If <布尔表达式> then <语句>; ``` ```text 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。 ```text Function IfExample(); 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。 ```text FunctionIfExample2(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 表达式更通用,可读性更高 其基本形式通常如下: ```text if 条件 then 值1 else 值2 ``` 例如 if a>1 then 2 else 1,如果 a 大于 1,整个表达式的结果就是 2,否则是 1。 if 表达式必须存在 else 部分,主要是为了确保表达式始终有确定的返回值。如果没有 else,当条件为假时,表达式的返回值将是不确定的。 注:仅 2025-08-27 以后的语言版本支持此功能 示例: ```text ret:=if x>0 then x*x else 0; return ret; ``` 当 x>0,返回 x\*x;x<=0 时,返回 0。 //多个分支 ```text ret:=if x>0 then x*x else if x<0 then -(x*x) else 0; return ret; ``` 当 x>0,返回 x*x;x<0 时,返回-x*x;x=0,返回 0。 #### CASE 多分支条件语句,Case of 语法一:普通语法。 CASE OF <情况标号表 1>: 语句 1; <情况标号表 2>: 语句 2; ... <情况标号表 N>: 语句 N; [Else 例外语句;] End; 情况标号表的语法为: CASE 区间 1[,CASE 区间 2..CASE 区间 N] CASE 区间的语法为: 区间开始值[TO 区间结束值] 如果没有 TO 语句,则结束值和开始值相同。 例: ```text 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 表达式,在该种情况下,分支语句不支持语句段,只能是单语句表达式。 B:= CASE OF <情况标号表 1>: 表达式 1; <情况标号表 2>: 表达式 2; …(其它的与普通用法一致) 范例: 范例一: ```text a:=3; b:=case a of 1,2:"1/2"; 3,4:"3/4"; else "OTHER"; end; return b; ``` //结果:3/4 范例二: ```text a:=3; b:=case a of 1,2:echo "1/2"; 3,4:echo "3/4"; else "OTHER"; end; return b; ``` //结果:0。打印窗口:3/4 范例三:表达式的用法 ```text 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 - REPEAT - FOR - BREAK - CONTINUE #### WHILE while 语句用于"当满足某一条件时重复执行语句"的情况。while 语句的语法格式: while 布尔表达式 do 语句; 循环结束条件在进入循环体之前测试,若最初的测试值为 false,则根本不进入循环体。为了能使 while 重复能终止,循环体中一定要有影响布尔表达式的操作,否则该循就是一个死循环。 说明: 语句可以是一条语句或是一组语句,如果是一组语句时,这组语句必须使用 Begin … End 标识符来限定,写成复合语句。 例 4:计算从 0 到某个数之间的和。 ```text Function sums(limit); begin sum:=0; num:=0; while num<=limit do begin sum:=sum+num; num++; end; return sum; end; ``` #### REPEAT repeat 语句用于”重复执行语句直到满足某一条件”的情况。repeat 语句的语法格式: ```text repeat 语句段; until 布尔表达式; ``` 说明: repeat 与 while 不同之处有几点: 1,repeat 先做后判断是否结束,while 先判断后做,也就是说 repeat 至少会做一次; 2,repeat 的判断条件是结束条件,而 while 的判定条件是开始做的条件; 3,repeat 和 util 之间可以有语句段,不需要 begin end 来限定,而 while 由于没有结束的特殊标识符,因此当使用语句段的时候必须用 Begin end 来约束。 例 5:求第一个阶乘超过指定值的值 ```text Function MinMultiValue(limit); begin multi:=1; value:=1; repeat multi:=multi*value; value++; until multi>limit; return value; end; ``` #### FOR 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 的值 ```text Function PlusFor(); begin sum:=0; for i:=1 to 100 do //缺省步长,默认步长为1 sum:=sum+i; return sum; end; ``` 例如:计算 1+3+5+……+99 的值 ```text 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 进行变更操作。 适应场景:对于非数字下标的数组,处理比较方便,且效率高 范例一:一维数组的应用 ```text data:=array('a':1,'b':5,'c':3,'d':-2); s:=0; for i,v in data do s+=v; return s; //返回实数7 ``` 范例二:二维数组的应用 ```text 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 值 ```text 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 语句则强制地结束当前循环开始进入下一次循环。 如: ```text While true do Begin i++; if i=100 then continue;//跳过100 if i>=1000 then break; //到1000结束 End; ``` ### GOTO 几乎所有的分支流程控制语句都指令跳转有关,只是绝大多数情况下是有条件跳转,GOTO 是无条件跳转语句,其规则是使用 label 定义标号,使用 goto 可以跳转到指定的标号。 一个 GOTO 的案例: ```text 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 - ExceptObject 异常对象 - RAISE - DEBUGRETURN - DebugRunEnv 与 DebugRunEnvDo - MTIC,MTOC 计算运算时间 - SetProfiler,GetProfilerInfo 优化信息 - 调用信息与代码行号 #### 异常处理 Try Except/Finally 某些函数在执行的过程中可能会自动抛出异常,或者被手动 Raise 抛出异常,这个时候如果没有异常处理运行就会终止。 使用异常处理则可以保护程序继续执行,并可以对异常进行相应的处理。异常的信息可以由 ExceptObject 对象获得,异常处理使用如下模式: ```text Try 被保护的程序执行段 Except 异常处理程序段 End; ``` 例如: ```text Try I:=StrToInt(S); //当S不能转换为整数的时候会产生异常。 Except I:=0; //当发生异常的时候设置I为0; Writeln(ExceptObject.ErrInfo); End; ``` 对于某个程序段可能出现中途返回或者退出,或者中途被异常中断,而某些代码必需要在其后执行的,则采用如下模式: ```text Try 被保护的程序执行段 Finally 保证执行的处理程序段,即便Try Finally之间的语句有返回或者异常产生。 End; ``` 注:try...Except...end 可以使程序在报错时继续向下运行,即主程序不终止。 try...Finally...end 则是该报错时就会报错,即发生错误时程序会报错且终止,只是在中断前会执行完 Finally 中的命令行。 #### ExceptObject 异常对象 在 Except 块中,可以用 ExceptObject 获得当前的异常信息。 ExceptObject 是一个异常对象,包括以下几个成员: ExceptObject. ErrInfo 获得错误的信息串 ExceptObject. ErrLine 错误的行号 ExceptObject. ErrNo 错误号 例如: ```text 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 后跟随一个字符串,该字符串为出错返回的错误信息。 如: ```text A:=-1; If a<0 then raise “a不能小于0”; ``` 运行时报错如: #### DEBUGRETURN 调试返回,后面跟返回值,可在任何地方直接将结果返回,而不是象 RETURN 一样返回到上一级别,这有助于用户调试使用。 如下面示例,返回为 3 而不是 4: ```text 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 记录一段程序运行的时间。 一般使用:以上代码可以计算所耗费的秒数 ```text MTIC ; A:=0; For i:=0 to 99999 do  A++; Return MTOC ; ``` 扩展使用:同时统计多段程序的运行时间 默认 MTOC 和上次 MTIC 匹配,但是也可以指定某个 MTIC 的返回来计算时间 ```text 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 窗口来显示这些信息,客户端可以在运行时指定优化信息系统参数。 函数具体用法及优化信息结构可参考:SetProfiler、GetProfilerInfo #### 调用信息与代码行号 通过关键字\_\_stack_frame 获得调用的堆栈的函数名以及行号 通过关键字**line**获得当前所在的行号 ### 函数的返回和退出 参见函数返回以及退出