精華區beta mud_sanc 關於我們 聯絡資訊
基礎 LPC 作者: Descartes of Borg 第一版: 23 april 1993 第二版: july 5 1993 第六章: 變數 (variable) 處理 6.1 回顧 現在你應該能利用你 mud 的標準物件庫, 撰寫一些簡單的物件. 繼承能讓你使 用那些物件中已經定義好的函式, 而不用自己去定義. 另外, 你應該知道如何宣 告你自己的函式. 這一章將教你 LPC 的基本元素, 讓你能藉由處理變數來定義 你自己的函式. 6.2 數值與物件 基本上, mud 裡頭的物件都不一樣的原因有兩個: 1) 有的物件擁有不同的函式 2) 所有的物件都有不同的數值 現在, 所有的玩家物件都有同樣的函式. 它們不一樣的地方在於它們自己所擁有 的數值不同. 舉例來說, 名字叫做「Forlock」的玩家跟「Descartes」「至少」 他們各自的 true_name 變數值不同, 一個是 "descartes", 另一個是 "forlock". 所以, 遊戲中的改變伴隨著遊戲中物件值的改變. 函式名稱就是用來處理變數的 過程名稱. 例如說, create() 函式就是特別用來初始化一個物件的過程. 函式 之中, 有些特別的事稱為指令. 指令就是負責處理變數的. 6.3 區域 (local) 和全域 (global) 變數 跟大多數程式設計語言的變數一樣, LPC 變數可以宣告為一個特定函式的「區域 」變數, 或是所有函式可以使用的「全域」變數. 區域變數宣告在使用它們的函 式之內. 其他函式並不知道它們存在, 因為這些值只有在那個函式執行時才儲存 在記憶體中. 物件碼宣告全域變數之後, 則讓後面所有的函式都能使用它. 因為 只要物件存在, 全域變數就會佔據記憶體. 你只有在整個物件中都需要某個值的 時候, 才要用全域變數. 看看下面兩段程式碼: ----- int x; int query_x() { return x; } void set_x(int y) { x = y; } ----- ----- void set_x(int y) { int x; x = y; write("x 設定為 "+x+" 並且會消失無蹤.\n"); } ----- 第一個例子裡, x 宣告在所有的函式之外, 所以在 x 宣告之後的所有函式都能 使用它. x 在此是全域變數. 第二個例子中, x 宣告在 set_x() 函式裡. 它只有在 set_x() 執行的時候存 在. 之後, 它會消失. 在此, x 是區域變數. 6.4 處理變數的值 給 driver 的指令 (instruction) 用來處理變數值. 一個指令的範例是: ----- x = 5; ----- 上面的指令很清楚. 它把 5 這個數值指定給 x 變數. 不過, 這個指令牽涉到 一些對普通指令來說很重要的觀念. 第一個觀念是運算式 (expression). 一個運算式就是有值的一系列符號. 在上面的指令中, 運算式 5 的值指定給變 數 x. 常數 (constant) 是最簡單的運算式. 一個常數就是不變的值, 像是整數 5 或是字串 "hello". 最後一個觀念就是運算子 (operator). 在上面的例子 中, 使用了 = 這個指定運算 (assignment operator). 在 LPC 有更多其他的運算子, 還有更複雜的運算式. 如果我們進入一個更複雜 的層次, 我們得到: ----- y = 5; x = y +2; ----- 第一個指令使用指定運算子以指定常數運算式 5 的值給變數 y. 第二個指令把 (y+2) 的值以加法運算子把 y 和常數運算式 2 加起來, 再用指定運算子指 定給 x. 聽起來一點意義都沒有吧 ? 換另一種方法來講, 使用多個運算子可以組成複雜的運算式. 在前面的範例中, 一個指令 x = y + 2; 裡面含有兩個運算式: 1) 運算式 y+2 2) 運算式 x = y + 2 前面曾提過, 所有的運算是都有其值. 運算式 y+2 的值是 y 和 2 的總和 (在此是 7) ; 運算式 x = y + 2 「也」有其值 ── 7. 所以運算子有兩個重要的工作: 1) 它們「可以」像函式一樣當作輸入. 2) 它們運算起來就像本身有值一樣. 現在, 不是所有的運算子的功能都像 1) 一樣. = 運算子將它右邊的值指定給 x. 但是 + 就沒有這種功能. 而且, 它們兩個也有自己的值. 6.5 複雜的運算式 前面你大概已經注意到, 運算式 x = 5 「本身」也有個值是 5. 實際上, 因為 LPC 運算子如同運算式一樣也有自己的值, 它們能讓你寫出一些非常難解、看起 來毫無意義的東西, 像是: i = ( (x=sizeof(tmp=users())) ? --x : sizeof(tmp=children("/std/monster"))-1) 基本上只是說: 把外部函式 users() 傳回的陣列指定給 tmp, 然後把此陣列元素的數目指 定給 x. 如果指定給 x 的運算式值為真 (不是 0) , 就指定 x 為 1 並 指定 i 的值為 x-1 的值. 如果 x 為偽, 則設定 tmp 為外部函式 children() 傳回的陣列, 並指定 i 為陣列 tmp 的元素數目再減 1. 你曾經用過以上的敘述嗎 ? 我很懷疑. 不過你可能看過或使用與它相似的運算 式, 因為一次合併這麼多的東西在一行裡面, 能提昇你程式碼的執行速度. 比較 常使用 LPC 運算子這種特性的寫法大概像這樣: x = sizeof(tmp = users()); while(i--) write((string)tmp[i]->query_name()+"\n"); 取代這樣子的寫法: tmp = users(); x = sizeof(tmp); for(i=0; i<x; i++) write((string)tmp[i]->query_name()+"\n"); 像是 for()、while() 、陣列......等等東西稍後會解釋. 不過第一段程式碼比較簡潔, 執行起來也比較快. 附註: 在本章總結之後會對所有的 LPC 運算子有更詳細的說明. 6.6 本章總結 你目前知道如何宣告變數, 並了解宣告、使用全域和區域變數之間的不同. 一旦 你熟悉你 driver 的外部函式, 你就能用許多不同的方法顯示那些值. 另外, 藉 由 LPC 運算子, 你知道怎麼改變並運算變數裡頭的值. 這當然對你很有用, 因 為它讓你能做一些事, 像是算出從樹上摘下了多少顆蘋果, 一旦蘋果都摘完了, 就沒有人有蘋果可摘. 很不幸, 你現在只會寫寥寥幾行能執行的程式. 換句話說 , 到下一章以前先別管蘋果的問題, 因為你還不知道如何檢查全部摘下的蘋果數 目和樹上原先的蘋果數目是否相等. 你也不知道特殊的函式 init(), 能讓你給 玩家使用新的指令. 但是你已經準備好撰寫良好而複雜的區域程式碼. 6.7 LPC 運算子 這一段將詳細列出比較簡單的 LPC 運算子, 包括對它們使用的值所作的事 (如 果有值的話), 以及它們自己擁有的值. 在此說明的運算子有: = + - * / % += -= *= /= %= -- ++ == != > < >= <= ! && || -> ? : 下面, 這些運算子將全部用相當簡單的方式說明之, 但是你最好把每個運算子至 少都看過一次, 因為有些運算子的功能「不見得」如你所想的一樣. 不過, 這段 說明可以當作相當好的一個參考. = 指定運算子 (assignment operator): 範例: x = 5; 值: 在完成它的功能之後, 「左邊」的變數值 說明: 把它「右邊」任何運算式的值指定給它「左邊」的變數. 注意, 你只 能於左邊使用一個變數, 也不能指定給常數或複雜的運算式. + 加法運算子 (addition operator): 範例: x + 7 值: 左邊值加上右邊值的總和 說明: 把右邊運算式的值加上左邊運算式的值. 對整數 (int) 型態值來說 , 就表示數值總和. 對字串 (string) 來說, 表示右邊的值接在左邊 的值後面 ("a"+"b" 的值是 "ab"). 這個運算子不改變任何原始值 ( 即變數 x 維持原來的值). - 減法運算子 (subtraction operator): 範例: x - 7 值: 左邊運算式的值減去右邊的 解釋: 除了它是減法以外, 與加法的特性相同. 字串: "ab" - "b" 的值是 "a". * 乘法運算子 (multiplication operator): 範例: x*7 值與說明: 除了這個作數學乘法之外, 與加法、減法相同. / 除法運算子 (division operator): 範例: x/7 值與說明: 同上 += 加法指定運算子(additive assignment operator): 範例: x += 5 值: 與 x + 5 相同 說明: 它把左邊的變數值和右邊的運算式值加起來, 把總和指定給左邊的變 數. 例如: 如果 x = 2... x += 5 指定 7 值給變數 x. 整個運算式的值是 7. -= 減法指定運算子 (subtraction assignment operator): 範例: x-=7 值: 左邊的值減去右邊的值. 說明: 除了減法以外, 與 += 相同. *= 乘法指定運算子 (multiplicative assignment operator): 範例: x *= 7 值: 左邊的值乘上右邊的. 說明: 除了乘法以外, 與 -= 和 += 相似. /= 除法指定運算子 (division assignment operator): 範例: x /= 7 值: 左邊變數的值除以右邊的值. 說明: 除了除法以外, 同上. ++ 後/前增加運算子 (post/pre-increment operators): 範例: i++ 或 ++i 值: i++ 的值是 i ++i 的值是 i+1 說明: ++ 改變 i 的值, 將 i 加上 1. 但是, 運算式本身的值是多少, 要看你把 ++ 擺在哪裡. ++i 是前增加運算子. 這表示它的增加在給 予值「之前」. i++ 是後增加運算子. 它計算在 i 增加之前. 重點在 哪 ? 好, 目前這對你來說無關緊要, 但是你應該記住它代表的意思. -- 後/前減少運算子 (post/pre-decrement operators): 範例: i-- 或 --i 值: i-- 的值是 i --i 的值是 i 減掉 1 說明: 除了是減法以外, 就像 ++ == 相等運算子 (equality operator): 範例: x == 5 值: 真或偽 (非 0 或 0) 說明: 它不更改任何值, 但是 如果兩個值相等就傳回真. 如果兩邊不相等則傳回偽. != 不等運算子 (inequality operator): 範例: x != 5 值: 真或偽 說明: 如果左邊的運算式不等於右邊的運算式就傳回真. 如果它們相等則傳 回偽. > 大於運算子 (greater than operator): 範例: x > 5 值: 真或偽 說明: 只有在 x 大於 5 時為真 如果相等或小於就為偽 < 小於運算子 (less than operator) >= 大於或等於運算子 (greater than or equal to operator) <= 小於或等於運算子 (less than or equal to operator): 範例: x < y x >= y x <= y 值: 真或偽 說明: 與 > 相似, 除了 < 如果左邊小於右邊就為真 >= 如果左邊大於「或等於」右邊則為真 <= 如果左邊小於「或等於」右邊就為真 && 邏輯與運算子 (logical and operator) || 邏輯或運算子 (logical or operator): 範例: x && y x || y 值: 真或偽 說明: 如果右邊的值和左邊的值是非零值, && 為真. 如果任何一邊是偽, 則 && 為偽. 對 || 來說, 只要兩邊任何一個值是真, 則為真. 只有兩邊都是偽值 時, 才為偽. ! 否定運算子 (negation operator) 範例: !x 值: 真或偽 說明: 如果 x 為真, 則 !x 為偽 如果 x 為偽, !x 就為真. 底下有兩個更複雜的運算子, 在此為了存在而存在. 如果它們讓你一頭霧水也別 掛心. -> 呼叫運算子 (the call other operator) 範例: this_player()->query_name() 值: 被呼叫函式的傳回值 說明: 它呼叫右邊這個函式, 而這個函式位於運算子左邊的物件之內. 左邊 的運算式「必須」是一個物件, 而右邊的運算式「必須」是函式的名 字. 如果物件之中沒有這個函式, 它會傳回 0 (更精確一點, 沒有定 義 (undefined) ). ? : 條件運算子 (conditional operator) 範例: x ? y : z 值: 上面的例子裡, 如果 x 為真, 其值為 y 如果 x 為偽, 其值為運算式 z 說明: 如果最左邊的值為真, 這整個運算式的值就是中間的運算式. 不然, 就把整個運算式的值定為最右邊的運算式. 相等 (equality) 的註解: 大家所犯的一種很難除錯、很糟糕的錯誤是把該寫 == 的地方寫成 =. 因為運算 子有它的傳回值, 這兩種情況都能進行計算. 換句話講, 這情形不會產生錯誤訊 息. 但是這兩者的值大不相同. 例如: if(x == 5) if(x = 5) 如果 x 是 5, 則其值為真. 反之則否. x = 5 的值為 5 (所以它永遠為真). if 敘述會判斷 () 之中的運算式是真還是偽, 所以如果你把 = 錯當成 == , 你就會得到永遠為真的運算式. 你會扯掉許多根頭髮, 也搞不清楚到底是為什麼 出錯 :) 譯者: Spock of Final Frontier 98.Jan.26