第4章 控制结构
4.1 {e1; e2; ; en }
大括号用于包围表达式序列。除最后一个表达式外,每个表达式都按顺序求值,以产生最多一个值。无论成功还是失败,都将继续并计算序列中的下一个表达式。最后一个表达式en将生成周围上下文允许的尽可能多的值。例如:
every {writes(" ",1 to 5);writes(" ",6 to 10)}
会输出
1 6 7 8 9 10
大括号中表达式序列用的分号分隔,也可换成换行,比如上面的可改写成:
every
{
writes(" ",1 to 5)
writes(" ",6 to 10)
}
4.2 every do
我们已经看过了every表达式的简单形式:
every e1
另一种形式是:
every e1 do e2
也就是说对于e1的每一个值,求e2的值。
• e1生成它能生成的所有值。
• 每次e1生成一个值,就去计算e2。
• 表达式e2可能成功也可能失败。
• 每次计算e2时最多生成一个值。
• 当e1失败时,every-do表达式失败。
它看起来更像是带有do的传统语言中的循环,它更直观。对于break和next表达式,它的行为是不同的,见第4.11节和第4.12节讨论。
“Every-expressions”是表达式,可以放在其他表达式中。这个表达式不产生返回值。和&fail一样,它是终止于失败而不返回值。但是,如果它们由break表达式退出,则可以返回值。参见4.11节
4.3 if then else
写成多行形式:
if e1
then
e2
else
e3
if表达式的行为与传统语言中的if语句非常相似,但有以下区别:
• 评估表达式e1以确定其成功或失败。如果e1成功,if表达式的行为类似于e2;如果e1失败,它的行为就像e3一样。
• 表达式e1是生成器的话,e1最多只能生成一个值用于if判断后就终止了。
• e2或e3可以根据上下文的需要生成任意多的值。
与其他语言一样,有一种版本省略了else表达式:
if e1 then e2
如果省略else子句并且e1失败,则整个if表达式失败。
4.4 目标导向的评估
看下面的程序,求某个直角三角形的边长,其边长之和不小于1000且不大于2000个单位
例8 求一个直角三角形
procedure main()
local i,j,m,n
if
i:=1 to 2000 &
j:=i to 2000 &
n:=i*i+j*j &
m:=integer(sqrt(n)) &
m*m = n &
1000 <= i+j+m <= 2000
then
write(i," ",j," ",m)
end
这是一个以目标为导向的求值示例。目标用测试m*m = n & 1000 <= i+j+m <= 2000表示。计算可以被看作是试图生成达到目标的边i和边j。
在其他编程语言中,您可能必须编写几个嵌套循环才能找到这样的三角形。(顺便说一下,它找到的边是33,544和545。)一旦找到了它,就必须跳出多重循环终止查找。
4.5 case of { }
case表达式的行为类似于其他编程语言中的case或switch语句。一般形式:
case e1 of {
e2 : e3
e4 : e5
...
e2n : e2n+1
default : e2n+2
}
等价于
{
(tmp := e1)\1 &
if tmp===(e2) then e3
else if tmp===(e4) then e5
...
else if tmp===(e2n) then e2n+1
else e2n+2
}
相等比较的比较运算符是全称相等检验===。如果给定两个数字,则执行数值检验;如果两个字符串,一个字符串测试。对于其他类型的对象,它测试它们是否为相同的对象。
4.6 while do
一般形式:
while e1 do e2
while表达式类似于其他编程语言中的while语句。它与every表达式的不同之处在于,它每次迭代最多从e1生成一个值。
简单的来说:
• 计算e1
• 如果e1成功,则计算e2
• e2可能成功也可能失败
• 每次迭代最多从e1生成一个值,最多从e2生成一个值
• 一旦e2被求值,它将重新开始对e1求值
• e1失败则while do表达式失败
有一个没有do子句的版本:
while e
它从一开始就重复计算e,直到失败。
例如,这里有一个文件复制程序。它把输入复制到输出。如果您从键盘上运行它,它会回写您键入的每一行。
例9 文件复制
procedure main( )
while write(read( ))
end
read过程每次读取一行输入,并在输入完成时失败。
文件副本不能使用every表达式。读取过程不是生成器。当控制向前移动时,每次输入一行,它将返回一行,但在回溯过程中不能恢复;回溯将回溯过去。
这是一个小工具程序,用于在 C 中为bit位置创建定义宏。你可以输入一系列标识符,它将为每个标识符创建一行 #define。这使得每个标识符都成为一个不同的2的幂。也就是说,它将每个标识符设置为不同bit位置的掩码。
例10 定义位
procedure main()
i := 1
while x := read() do
{
write("#define ",x," ",i)
i := i+i
}
end
4.7 not
一般形式:
not e
如果表达式e失败,“not e”将成功;如果e成功,则“not e”失败。
如果“not e”成功(e失败),它将返回“&null”。
因为not是一元操作符,所以它的优先级比任何二元操作符都高。您几乎总是需要将其操作数用圆括号括起来。
4.8 全真逻辑用全否形式写出来
Icon对表达式进行评估,试图找到一些方法使它们成功。成功意味着至少有一种方法可以使它成功。例如,假设我们想知道列表L中是否有两个元素相等。我们可以这样写:
i:=1 to *L-1 & j:=i+1 to *L & L[i]===L[j]
有时候你想知道某件事是否对所有人都成立,而不是对某个人成立。没有办法直接在Icon中编写。你不得不求助于双重否定。
某事物对所有人都为真,当且仅当没有人对它不为真。
假设我们希望找出一个列表中的所有元素是否都相等。如果所有元素彼此相等,它们就等于第一个元素,所以如果有任何元素不等于第一个元素,那么它们就不都相等。我们可以把测试写成
not (i:=2 to *L & L[1]~===L[i])
4.9 until do
一般形式:
until e1 do e2
until表达式的行为类似于
while not ( e1 ) do e2
简单的来说:
• 计算e1
• 如果e1失败,则计算e2
• e2可能成功也可能失败
• 每次迭代最多从e2生成一个值
• 一旦e2被求值,它将重新开始对e1求值
• 它最多从e1生成一个值,因为一旦e1成功,它就会失败
有一个没有do子句的版本:
until e
它从一开始就反复求值,直到成功为止。
例如,考虑下面的程序来生成最多100,000的斐波那契数。斐波那契数的序列定义如下:前两个斐波那契数为1;后面的每一个数字都是前面两个斐波那契数的和。
例11 使用until编写斐波那契数
procedure main()
local i,j
i:=1
j:=1
until i>100000 do
{
write(i)
i+:=j
i:=:j
}
end
4.10 repeat
一般形式:
repeat e
将反复计算表达式e。除非通过break、return、suspend或fail等显式退出,否则它永远不会终止。
4.11 break
break表达式用于退出任何类型的循环(every、while、until、repeat)。break有两种形式,单独break和包含表达式的break,如下:
break
break expression
对于简单的break,会直接退出当前循环。与循环失败退出不同的是,使用break退出会成功返回表达式的值。如果表达式不存在,则返回&null。
你可以这样写:
if every {... break ...}
then exited_by_break()
else exited_normally()
"break expression"形式会退出循环,并返回表达式生成的值。实际上,表达式会表现得好像不在循环中。如果表达式包含break或next,它们会应用于包含该循环的下一层循环。
例12 使用repeat编写斐波那契数
procedure main()
local i,j
i:=1
j:=1
repeat
{
write(i)
i+:=j
i:=:j
if i>100000 then break
}
end
4.12 next
一般形式:
next
表达式next使它所在的循环开始下一次迭代。
如果它在every表达式中
every e1 do e2
它立即导致回溯到表达式e1。
如果它在“while”或“until”之内
while e1 do e2
或
until e1 do e2
会再次计算表达式e1。
如果它在“重复”范围内
repeat e1
它又开始计算e1。