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

MudOS v21c2

基础 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.

回上一页