精華區beta mud_sanc 關於我們 聯絡資訊
基礎 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.