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