第六章: 变数 (variable) 处理

MudOS v21c2

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

    回上一页