LPC 入门：

一、解释：

  LPC 是一个程式语言，目前用来在 MudOS(及 LPmud)作业系统下撰写物件，
它是由 Lars Pensj|  所建立，如您所想，LPC 其实是基于 C  语言的语法而
建立其既特殊又方便的程式环境，不过，LPC 并不是编译式的，而是采解译式
完成其物件的载入。在进一步说明 LPC  之前，先提醒读者，LPC 是泥巴世界
中，属于 LPMud  的程式语言，您学习它的目的无非是为了撰写物件建构您心
目中的幻想世界。

二、LPC 的组成--叙述：

  任何程式语言都是由叙述堆砌而成的，每一个 LPC　叙述均以分号 ';'  结
尾，而叙述则由运算子、函数、变数等构成，我们以最简单的实例来说明：

  int a = 3;

  上一行是 LPC  合法的叙述，「int” 是 LPC  的保留字，也就是我们不能
以「int” 这种保留字作为其它用途，事实上，「int” 是用来宣告整数变数
用的，如本例，既宣告了 a  这个整数变数，在宣告的同时，也告诉 MudOS
它的值是 3。既是变数，在往后是可以改变变数 a  的值，因此，我们称此种
宣告为，「给定初始值”；而「给定初始值”除了在宣告的同时给定外，还可
以在用到变数 a  之前再给，如 「a = 3;”。 细心的读者也许注意到我们在
每个叙述后面加上分号。在进一步说明 LPC  之前，先讨论范例中的「=”。
很多第一次接触 LPC  的人都会把，「=” 跟「==”搞混，前者是指定值用，
后者是判断前后运算元是否相等，也有人把「=” 想成「<-”，即 「a = 3;”
想成「a <- 3;”。 「=” 跟「==”在语法上是完全不同的，不幸的，LPC 无
法判断使用者是企图，因此，若使用者用错「=” 跟「==”所得的结果会出乎
意料之外，请小心使用。

三、结构化--叙述的组合：

  LPC 是一种结构化的语言，它可以将一堆叙述组合起来，将这些组合起来的
叙述看成一个叙述，而这种结构化的叙述最常见的型式是由「{”及「}”所□堸_来的叙述方块。我们同样以实例来说明。

  int add (int a, int b)
  {
    return (a + b);
  }

  上面定义了一个函数，add， 在使用时需两个变数，暂时称为 a, b， 这两
个变数都是整数变数，此函数「add” 代表「return (a + b);” 这个叙述。
函数是 LPC  这种结构化程式语言的命脉，往后用到的机会是 100%。 不知读
者是否有注意到那一对大括号？让我们先看看函数的定义方式。首先，函数可
概分为两部份：函数宣告、函数主体。函数宣告即第一行

  int add (int a, int b)

除了上面说明的部份，它还宣告函数 add  会传回一个整数值，这点以后再详
细讨论，另外，函数主体即由一对大括号所组合起来的部份。其传回值部份跟
变数的宣告是一样的，等讨论变数的时候再看。

四、运算子：

  我们以简单的观念来看运算子，在我们的求学时代不就有四则运算吗？加法
是用符号「+” 来表示，「+” 称为运算子，而运算子「+” 左右两边的数字
则称为「运算元”，就算在方程式里也是透过运算子结合运算元来表示。在
LPC 中的运算子当然不止「+”、「-”、「*”、「/”，还有专门用在位元运
算的位元运算子：「&”、「|”、「^”， 及用在逻辑运算的逻辑运算子：
「&&”、「||”、「!” 等等，详细的内容请看任何 C  语言入门的书。
  在此我们看看 LPC  将指定运算子「=”与其他运算子结合的特殊情形：

  a += 3;
  
  上面那一行叙述其实等于

  a = a + 3;

  在此不多说，再举几个例子，底下分左右两边，它们是相等的：

  a -= b;		<-->	a = a - b;
  a &= b;		<-->	a = a & b;

五、型态：

  在 LPC  程式语言里，变数有如我们在国中数学的观念，然而在电脑的世界
中，我们为了节省记忆体，会将整数、实数、字串等等型态的变数区分开来，
因为变数有型态的区别，在 LPC  中使用变数前要先宣告其型态。在后面的章
节中会详细介绍各种不同的变数型态如何使用，在此我们只介绍型态种类：

  int		整数，可以说是目前电脑最常用的型态，处理速度也快。
  float		实数，在电脑中，通常称为浮点数，处理速度上慢了点。
  string	字串，这是 LPC  跟 C  不一样的型态，以后会详细说明。
  mapping	对应，这是 LPC  特有的型态，有点类似阵列，我们可以称
		为关联阵列，一样，以后会详细说明。
  object	物件是最特别的型态，而物件在泥巴的世界中也有着非常重
		要的角色，我们会常常提到。
  function	这个型态非常特殊，会另篇介绍。
  void		这个型态只有函数用得到，它说明函数不必传回值。
  mixed		这也是特殊型态，它说明不用检查型态，也可以说是这种型
		态的变数可能是任何型态。
  阵列		阵列是用星号来表示，可以上面所有型态的阵列，宣告方式
		如： int *a; object *obj; mixed *m; 等等

六、修饰子：

  有一些特殊的型态可以加在前一节基本型态的前面，底下先列出简单的说明
后再举例来看。

  varargs	用在函数，用来说明该函数的参数个数是可变动的，或者说
		告诉驱动程式，不用因该函数的参数个数变动而传回错误。
  private	指示变数、函数是「私有的”，也就是不能有其他地方使用
		如：在物件 a 定义 private int add();则不能用下列呼叫
		call_other (a, "add", ...); 或
		a->add(...);
  public	MudOS v21 版(含以前)使用，用在函数上，说明该函甚至以
		private 继承也可以呼叫。
		MudOS v22 版(含)以后已取消，改为预设值，不需特别指定
  static	MudOS v21 版(含以前)使用，此修饰子用在变数与函数效果
		不同，作用在函数上同 private, 作用在变数上的时候，该
		变数不能用「save_object()”或「restore_object()” 来
		「储存”与「取回”该变数存在档案的值，也就是说，该变
		数的值离不开定义它的物件。
  		MudOS v22 版(含)以后可在 driver 的 options.h  中取消
		SENSIBLE_MODIFIERS  的定义，或用下列新修饰子：
  nosave	用在变数上，用来达成上面的目的：该变数的值离不开定义
		它的物件。
  protected	用在函数上，同 private。
  nomask	用来指示驱动程式，该变数或函数不能再被定义，善用它可
		避免被不肖巫师利用重新定义函数来做违法的事。

七、继承：

  在 LPC  中有物件导向程式设计的观念，其中继承是用关键字「inherit”
来达成，底下是语法与说明：

语法: inherit 档案完整路径名;

说明:
    档案完整路径名 要用双引号括起来. 如:
        inherit "/std/user.c"
    通常, 常用的"被继承"物件会由系统用 #define 定义常数, 如
        #define USER_OB "/std/user.c"
    那么, 就可以用
        inherit USER_OB;

  在稍后「前端处理”一节中我们会对「#define” 作解释。
  inherit 叙述提供 LPC 继承的能力，没错，就是 OOP 的概念，继承让物件
可以很安全跟很方便的使用其他物件所定义的函数跟变数。由于 MudOS  驱动
程式内部会储存整体变数，然后把整体变数跟档案分开编译，因此各个物件可
以彼此分享已经编译好的物件，当然，这些物件彼此有自己的整体跟局部变数
可以使用，例如: 物件 A, B, 都继承 C， 编译 A 或 B 的时候不会让编译器
重新编译 C，不过，如果 A 或 B 有重新定义整体变数的话，不会引起重复定
义的错, 但是在 A 或 B 中会让 C  的整体变数失效，也就是 A 或 B 的宣告
会「盖过” C  中的宣告，如果没有在 A 或 B 中重复宣告的话，当然会使用
 C  的宣告啰。还有，上述的重复宣告并不影响 C  使用自己的变数的运作。

  现在再假设 A 继承了 B，物件 A 可以定义跟物件 B  中相同名称的函数或
变数，如前所述，A 的引用会自动「盖过”B 的，不过如果只是这样的话, 继
承就不会那么特殊了。如果你因为变数同名，又想引用 B  的函数或变数，可
以使用「::”来引用。例如物件 A 继承了 B， 两个各自定义 query_long();
在物件 A  中使用 query_long() 的时候，是使用到 A  定义的函数，如果要
在 A 使用 B 的 query_long()， 那就可以用 B::query_long(); 不过，如果
是同名变数的引用，则只能透过 B  中定义的函数来存取，这是为了物件的概
念产生的。
  此外，B 可以限定是否要让别的物件继承函数或整体变数，限定的方式是以
static  来修饰函数或变数。
  另外一点，如果 B  重新编译的话，A 也只会用到旧的 B，除非 A 在 B 之
后也重新编译过。
  LPC 允许多重继承，也就是说，在一个物件内可以写多行的 inherit  叙述
假设 special.c  继承 weapon.c 跟 armor.c，而 weapon.c 跟 armor.c  也
都提供了自己的 query_long()，假设要让 special.c 有时像 weapon， 有时
像 armor，要像 weapon， 就用 weapon::query_long()，要让它像 armor 就
用 armor::query_long()，要用自己的 long，就直接定义 query_long()  并
直接呼叫来用。「::”又称为「引用子”， 或是「视域子”， 也就是让你引
用祖先的函数，或者把函数加上适当的视域范围，这样就不会「用错”，也就
是不会让编译程式「看错”，所以称为「视域子”。
  请参考上一节跟「修饰子”有关的说明。那边会有如何隐藏函数跟变数宣告
的说明（在前面有提到 static 这个修饰子）。

八、前端处理：

  前端处理是在 LPC  编译程式编译之前的处理工作。通常做下列工作：

  。  分享「定义”及程式码（用 #include）。
  。  巨集定义（用 #define，#undef）。
  。  条件编译（用 #if，#ifdef，#ifndef，#else，#elif，#endif）。

　--- 前三项是跟 C  语言一样的。

  。  除错（用 #echo）。
　。　编译指示（#pragma）。
　。　格式化文字字串（用 @, @@）。

  前端处理指示子是用「#” 当开头的，而且一定要在该行的第一个字。

  １、分享「定义”及程式码
      ^^^^^^^^^^^^^^^^^^^^

    语法一：#include <FILE.h>
    语法二：#include "FILE.h"

    说明：
	语法一与语法二的不同在于搜寻 FILE.h 时的顺序，语法一是系统内
    定的，该顺序定义在启动驱动程式时组态档中「include directories”
    而语法二则从出现「#include "FILE.h"” 的档的目录下找。
        档案Ａ若出现 #include 前端处理指示，则驱动程式会把 FILE.h 读
    进档案Ａ中 #include 出现的地方。这种方式的分享程式码会在编译物件
    时重新编译分享程式码，因此跟继承是完全不一样的。再者，如果分享程
    式码中定义的变数或函数名称与档案Ａ定义的变数或函数名称一样的话，
    驱动程式会出现「duplicate-name error”的重复定义错误讯息。若用继
    承的话就不会有此重复定义错误出现，如继承那节所述。

  ２、巨集定义
      ^^^^^^^^

    语法一：#define 识别字 巨集定义字串
    语法二：#define 识别字(参数) 巨集定义字串

    说明：
	巨集定义可以用简单的「识别字”取代较长的「巨集定义字串”，事
    实上巨集定义字串可以是好几行。不用前一段的分享而改用巨集定义的原
    因是巨集定义有识别字可资识别，在引用时容易记忆。再说，巨集定义通
    常都定义在一个专门的档中，以分享的方式含括进来，以利往后引用。
        习惯上我们会把巨集定义的识别字用大写字母，而且巨集定义会放在
    档案的前面，如果有程式码分享，会放在那之后。底下举例说明：

        #define STACKSIZE 40

    上例以 STACKSIZE 代替 40, 因为 STACKSIZE  比 40 更具识别与记忆性

        #define INITCELL(x) 2*x

    语法二的宣告方式在引用时参数会随引用而改变。如 INITCELL(2)  等于
    2*2， 而 INITCELL(10) 等于 2*10。 值得注意的是在定义巨集时，识别
    字与参数间不可有空白字元，而呼叫时却可以。即 

        #define INITCELL (x) 2*x  事实上是跟我们上一例不一样，它等于
    把 "(x) 2*x" 用 INITCELL  取代，所以 INITCELL(2) 等于 (x) 2*x(2)

        若我们要取消巨集定义，可以用「#undef 识别字”来达成。

  ３、条件编译
　　　^^^^^^^^

    语法：
	#ifdef <识别字>
        #ifndef <识别字>
        #if <运算式>
        #elif <运算式>
        #else
        #endif

    说明：
	条件编译指示子可以给你的程式码增加弹性，当「识别字”有（或没
    有）定义时，编译程式会作出不同反应。条件编译可将该物件应用在不同
    版本的驱动程式上，或提供管理者一些弹性限制功能，如是否开放玩家连
    线，开放上线人数等。

        <识别字>的定义方式如上一段所说。不过也可以引用驱动程式的定义
    而<运算式>则是一个常数运算式，也就是在编译阶段就可以决定值。不过
    其效果只有布林值，也就是０或非０。<运算式>可包括下列任何运算子：
	 ||, &&, >>, <<,	<-- 逻辑运算
	 +, -, *, /, %,		<-- 四则运算
 	 ==, !=, <, >, <=, >=,	<-- 比较运算
	 ?:			<-- 条件运算
	 ()			<-- 组合程式码

	#ifdef 识别字    	<-- 等于 --> #if defined(识别字)
	#ifndef 识别字		<-- 等于 --> #if !defined(识别字)
	#elif 运算式		<-- 等于 -->
		#else
		#  if 运算式	<-- # 跟 if 有空白是合法的，但 #  一
	 	#  endif	    定要在该行的第一个字的位置。

    ☆ #if 0 可以用来将一段程式码视为注解。

    例一：

    #if 0
	// 因 <运算式> 的值是 0，所以
        // 本例中的这段程式码不会被编译，效果就像注解一样
        // 

        write(user_name + " has " + total_coins + " coins\n");
    #else
        // 
        // 本段程式码则永远会被编译。
        // 

        printf("%s has %d coins\n", user_name, total_coins);
    #endif

    例二：

    // 本段程式码可以在 TMI's /adm/simul_efun/system.c 找到，
    // 请自行研究
    #ifdef __VERSION
          string version() { return __VERSION__; }
    #elif defined(MUDOS_VERSION)
          string version() { return MUDOS_VERSION; }
    #else
    #  if defined(VERSION)
          string version() { return VERSION; }
    #  else
          string version() { return -1; }
    #  endif
    #endif

  ４、除错
　　　^^^^

    语法：#echo 这一段讯息会在驱动程式启动时印出来。

    我们可以用 #echo  来告诉驱动程式印出一段讯息，如此有利于除错。
    通常我们都会配合条件编译来除错。

  ５、编译指示
　　　^^^^^^^^

    语法：#pragma 关键字

    说明：
	关键字的种类有：
	。 strict_types		严格的型态检查
	。 save_binary		储存编译好的二进位档，利下次快速载入
	。 save_types		储存函数的参数型态，继承时可用来检查
	。 warnings		对各种危险的程式码提出警告
	。 optimize		作第二回编译，以利最佳化处理
	。 error_context	当出现编译错误时，印出某些程式内容

  ６、格式化文字字串
      ^^^^^^^^^^^^^^

    语法一： @标记		<-- 开始
		.
		. 叙述区块
		.
	     标记		<-- 结束
    语法二：@@标记              <-- 开始
                .
                . 叙述区块
                .
             标记               <-- 结束

    说明：
	语法一是产生字串，而语法二是产生字串阵列，也就是语法二可以方
    便用来换页。底下举例来说明：

        int help() {
            write( @ENDHELP
        This is the help text.
        It's hopelessly inadequate.
        ENDHELP
              );
            return 1;
        }

	上一段跟下一段相同

        int help() {
            this_player()->more( @@ENDHELP
        This is the help text.
        It's hopelessly inadequate.
        ENDHELP
              , 1);
            return 1;
        }

	也跟下一段相同

        int help() {
	  write ("This is the help text.\n");
	  write ("It's hopelessly inadequate.\n");
	  return 1;
	}

	也跟下一段相同

        int help() {
            this_player()->more( ({ "This is the help text.",
                  "It's hopelessly inadequate." }), 1);
            return 1;
        }

九、函数的原型：

  LPC 的函数的原型宣告非常非常类似 ANSI C。 如果你在还没定义函数前就
想引用到某函数，那么，没有函数的原型宣告的话很可能造成错误，甚至连参
数格式都不对了。

  一般的函数的原型宣告如下：

      函数传回型态 函数名称(资料型态 参数1, 资料型态 参数2, ...);

  也可以不给参数名称，宣告方式如下：

      函数传回型态 函数名称(资料型态1, 资料型态2, ....);

  如：string query_long();
      int smile(string name);
      int smile (string);

十、重复使用程式码：

  除了前面提的前端处理「#include”、「#define” 可提供重复使用程式码
外，LPC 还提供，前面提到的继承「inherit”、「函数” 及下列方便的关键
字可让我们重复使用程式码。

  。 for
  。 while/do
  。 if
  。 switch/case

  １、for 回圈
      ^^^^^^^^

    语法：
	for (表示式-1; 表示式-2; 表示式-3) {
          叙述;
          ...;
	}

    说明：
	其中的「表示式-1”是用来初始化 for  回圈。也就是在回圈执行之
    前就先执行「表示式-1”，此后每做完一次回圈就先判断「表示式-2”，
    如果「表示式-2”为真（成立）就继续下一次的回圈，当然，第二次以后
    的每个回圈在执行前会先执行 表示式-3。
	再说一次，「表示式-1”是第一次回圈执行前做的，「表示式-2”是
    在每次回圈结束前用来判断是否继续用的，「表示式-3”是除第一次外在
    回圈执行前执行的。其中「表示式-2”用来判断是否要继续回圈，如果在
    该次回圈结束后计算「表示式-2”的值非０就继续执行。
	如果在回圈内执行到 break  叙述，则强迫停止该回圈，并跳出该回
    圈，如果执行到 continue 叙述，则停止「该次”回圈，并继续下一次回
    圈。
	底下有一个典型的 for 回圈：

        int i;

        for (i = 0; i < 10; i++) {
          write("i == " + i + "\n");
          write("10 - i == " + (10 - i) + "\n");
        }

  ２、while 回圈
      ^^^^^^^^^^

    LPC 的 while 回圈跟一般的 C 一模一样, 语法如下:

        while (表示式)
          叙述;

    或

        while (表示式) {
          叙述;
          叙述;
          .
          .
        }

    在 while 回圈内的叙述会在表示式的值不为 0 的情况下执行, 也就是
  该表示式为"真". 如果 while 的叙述有 break 叙述, 则会无条件跳出回
  圈, 如果碰到 continue 叙述, 则会跳过该次回圈, 继续下一次的回圈,
  这种用法就像在 for 回圈里说的一样. 
    值得注意的是, while 比 for 回圈更容易造成无穷回圈, 如果这样的
  话, 会造成系统莫大的负担, 甚至是当机.

    底下看一个实际的例子.

        int test(int limit)
        {
          int total = 0;
          int j = 0;

          while (j < limit) {
                if ((j % 2) != 0)
                        continue;
                total += j;
                j++;
          }
          return total;
        }

    上面的结果会跳过奇数(在 if ((j % 2) != 0) continue; 这儿跳过), 
  而把 0 到 limit-1 之间所有的偶数加起来并传回去.
    底下的 for 回圈可以做到相同的功能

        int test(int limit)
        {
          int total, j;

          for (j=total=0; j<limit; j = j+2)
                total += j;
          return total;
        }

  或
        int test(int limit)
        {
          int total=0, j=0;

          for (; j<limit; j += 2)
                total += j;
          return total;
        }

  ３、if 条件叙述
      ^^^^^^^^^^^

    LPC 的 if/else 跟 C 一模一样. 语法如下:

    单一叙述:
    if (表示式)
      叙述;

    多重叙述:
    if (表示式) {
        叙述;
        叙述;
        ....;
    }

    if/else 的方式:

    if (表示式) {
        叙述;
        叙述; 
        ....;
    }
    else {
        叙述;
        叙述;
        ....;
    }

    多重 if/else, 或称为巢状的 if/else

    if (表示式) {
        叙述;
        叙述;
        ....;
    }
    else if (表示式-2) {
        叙述;
        叙述;
        ....;
    }

    注意: if/else 的层数没有明显的限制.

- - - - -

    另外一种受欢迎的 if/else 叙述, ?: 配对, 这也跟 C 的用法一样.

        表示式 ? 叙述-1 : 叙述-2;
    如果表示式为真执行 叙述-1, 否则执行 叙述-2

    底下列出实际例子.

    if (表示式)
        叙述-1;
    else
        叙述-2;

    跟下面的方式一样:

    var = 表示式 ? 叙述-1 : 叙述-2;

  ４、switch 多重条件叙述
      ^^^^^^^^^^^^^^^^^^^

    LPC 的 switch 叙述跟 C 几乎一模一样. 唯一的差别在于 LPC 允许使用
  字串跟整数. 一般的语法如下:

        switch (表示式) {
          case 条件-1:
                叙述;
                .....
                break;
          case 条件-2:
                叙述;
                .....
                break;

          .
          .
          .
          default :
                叙述;
                .....
                break;
        }

    一般来说, switch 能做到的都可以用 if/else 来达成, 如果有很多种
  状况的话, 用 switch 会比较容易阅读跟除错. 再说, if/else 可能配对
  错误而造成意想不到的状况. 每个条件都要用 break 隔开, 如果没有 break
  的话, 不会结束该条件内的叙述. 这种现象可以让你很容易就做到让多种
  状况都执行相同的叙述.

    上面的 switch 叙述约等于下面的程式码.

        tmp = 表示式;
        if (tmp == 条件-1) {
          叙述;
          ...;
        }
        else if (tmp == 条件-2) {
          叙述;
          ...;
        }
        .
        .
        .
        else {          // 等于 default 部份
          叙述;
          ...;
        }


