注册 登录 查询

迷你方式显示论坛 RSS订阅此版新信息  
首页 >> 论坛 >> ┈┋MUD 交流区┋┈ >> 梦幻泥潭 >> 查看帖子
 新帖 新投票 讨论区 精华区 上篇 刷新 平板 下篇


 帖子主题: LPC教学手册
 
头衔 小混混

离线

ivy
门派 秋林拾叶
职务 总舵主
人物等级 炉火纯青
江湖威望 +8
江湖阅历 30
门派贡献 1507
实战经验 22561
文章 534
注册 05-01-09 22:36
发表 2007-04-12 17:52:24 人气:414

LPC的流程控制

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 有关建造房间的文件) 、简
单的怪物、其他简单的物件.


点击这里给我发消息
相关帖子
LPC教学手册 (ivy,5158,2007-04-12 17:41:06)
    LPC基本简介 (ivy,769,2007-04-12 17:43:36)
    LPC的资料型态 (ivy,496,2007-04-12 17:45:51)
    LPC的函式 (ivy,571,2007-04-12 17:47:08)
    LPC的基础继承 (ivy,407,2007-04-12 17:48:56)
    LPC的变数处理 (ivy,343,2007-04-12 17:49:51)
    LPC的流程控制 (ivy,414,2007-04-12 17:52:24)
    LPC的物件资料型态 (ivy,542,2007-04-12 17:53:16)
    好复杂啊。。。。 (gfdsa1,316,2007-05-20 04:00:57)