			基础 LPC
		作者: Descartes of Borg
		第一版: 23 april 1993
		第二版: 10 july 1993

第七章: 流程控制 (flow control)

7.1 回顾变数

藉由 =、+=、-=、++、--  等运算式, 可以指定或更改变数的值. 这些运算式可
以与 -、+ 、* 、/ 、% 结合使用. 但是, 到目前为止, 我们只告诉你如何用函
式, 以线性的方式写出这些. 例如:
 
int hello(int x) {
    x--;
    write("嗨, x 是 "+x+".\n");
    return x;
}
 
你应该知道怎么写出这个函式并了解它. 不过, 如果你只想于 x = 1  时显示
x 的值怎么办 ?  不然, 如果你想在传回 x  之前, 一直显示出 x  的值直到
x = 1 又要怎么做 ?  LPC 使用的流程控制与 C  和 C++  并无二致.

7.2 LPC 流程控制叙述
 
if(运算式) 指令;
 
if(运算式) 指令;
else 指令;
 
if(运算式) 指令;
else if(运算式) 指令;
else 指令
 
while(运算式) 指令;
 
do { 指令; } while(运算式);
 
switch(运算式) {
    case (运算式): 指令; break;
    default: 指令;
}
 
我们讨论这些东西之前, 先谈一下什么是运算式和指令. 运算式是任何有值的东
西, 像是变数、比较式 (像 x > 5, 如果 x  是 6  或 6  以上, 则其值为 1, 
不然其值为 0) 、指定式 (像 x += 2). 而指令是任何一行单独的 LPC  码, 像
是函式呼叫、值指定式 (value assignment) 、值修改式 (value modification) ......等等.

你也应该知道 && 、||、==、!=、! 这些运算子. 它们是逻辑运算子. 当条件为
真时, 它们传回非零值, 为伪时则传回 0. 底下是运算式值的列表: 
 
(1 && 1)  值: 1	(1  和 1)
(1 && 0)  值: 0	(1  和 0)
(1 || 0)  值: 1	(1  或 0)
(1 == 1)  值: 1	(1  等于 1)
(1 != 1)  值: 0	(1  不等于 1)
(!1)      值: 0	( 非 1)
(!0)      值: 1	( 非 0)
 
使用 && 的运算式中, 如果要比较的第一项测试值为 0, 则第二项永远不会测试
之. 使用 || 时, 如果第一项为真 (1), 就不会测试第二项.
 
7.3 if()

我们介绍第一个改变流程控制的运算式是 if().  仔细看看底下的例子:
 
1 void reset() {
2     int x;
3
4     ::reset();
5     x = random(100);
6     if(x > 50) set_search_func("floorboards", "search_floor");
7 }
 
每一行的编号仅供参考.
在第二行, 我们宣告一个称为 x  的整数型态变数. 第三行则优雅地留下一行空
白, 以明示宣告结束和函式码开始的界线. 变数 x  只能在 reset()  函式中使
用. 
第四行呼叫 room.c 中的 reset().
第五行使用 driver 外部函式的 random() 以传回一个随机数字, 此数字介于 0
到参数减一. 所以在此我们想得到一个介于 0  到 99 的数字.
第六行中, 我们测试运算式 (x>50) 的值, 看它是真是伪. 如果为真, 则呼叫
room.c  的函式 set_search_func(). 如果为伪, 就不可能执行呼叫
set_search_func() .
第七行, 函式将 driver 的控制权交回呼叫此函式的函式 (在这个例子中, 呼叫
reset() 的是 driver 自己) , 也没有传回任何值.

如果你想执行一个以上的指令, 你必须按照以下的方法来做: 
 
if(x>50) {
    set_search_func("floorboards", "search_floor");
    if(!present("beggar", this_object())) make_beggar();
}

注意运算式为真时, 要执行的指令以 {} 包围起来. 这个例子里, 我们再次呼叫
room.c  中的 set_search_func()  来设定一个函式 (search_floor()) , 这个
函式稍后被你设定为: 玩家输入 "search floorboards" 时, 呼叫
search_floor().  (注: 这种例子要看 mudlib 而定. Nightmare 有这个函式呼
叫, 其他 mudlib 可能会有类似的东西, 也可能完全没有这一类用途的函式)
接着, 另一个 if() 运算式检查 (!present("beggar", this_object()))  运算
式是否为真. 测试运算式中的 !  改变它后面运算式的真伪. 在此, 它改变外部
函式 present()  的真伪值. 在此, 如果房间里有个乞丐, present() 就传回乞
丐这个物件 (this_object()), 如果没有乞丐, 则传回 0. 所以, 如果房间里面
还有个活乞丐, (present("beggar", this_object()))  的值就会等于乞丐物件
 (物件资料型态) , 不然它会传回 0.  ! 会把 0  变成 1 , 把任何非零值 (像
是乞丐物件) 变成 0. 所以, 房间里没有乞丐时, 运算式
(!present("beggar", this_object())) 为真, 反之, 有乞丐为 0. 如果房间里
没乞丐, 它呼叫你房间码中定义的函式来制造一个新的乞丐, 并放进房间.  (如
果房间中已经有一个乞丐, 我们不想多加一个 :) )

当然, if() 常常和一些条件一起出现 :). LPC 里, if()  叙述的正式写法为: 
 
if(运算式) { 一堆指令 }
else if(运算式) { 一堆指令 }
else { 一堆指令 }
 
这样表示:
 
如果运算式为真, 执行这些指令.
不然, 如果第二个运算式为真, 执行第二堆指令.
如果以上皆伪, 执行最后一堆指令.
 
你可以只用 if() :
 
if(x>5) write("Foo,\n");
 
跟着一个 else if():
 
if(x > 5) write("X 大于 5.\n");
else if(x >2) write("X 小于 6, 大于 2.\n");
 
跟着 else:
 
if(x>5) write("X 大于 5.\n");
else write("X 小于 6.\n");
 
或是把上面列出来的东西全写出来. 你有几个  else if() 都没关系, 但是你必
须有一个 if() (也只能有一个), 也不能有一个以上的 else . 当然, 上面那个
乞丐的例子中, 你可以在 if() 叙述中重复使用 if() 指令. 举例来说,
    if(x>5) {
        if(x==7) write("幸运数字 !\n");
        else write("再试一次.\n");
    }
    else write("你输了.\n");

7.4 叙述: while() 和 do {} while()

原型:
while(运算式) { 一堆指令 }
do { 一堆指令 } while(运算式);
 
这两者让你在运算式为真时, 一直重复执行一套指令. 假设你想设定一个变数等
于玩家的等级, 并持续减去随机的金钱数量或可承受伤害值 (hp, hit points)
直到该等级变数为 0 (这样一来, 高等级的玩家失去的较多).  你可能会这样做:
 
1    int x;
2
3    x = (int)this_player()->query_level();  /* 这行内容等一下会解释 */
4    while(x > 0) {
5        if(random(2)) this_player()->add_money("silver", -random(50));
6        else this_player()->add_hp(-(random(10));
7        x--;
8    }
 
第三行中呼叫的 this_player()->query_level() 运算式 (译注: 之后内容遗失
, 在此由译者补充) 的意义: 呼叫 this_player() 外部函式, this_player() 
传回一个物件, 为正在呼叫此函式的玩家物件. 再呼叫此玩家物件中的
query_level() 函式. (译注: 补充结束)

在第四行, 我们开始一个回圈, 只要 x  大于 0  就重复执行. 
我们可以用另一种写法:
    while(x) {
(译注: 以下遗失, 由译者补充)
由于 x  本身稍后会一直减 1  直到到 x = 0  , 所以 x = 0 时也是伪值 (为 0). 
第五行以 random(2)  随机传回 0  或 1. 如果它传回 1 (为真),  
(译注: 补充完毕)
则呼叫玩家物件的 add_money() 将玩家身上的银币随机减少 0  到 49 枚.
在第六行, 如果 random(2)  传回 0, 我们呼叫玩家物件中的 add_hp() 函式来
减少 0  到 9  点的可承受伤害.
第七行里, 我们把 x  减 1.
第八行执行到 while()  指令的终点, 就回到第四行看 x 是否还大于 0 . 此回
圈会一直持续执行到 x  小于 1  才结束.

但是, 你也许想在你执行一些指令“之后”再测试一个运算式. 比如用上面的例
子, 如果你想让每个人至少执行到一次指令, 甚至还不到测试的等级:
 
    int x;
 
    x = (int)this_player()->query_level();
    do {
        if(random(2)) this_player()->add_money("silver", -random(50));
        else this_player()->add_hp(-random(10));
        x--;
    } while(x > 0);
 
这个例子真的很奇怪, 因为没几个 mud  会有等级为 0  的玩家. 而且, 你可以
修改前面例子中的测试条件做到同样的事. 不管如何, 这个例子只是要展现出
do {} while() 的如何工作. 如你所见, 此处在回圈开始的时候没有初始条件
 (在此不管 x  的值为何, 立刻执行) , 回圈执行完之后才测试. 这样能保证回
圈中的指令至少会执行一次, 无论 x  为何.
 
7.5 for() 回圈

原型:
for(初始值 ; 测试运算式 ; 指令) { 指令 }
 
初始值:
让你设定一些变数开始的值, 用于回圈之内. 此处可有可无.
 
测试运算式:
与 if() 和 while()  的运算式相同. 当这一个 (或一些) 运算式为真时, 执行
回圈. 你一定要有测试运算式.
 
指令:
一个 (或一些) 运算式, 于每个回圈执行完毕之后执行一次. 此处可有可无.
 
注:
for(;运算式;) {}
与
while(expression) {}
“  完  全  相  同  ” 

范例:
 
1    int x;
2
3    for(x= (int)this_player()->query_level(); x>0; x--) {
4        if(random(2)) this_player()->add_money("silver", -random(50));
5        else this_player()->add_hp(-random(10));
6    }
 
这个 for()  回圈与前面 while()  的例子“完全相同”. 还有, 如果你想初始
化两个变数:
 
for(x=0, y=random(20); x<y; x++) { write(x+"\n"); }

在此, 我们初始化 x  和 y  两个变数, 我们把它们用逗号分开来. 你可以在
for() 三个部分的运算式中如此使用. 

7.6 叙述: switch() 

原型:
switch(运算式) {
    case 常数: 一些指令
    case 常数: 一些指令
    ......
    case 常数: 一些指令
    default: 一些指令
}
 
这样有点像 if() 运算式, 而且对 CPU  也好得多, 但是 switch() 很少有人使
用它, 因为它看起来实在很复杂. 但是它并非如此.

第一点, 运算式不是测试条件. case  才是测试. 用普通的话来读:
 
1    int x;
2
3    x = random(5);
4    switch(x) {
5        case 1: write("X is 1.\n");
6        case 2: x++;
7        default: x--;
8    }
9    write(x+"\n");
 
就是:
 
设定变数 x  为一个 0  到 4  的随机数字.
x = 1 的 case 中, 显示 x  的值, 将 x  加上 1  之后再将 x  减 1.
x = 2 的 case 中, 将 x  加上 1  之后再减 1.
其他情形下, x 减 1.
显示 x  的值.
 
switch(x) 基本上告诉 driver,  变数 x  的值是我们想配合各个 case 的情形.
当 driver 找到一个能配合的 case 时, 这个 case “以及所有在它之后”的
case  都会执行. 你可以使用 break  指令, 在执行一个 case 之后跳出
switch  叙述, 就像其他流程控制叙述一样. 稍后会解释这一点. 只要 switch()
流程还没中断, 任何 x  值都会执行 default  叙述. 你可以在 switch 叙述中
使用任何资料型态:

string name;
 
name = (string)this_player()->query_name();
switch(name) {
    case "descartes": write("You borg.\n");
    case "flamme":
    case "forlock":
    case "shadowwolf": write("You are a Nightmare head arch.\n");
    default: write("You exist.\n");
}
 
对我来说, 我会看到:
You borg.
You are a Nightmare head arch.
You exist.
 
Flamme、Forlock 、或 Shadowwolf 会看到:
You are a Nightmare head arch.
You exist.
 
其他人会看到:
You exist.

7.7 改变函式的流程和流程控制叙述 

以下的指令:

return    continue    break
 
能改变前面提过的那些东西, 它们原本的流程.
首先,

return
一个函式中, 不管它出现在哪里, 都会终止执行这个函式并将控制权交回呼叫这
个函式的函式. 如果这个函式“不是”无传回值 (void) 的型态, 就必须在
return 叙述之后跟着一个传回值. 一个绝对值函式长得大概像这样:
 
int absolute_value(int x) {
    if(x>-1) return x;
    else return -x;
}

第二行里, 函式终止执行, 并回到呼叫它的函式. 因为在此, x  已经是正整数.

continue 在 for() 和 while()  叙述中用得最多. 它停止目前执行的回圈, 把回
圈送回开头执行. 例如, 你想要避免除以 0  的情况:
 
x= 4;
while( x > -5) {
    x--
    if(!x) continue;
    write((100/x)+"\n");
}
write("完毕.\n")
 
你会看到以下的输出:
33
50
100
-100
-50
-33
-25
完毕.

为了避免错误, 每一次回圈都检查 x, 确定 x  不为 0. 如果 x  是 0, 则回圈
回到开头处的测试运算式, 并不终止目前的回圈.
 
用 for()  运算式来说就是:
 for(x=3; x>-5; x--) {
    if(!x) continue;
    write((100/x)+"\n");
 }
 write("完毕.\n");

这样执行起来差不了多少. 注意, 这样子跟前面输出的结果一模一样. 当 x = 1
, 它测试 x  是否为 0, 如果不是, 就显示 100/x, 然后回到第一行, 将 x  减
1, 再检查 x  是否是 0 , 如果为 0, 回到第一行并把 x  再减 1.
 
break
它停止执行流程控制叙述. 不管它出现在叙述里面的任何地方, 程式控制会结束
回圈. 所以, 如果在上面的例子中, 我们把 continue 换成 break, 则输出的结
果会变成像这样:
 
33
50
100
完毕.
 
continue 最常用于 for() 和 while()  叙述. 但是 break 常用于 switch().
 
switch(name) {
    case "descartes": write("You are borg.\n"); break;
    case "flamme": write("You are flamme.\n"); break;
    case "forlock": write("You are forlock.\n"); break;
    case "shadowwolf": write("You are shadowwolf.\n"); break;
    default: write("You will be assimilated.\n");
}
 
下面这个函式跟上面的一样:
 
if(name == "descartes") write("You are borg.\n");
else if(name == "flamme") write("You are flamme.\n");
else if(name == "forlock") write("You are forlock.\n");
else if(name == "shadowwolf") write("You are shadowwolf.\n");
else write("You will be assimilated.\n");
 
但是 switch 叙述对 CPU  比较好.
如果这些指令放在多层巢状 (nested) 的叙述中, 它们会改变最近的叙述.
 
7.8 本章总结

这一章讲的东西实在是太多了, 但是它们马上就用得到. 你现在应该完全了解
if()、for() 、while() 、do{} while()、switch()  , 也该完全了解如何使用
return、continue、break 改变它们的流程. 使用 switch() 要比一大堆 if() 
else if() 来得有效率, 所以应该尽量使用 switch() . 我们也向你介绍过怎么
呼叫其他物件中的函式. 不过, 以后会详细解释这个主题. 你现在应该能轻轻松
松写出一个简单的房间 (如果你已经读过你 mudlib 有关建造房间的文件) 、简
单的怪物、其他简单的物件.

译者:
	Spock of Final Frontier		98.Feb.1.
