发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc程序和编程环境 (1)
发信站: 广州网易BBS站 (Mon Apr 13 23:22:57 1998), 转信

发信人: maht (Trill), 信区: creator 
标　题: Lpc程序和编程环境 
发信站: 天地创造 (Tue Oct  7 21:07:59 1997), 转信 
 
 
                第一章 Lpc程序和编程环境 
 
                第一节，编程环境 
 
 
    通常我们所见到的Mud大多是LpMud。LpMuds使用Unix的指令和 
文件结构。如果你对Unix有所了解，那么LpMud中的一些指令和它的 
文件结构与普通的Unix基本一样。如果你从未使用过Unix，那么它 
Dos不同的是在文件的路径用"/"，而不是Dos的"\". 
一个典型的LpMud的文件是这样的: 
    /clone/player/player.c 
"/clone/player/"是路径，player.c是文件名。 
在多数的LpMud中，下面这些的基本的Unix指令是可以使用的： 
pwd, cd, ls, rm, mv, cp, mkdir, rmdir, more, tail, cat, ed 
如果从未使用过Unix，那么下面这张表也许是有用的。 
pwd: 显示当前目录 
cd: 改换你当前的工作目录，和Dos的cd一样。 
ls: 列出指定目录下的所有文件，如果没有指定任何目录，那就列 
    出当前目录底下的文件。和Dos的dir一样。 
rm: 删除一个文件 和Dos的rmdir一样 
mv: 从命名一个文件 和Dos的move一样 
cp: 复制一个文件 和Dos的copy一样 
mkdir: 创建一个目录 
rmdir: 删除一个目录 
more: 按页显示一个文件在你的当前屏幕。 
cat: 显示整个文件。和Dos的type一样。 
tail: 现在一个文件的结尾几行。 
ed: 允许你使用Mud自带的编辑器，编辑一个文件。 
 
                第二节 Lpc程序 
 
 
2.1 Lpc程序。 
    Lpc的程序看起来和一般的C区别不断大，语法基本一样，但是 
Lpc和一般的语言有着根本的不同，Lpc程序是编写一个一个的"Object"。 
这有什么区别呢？一般的程序是在执行过程中，通常有一个明显的 
开始和和结束。程序从一个地方开始，然后顺序执行下去，到了结 
束的地方就中断了。Lpc的Object不是这样的。 
   所谓的不同的Mud，实际上是一些不同的Lpc的Object在一个Driver 
的上的各种不同的表现。也就说，Lpc的Object是运行在一个Driver 
上的。这些Object组成了LpMud的丰富多彩的世界。Driver几乎不参 
与创建你所接触到的世界，它所完成的工作只是让那些Lpc的Object 
活动起来。Lpc的Object可能没有明显的开始和结束的标志，它可能 
永远在工作。  和一般的程序一样，Lpc“程序”也是由一个或多个 
文件组成。一个Lpc的Object是按如下方式被执行的：Driver把和这 
个Object相关的文件读入内存，然后解释执行。但是要记住的是， 
读入内存，并不是说，它就开始按顺序执行。 
 
2.2 Driver和Mudlib关系 
  在有些游戏中，整个游戏包括Driver和游戏世界都用C写好，这样 
能快一些，但是游戏的扩充性很差，巫师们不可能在游戏进行中添 
加任何东西。LpMud则相反。Driver理论上应该和玩家所接触的世界 
几乎没有任何直接的联系。游戏世界应该是自己独立的，而且是“即 
玩即加”的。这就是为什么LpMud使用Lpc作为编程语言的原因。它 
允许你创建一个游戏世界，再由Driver在需要时读入解释执行。Lpc 
甚至比C更简单，更容易明白，但是它可以创建一个可以让许多人在 
上面游戏的世界。 
  在你写完一个Lpc的文件时，它存在于主机的硬盘上。在游戏进行 
中，当需要整个Object时，这份文件将被调入内存，一个特殊的函 
数被调用来初始化这个Object的一些变量。现在你不用关心什么是 
变量，什么是函数以及游戏本身怎样来调用这个object，你只要记 
住Driver从硬盘中读入一个文件，然后放在内存中，如果没有任何 
错误的话。 
 
2.3 一个Object被装人内存。 
  一个Object不会也不必有一个特点的地方让Driver去执行它，通 
常Drvier会在Object中找一个地方去初始化它。一般都是这个函数 
叫做create()。 
  Lpc的Object是一些变量(它的值能变化)和函数(函数通常是用来 
操纵那些变量的一段程序)的组合。函数操纵变量的方式有：调用 
其他函数，使用Driver内部定义的函数(efun)，基本的Lpc表达式 
以及流控制。 
  我们来看个变量的例子: wiz_level。这个变量记录你的巫师等 
级，如果是0呢，通常是普普通通的玩家了。这个值如果越大就表 
示你的巫师等级越高。这个也同时控制了你能不能执行一些巫师 
指令。基本上来说，一个Object就是一些变量“堆”在一起的东 
西。一个Object改变了，也就是某一个或者一些变量改变了。 
  总的来说，一个Object如果要被内存中的另一个Object调用， 
Driver会去找这个Object的那堆变量放在哪里，如果这些变量没 
有值，那么Driver会调用一个特定的函数create来初始化这些变 
量。 
  但是create()不是Lpc代码开始执行的地方，只是大多数的Object 
从这里开始。事实上，create()可以不存在。如果这个Object不 
需要对变量初始化，那么create()可以不存在。那么这样的Object 
开始执行的地方就完全的不同于一般的Object，可以从任何地方开 
始。 
 
  那么究竟什么是Lpc的Object？Lpc的Object就是一堆变量的集合， 
它带有一个或者更多的函数来操纵控制这些变量，函数的排列顺序 
是无所谓的，随便那个排在前面对这个Object的特性没有影响。 
 
                                        (待续) 
 



发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc编程和编程环境(2)
发信站: 广州网易BBS站 (Mon Apr 13 23:24:26 1998), 转信

⑿湃 maht (Trill), 信区: creator 
标　题: Lpc编程和编程环境(2)
发信站: 天地创造 (Wed Oct  8 14:49:39 1997), 转信 
 
 
2.3 代码风格 
  在上面说过函数的顺序对这个Object的特性是毫无影响的。 
但是一个有着良好代码风格的程序对LpMud是很重要的。因为 
LpMud通常不会也不可能是一个人完成的，如果程序没有较好 
的可读性，那么别人理解你的“作品”是很困难的。而且有个 
良好的程序风格能给人以优雅的感觉，因此希望大家写的Lpc 
程序能有个好的风格。大家中的有些人可能以后会加入XO team 
创建自己梦想中的世界，我们要求你采用如下的格式书写程序。 
 
2.3.1 头文件 
  在一个文件的开头是一段说明。采用如下格式： 
 
/* /u/trill/obj/test.c 
 * from XO Object Library 
 * 测试用的Object 
 * created by trill 19970808 
 * version @(#) test.c 2.1@(#) 
 * last modified by trill 19971008 
 * 测试tell_wizard这个simul_efun 
 */ 
第一行是这个文件的绝对路径，就是全路径。 
第二行是它所在的Mudlib 
第三行是它的功能的简单的描述，可以超过一行。 
第四行是这个文件的作者和创建时间。 
第五行是它的版本号，可能做了多次修改，甚至可能会重写， 
      这个数字2.1标志了它大概做过多少次改动。 
第六行是最后一次修改的人和时间。 
第七行是最后一些修改什么东西。 
 
  对于一个Object我们要求必须有这样一段说明，特别是前面 
的五行必须存在，如果做了改动那么最后两行也要加上。 
这样一般的一个Object，我们从这段说明就能了解到一些很重 
要的信息。 
 
下面是include一些文件和继承(inherit)一些Object。 
 
#include  
#include "include/test.h" 
 
inherit NPC; 
 
先系统的文件，后自己定义的一些头文件。特别要求的是必须 
有个和这个Object同名的".h"文件，比如"test.h"放在这个 
Object所在的目录的下一级目录"include"底下，就是说在 
include部分的最后一行是#include "include/test.h"。 
在test.h定义所有在test.c用到的函数的原形，以及定义一些 
宏和常量。 
  这样做的好处是： 
第一不用出现一个函数在引用时没有说明， 
第二如果想知道这个Object有什么函数，直接看这个文件就可以 
了，不必去看那个test.c，可能test.c非常长。 
第三如果建立一个help系统，用来查询每个Object存在的函数， 
那么这样直接去读test.h就可以，否则是一件很麻烦的事。 
 
关于inherit我们在继承部分再说。 
 
2.3.2 变量说明 
 
  在变量说明部分，大家最好在每个变量后面加一个简单的说明。 
 
2.3.3 函数 
 
  一个Object的函数的顺序和名字对这个Object的表现是毫无影 
响的。但是为了让这个Object有良好的可读性，我们要求一个Object 
的函数按如下方式排列和命名： 
  首先是变量的接口部分，这些函数统一用Set+变量名来改变该 
变量的值，用Get+变量来返回变量的值。比如 
 
static int level; 
 
void SetLevl(int i) 
{ 
    level = i; 
} 
 
int GetLevel() 
{ 
    return level; 
} 
  其次是一些操纵和控制变量的一些函数。比如 
 
void AddLevel(int i) 
{ 
  一个Object的函数的顺序和名字对这个Object的表现是毫无影 
响的。但是为了让这个Object有良好的可读性，我们要求一个Object 
的函数按如下方式排列和命名： 
  首先是变量的接口部分，这些函数统一用Set+变量名来改变该 
变量的值，用Get+变量来返回变量的值。比如 
 
 static int level; 
 
void SetLevl(int i) 
{ 
    level = i; 
} 
 
int GetLevel() 
{ 
    return level; 
} 
  其次是一些操纵和控制变量的一些函数。比如 
 
void AddLevel(int i) 
{ 
    level += i; 
} 
 
这两类函数要求每个单词的第一字母大写。 
 
  再是一些Object所能做的事件(event)，比如战斗，结婚等等。比如 
 
void eventQuit() 
{ 
   ... 
} 
 
  这些函数要求事件的每个单词的第一字母大写，比如eventFight, 
eventMarry等等。 
 
  再下面的是由Driver调用的一些函数，比如create(), heart_beat, 
setup()。 
 
  最后是一些这个Object自己私有的函数，完成一些特别的功能。这些 
函数通常让要求每个单词的小写，中间用下划线(_)隔开。 
 
  要注意的是每个函数之间用一个空行隔开。 
 
  这些是对一个文件的整体要求，如果你有兴趣将来在XO team写程序， 
最好从现在开始就养成这样的编程习惯。如果你是别的Mud里面的巫师， 
我想一个Mud里面最好也有一个统一的整齐的风格。 
  也许你会问，这样要求有必要吗？这样太麻烦，程序写了自己能明 
白就可以了。这是不对的，LpMud是大家合作的项目，如果你做的程序 
别人没法看懂，不知道写的东西里面有些什么，能调用什么函数，那 
么实际上你写的东西是失败的，没人会去用它，它可能永远“死”在 
硬盘上。而且函数统一的命名法能尽快找到你所需要的函数，同时也 
能提高整个程序的可读性。 
  对于代码风格XO还有一些别的要求，我们将在以后的文章中介绍， 
如果你加入了XO team，代码风格将是第一篇要读的文章。 
 
小结: 
  关于Lpc程序和编程环境，就介绍到这里。看完这一章，我想大家 
要记住的是LpMud是采用Lpc做为编程语言，Unix文件结构作为文件组 
织形式。Lpc是编写Object的一种语言，它的程序没有特殊的开始和 
结束的标志。如果Object被使用到，那么它被调入内存，如果这个 
Object有一个叫create()的函数，首先被执行，来初始化一些变量。 
Lpc的Object是一堆变量的集合，同时带有一些能操纵改变这些变量 
的函数。Lpc的代码风格，我想一个Mud最好有一个统一的风格，特别 
的XO有自己的特别的要求。 
 
题外话： 
  当了好久的巫师，也用Lpc写了一些东西。我一直在试着理解Lpc， 
因为以我看如果一个巫师没有真正理解Lpc，他就不可能真正理解 
LpMud。理解Lpc并不仅仅意味着会使用它，许多巫师能使用它但是 
并不真正理解它。我希望在这个Lpc的介绍文章，能给大家一个Lpc 
的整体的印象，真正把握和理解Lpc，能创造自己心中梦想的世界。 
 
 





发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc的数据类型(1) 
发信站: 广州网易BBS站 (Mon Apr 13 23:25:54 1998), 转信

⑿湃 maht (Trill), 信区: creator 
标　题: Lpc的数据类型(1) 
发信站: 天地创造 (Tue Oct 14 19:00:27 1997), 转信 
 
 
                       第二章 Lpc的数据类型。 
 
                           第一节 序言 
 
  Lpc的Object是由零个或更多一些的被一个或一个以上函数操纵 
控制的变量组成的。在代码中函数排列的顺序是不影响Object的特 
性，但是影响代码的可读性。当你写的那个Object被第一次调用时， 
Driver将你写的代码装入内存。当每一个Object被调入内存时，所 
有的变量是没有值的。create()这个函数被调用来初始化Object的 
值。create()这个函数在Object装入内存后立即被调用。在你读本 
文时可能对编程一无所知，你可能不知道什么是函数以及它是怎么 
调用的；或许你有了一些编程的经验，你可能对一个新创建的Object 
的函数相互调用过程是怎样开始感到迷惑。在这些困惑得到解决之 
前，你更有必要了解的是这些函数操纵控制的到底是什么东西。所 
以你最好先来读读这一章：Lpc的数据类型。可以这么说，几乎90％ 
的错误（包括丢失{}和()）是由于错误的使用Lpc的数据类型。我认 
为真正理解这一章能帮助你更容易的编程。 
 
                    第二节 让计算机理解你 
2.1 计算机语言 
  众所周知的计算机懂得的语言实际上由“0”和“1”组成的机器 
码。计算机根本不懂得人类的自然语言，实际上它也不懂得我们使 
用的高级语言，比如BASIC，C，C++，Pascal等等。这些高级语言 
能让我们更容易的实现我们的想法。但是这些高级语言最终都要被 
翻译成“0”和“1”组成的计算机语言。 
  有两种方法能把高级语言翻译成计算机语言：编译和解释。编译 
类的在程序写完之后用一个编译器将其翻译成计算机语言。编译在 
程序执行之前就完成了。解释类的翻译的过程在程序执行时进行。 
由于解释类的语言程序是边执行边解释，所以一般都要比编译编译 
执行的慢。 
  不管是哪种语言，他们最终都要被翻译成0和1。但是变量，那些 
你存在内存里面的变量，却不可能只是0和1。 
所以你必须有一种你 
使用的那种编程语言里面的方法来告诉计算机这些0和1应该被当做 
整数还是字符，或者是字符串，或者别的什么东西。这样就必须使 
用到数据类型。 
 
2.2 数据类型 
  一个简单的例子：你现在有了一个变量，你把它叫做‘x’并且 
赋予它一个十进制整数值65。在Lpc你可以这样的语句来做这件事： 
 
------ 
x = 65; 
------ 
 
接着你可以做象下面这样的事： 
 
----- 
write(x + "\n"); 
y = x + 5; 
----- 
第一行把65和字母"a"输出到屏幕上 
第二行把70这个值赋于变量y 
对计算机来说有个问题:它不知道你所说的 x = 65;中的65什么意思. 
你认为是65,但是计算机可能认为是: 
00000000000000000000000001000001 
但是,对计算机来说,字母'A'也是被当做: 
00000000000000000000000001000001 
所以,当你想让计算机明白 write( x + "\n" );, 它必须有一种方法 
知道你想看到的是65而不是'A'. 
  计算机就是通过数据类型来区分65和'A'. 一种数据类型简单的说就 
是在内存的某处, 那里代表了或者说指向某个给定的变量, 这些内存 
储存的数据是什么类型的. 每个LPC的变量都必须有它对应的变量类型. 
在上面给的例子, 本应在那些代码之前有下面一行: 
 
----- 
int x; 
---- 
 
这一行告诉Driver x应该指向什么类型的值, 它应该被当做数据类型'int' 
来使用. 'int' 是一个32位的整数. 到这里, 你应该有数据类型的基本 
印象, 以及为什么必须有数据类型. 他们可以让Driver知道计算机存在 
内存里面的'0'和'1'到底是什么东西. 
2.3 Lpc的数据类型 
 
  所有的LpMud的Driver都会有以下的数据类型： 
 
void, int, string, object, mixed, int *, string *, 
object *, mixed * 
 
  大多数的Driver都会有下面这些重要的数据类型： 
 
float, mapping, float *, mapping * 
 
  有一些Driver同时还支持下面这些数据类型： 
 
function, struct, class, char 
 
  特别的有MudOS支持的数据类型：(以v22pre8为例) 
 
void, int, string, object, float, mapping, function, 
class, mixed, int *, string *, object *, float *, 
mapping *, function *, class *, mixed * 
 
 
                                        (待续) 
 




发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc的数据类型(2) 
发信站: 广州网易BBS站 (Mon Apr 13 23:26:58 1998), 转信

⑿湃 maht (Trill), 信区: creator 
标　题: Lpc的数据类型(2) 
发信站: 天地创造 (Sat Oct 18 09:24:00 1997), 转信 
 
 
3.4 一些简单的数据类型 
  在Lpc入门里面将介绍以下的数据类型: 
void, int, float, string, object, 以及mixed. 对于复杂的数据 
类型比如: mapping, array, 以及一些不常用的类型比如: class, 
function, 我将在Lpc进阶介绍. 这一节我们主要介绍三种数据类型: 
int(整型), float(浮点数)和string(字符串). 
 
  一个int(整型)是一个整数, 比如1, 42, -18, 0, -10002938这些 
都是整型. 在MudOS中一个整型是一个32位的整数, 有符号的整数. 
在实际中int得到广泛的使用, 比如开始介绍变量中的wiz_level, 
再比如生物的天赋, 年龄等都通常都是int(整型). 
  一个float(浮点数)是一个实数, 比如2.034, -102.3453, 0.0, 
1132741034.33这些都是一个浮点数. 在MudOS中一个浮点数也是一个 
32位的实数, 有符号的实数. float通常不常用. 
  在Object的数值性质中, 我们通常也就使用int和float, 甚至只用 
int, 在变量的初始化中int和float自动被赋为0. 但是一般的Driver 
比如MudOS不检查数值越界的情况, 还要注意的是这里的int和float 
都是有符号的数, 这两点要注意. 
  string(字符串)是由一或更多的字符组成, 比如"a", "我是飞鸟!", 
"42", "飞鸟15岁.", "I am Trill.", 这些都是字符串. 注意的是, 
字符串都是被""括起来, 这样第一能区别象int(整型)42和string(字 
符串)"42", 第二可以区别变量名(比如 room)和同名的字符串(比如 
"room"). string类型的变量在初始化时, 如果没有显式的赋于一个 
字符串比如: 空字符串"", 那将是0, 就是一空的指针. 
  作为最基本的数据类型int, float和string, 是一些复杂的数据 
类型的基础. 对这些数据进行运算和操作的操作符, 将在后面介绍, 
不过Lpc的操作符和一般的C/C++的操作符一致. 只是有一点, 就+ 
Lpc支持string和int或者float直接相加, 比如我们上面提到的: 
 
    write(x + "\n"); 
 
"\n"是一个字符, x是一个整型的变量, 在Lpc解释执行中, 自动将 
x代表的数值转化成一个字符串, 然后把两个字符串接在一起. 
  当你在编程中使用一个变量, 你必须首先让Driver知道这个变量 
代表什么样的数据类型. 这个过程叫做变量声明. 这必须在一个函 
数或者一个Object的开始部分进行变量声明. 怎么做呢阿? 就是把 
数据类型的名字放在你所要用的变量名前面, 举个例子: 
 
----- 
void add_x_and_y() 
{ 
    int x; 
    int y; 
 
    x = 2; 
    y = x + x * x; 
} 
---- 
 
上面就是一个完整的函数. 函数名就是add_x_and_y(). 在这个函数中 
一开始声明落两个变量x, y都是int. 
 
下面介绍MudOS支持的数据类型: 
int 
  一个整数(32位). 
float 
  一个浮点数(32位). 
string 
  无限长的字符串. 
object 
  指向一个对象的指针. 
mapping 
  一个关系型数组. 
function 
  一种特殊的指针, 指向(object, 函数名)这样一个组合. 
arrays 
  数组的声明采用使用 '*' 跟在一种基本的类型. 
void 
  这种类型对变量没有任何用处, 只对函数有用. 它表明这个函数 
不返回任何值. 
mixed 
  这是一种特殊的数据类型, 可以指向任何的数据类型. 如果一个 
变量被声明成mixed, 那么Driver不会对它做任何检查. 
class 
  自定义的数据类型, 类似C的struct而和C++和class不一样. 
一上是MudOS支持的数据类型. 
 
小结: 
  对一个变量, Driver需要知道存在计算机内存中的'0'和'1'到底 
指的什么东西, 这样我们引入落数据类型. 我们学习3种简单的数据 
类型, 同时了解了MudOS支持的各种数据类型. 对于各种操作符, 不 
同数据类型有各自不同的操作符, 比如你让 "飞鸟"/"trill", 那 
Driver一定会返回一个错误的. 大多数数的操作符和C/C++的一样, 
只是+ 还支持字符串和数字相加. 
 






发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc的函数(1) 
发信站: 广州网易BBS站 (Mon Apr 13 23:27:50 1998), 转信

发信人: maht (Trill), 信区: creator 
标　题: Lpc的函数(1) 
发信站: 天地创造 (Wed Oct 22 20:15:10 1997), 转信 
 
                        第三章 Lpc的函数 
                          第一节 序言 
 
  在前面的介绍中，大家应该知道了Lpc的Object包含能处理变量的函数。 
当函数被执行时，它的工作就是处理操作变量，还有是调用(call)别的函 
数。变量在函数中被改变操作。变量必须有个数据类型使得计算机能明白 
它指向的内存中"0"和"1"到底是什么东西。一个Object的性质通常由它的 
包含的变量确定，但是它的特性的表现却是依赖于它包含的函数。一个 
Object如果不含有任何一个函数那是不可想象的。那么：什么是函数。 
 
                        第二节 函数 
 
2.1 什么是函数？ 
  和数学的函数一样，你给Lpc的函数一个值，它能返回一个值。有些语 
言，比如Pascal，会区分过程和函数。Lpc和C/C++一样，没有过程，但是 
明白这种区别还是有用的。Pascal叫做过程的东西，Lpc叫做类型是void 
的函数。换句话说，过程就是什么都不返回的函数。Pascal叫做函数的， 
必须返回一些东西。在Lpc中，最无聊的，最简单的，但也是正确的函数 
是这样的： 
 
----- 
void eventDoNothing() {} 
----- 
 
这个函数不接收任何输入，不执行指令，也不返回任何值。 
 
  每一个Lpc函数都由三部分组成： 
1) 函数声明 
2) 函数定义 
3) 函数调用 
 
  和变量一样，函数必须先有个声明。这样可以让Driver知道： 
  1) 这个函数将返回的是哪种数据类型。 
  2) 需要的输入是什么，多少。通常把输入叫做参数。 
  一个函数声明通常是这样的： 
类型 函数名(参数1, 参数2, ..., 参数N); 
  下面是一个函数声明的例子，这个函数叫 DrinkWater，有一个string 
类型的参数，返回的是一个int。 
 
----- 
int eventDrinkWater(string str); 
----- 
 
  在上面的声明中, str是输入的参数的变量名，也可以没有。就是说可以 
象下面这样声明 eventDrinkWater() 
 
----- 
int eventDrinkWater(string); 
----- 
 
  函数定义就是代码，它描述了这个函数对传人的参数究竟做了些什么。 
  函数调用就是别的函数在任何地方使用执行了这个函数。一个函数在它 
写完后永远不会被调用，那这个函数的存在的唯一意义只能是浪费内存和 
硬盘。一个函数写出来的目的是为了被调用。 
  下面是两个函数相互调用的例子，两个函数是 eventPrintValue() 和 
add(), 
 
----- 
/* 首先是函数声明，这个通常是在一个Object的开始部分。 
 */ 
 
void eventPrintValue(); 
int add(int x, int y); 
 
/* 其次是函数 write_vals() 的函数定义。我们假定这个函数将被调用 
 * 是为了描述这个Object. 
 */ 
void eventPrintValue() 
{ 
    int x; 
 
    x = add(2, 2); // 我们指定 x 接收调用函数 add() 后返回的值。 
    write(x + "\n"); 
} 
 
/* 最后是函数 add() 的函数定义。 */ 
int add(int x, int y) 
{ 
    return (x + y); 
} 
----- 
 
  有一点是指明的，在XO的编程的风格我们要求所有的函数都必须有声 
明，这个在我们最开始时候说明过。但是实际上必须有函数声明的函数 
是那些被调用在函数定义之前的函数。我们规定必须有函数声明，这个 
只是规定，但是它会给编程带来好处。 
 
  在这一节我们知道什么是函数，函数是由什么组成。要记住，写一个 
函数的根本目的是为用它，调用它。一个函数永远不会被调用，那它就 
失去了存在的价值。通常别人使用你写的函数，通常只关心它能对传人 
的参数做些什么加工，就是这个函数的功能是什么，返回什么。因此一 
个函数有一个好的函数名，能直接描述这个函数的功能是很重要的。我 
在第一章中说明了XO规定的对函数的命名机制。采用统一的命名方式有 
助于相互合作提高效率。 
 
2.2 Efuns 
  也许你已经听说过efun这个词了，他们是外部定义的函数，是 
externally defined function 的缩写。就是说，他们是由Mud Driver 
定义好的。如果参加过Lpc的编程，或者看过Lpc的代码，你可能找到这 
样的一些表达式：this_player(), strcmp(), implode(), filter()， 
等等，看起来象是一个函数，而你找遍整个Object以及这个Object继承 
的所有Object中都没有这些函数，这就表明他们是efun。efun存在价值 
是因为他们执行起来要比一般的Object带有的函数速度快的多，为什么 
快呢，因为他们是以计算机直接能理解的二进制的形式存在。对于Object 
内部定义的函数，我们通常叫他们是lfun(local function)。一个巫师 
主要工作也就是编写一些lfun组成的Object。 
  在上面的例子中的 eventPrintValue() 中调用了两个函数，第一个是 
函数 add(), 这个是有你声明和定义的，这个就是lfun。第二次调用， 
是调用函数 write() 这个函数通常就是efun。Driver已经替你声明和定 
义好了。你所要做只是调用它。 
  efun被创立是为了 
  1) 处理一些很常用的，每天都有许多函数会调用的。 
  2) 处理internet socket的输入输出。 
  3) 以及一些Lpc很难处理的事，毕竟Lpc是C的很小的子集。 
  efun是用C写好的，内嵌在Driver里面的。在Mud起来之前，和Driver 
一起编译好的，他们执行起来会快的多。但是正和你期望的一样，他们 
的调用和你写的函数的调用方法是完全一样的。总的来说，需要关心的 
和一般函数一样，它需要传入什么参数，它将会返回什么的东西。 
 
  怎样得到一些efun的信息，比如传入参数和返回的类型，通常在一个 
Mud里面，你可以在类似这样的 /doc/efun 的目录底下找到，或者直接 
用 help  指令就可以得到帮助。efun及其依赖于你所在的Mud 
的Driver，不同的Driver带有的efun区别是很大。 
  对于XO，使用的是MudOS，一般的efun，只要用 help 指令就能得到 
帮助，或者你多看看源码，看看别人是怎样使用的，当然你如果无论如 
何也不能明白一个efun，你可以问问大巫师，他们通常会很乐意和你探 
讨的。但是有一点是指出，能自己解决的问题最好自己解决。 
 
                                                (待续) 
 
 





发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc的函数(2) 
发信站: 广州网易BBS站 (Mon Apr 13 23:28:38 1998), 转信

发信人: maht (Trill), 信区: creator 
标　题: Lpc的函数(2) 
发信站: 天地创造 (Sun Oct 26 12:25:54 1997), 转信 
 
 
2.3 自己动手写函数 
  用Lpc写Object的函数，是为了表现这个Object的特性。这个特性的 
函数实际上就是一些代码按顺序排列，排列的顺序决定了这个函数。一 
个函数被调用，函数的代码就按照函数定义中代码按顺序执行。在 
eventPrintValue()中，下面这个语句： 
 
----- 
x = add(2, 2); 
----- 
 
必须在 efun: write() 之前调用，如果你想看到正确的结果。 
 
  为了返回这个函数执行后的值，必须这么做：使用 "return" 指令和后 
面跟着和这个函数数据类型一样的一个值。在上面的 add()，这个指令是 
"return ( x + y );"，( x + y )的值就是返回给 eventPrintValue() 
以及赋给 x 的那个值。更进一步的说，"return" 结束当前函数的执行 
并把控制权返回给调用它的那个函数。实际上，return 可以返回跟在 
它后面的一个表达式的值。如果想停止一个类型为void的函数的执行， 
只要简单使用 "return;"，不返回任何值。强调一点，想要返回的表达 
式的值的数据类型 必须 和函数自己的数据类型一致。 
  那到底如何写一个函数(lfun)？ 
  一个函数定义通常按次序有下面部分： 
  1) 函数返回数据类型 
  2) 函数名 
  3) 以 ( 开始，以 ) 结束的函数列表 
  4) 一个 { 标志这个函数从这里开始执行 
  5) 变量声明，在这个函数将要使用的变量 
  6) 计算机指令，各种语句，表达式，以及调用别的函数。 
  7) 一个 } 标志函数代码部分到次结束。除非这个函数的类型说明成 
     void，在 } 之前必须有个 return 返回和这个函数的数据类型，在 
     return 后继续执行调用这个函数的那个函数。 
 
举个平凡的例子： 
 
----- 
int my_function( string target ) 
{ 
    string name; 
    object ppl 
 
    name = "Trill"; 
    if ( ppl = find_player( target ) ) 
    { 
        tell_object( ppl, "hi. My name is " + name + ". \n" ) 
        return 1; 
    } 
    else 
    { 
        tell_object( this_player(), "sorry, i don't find the target : " 
            + name + ".\n" ); 
        return 0; 
    } 
    return 0; 
} 
----- 
 
  这个函数接收一个 string 类型的参数，执行一些指令，返回一个 int 
型的整数。 
 
                    第三节  小结 

  定义了Lpc的Object的文件实际是由一些函数组成的。所有的函数，按 
顺序由以下部分组成： 
    1) 函数声明 
    2) 函数定义(函数体) 
    3) 函数调用 
    3) The call 
  函数声明通常出现在这个文件的开头，在任何一个函数定义之前。必 
须做到是，每个函数的被调用之前有函数的声明。在 XO 中规定严格一 
些所有的函数必须有声明。 
  函数定义可以出现在文件的任何地方，只要在它的声明之后就可以了。 
有一点要注意，不要在一个函数内部定义另一个函数。在 XO 中规定是 
函数的定义按照一个给定的次序。 
  函数调用通常在别的函数定义的内部，如果你想要你的代码执行你写 
的函数的话。当然也可以出现这个函数自己的函数定义内部，但是对于 
一个新的巫师来说，最好别这么做，因为这样可能导致无限的循环。 
 
  每个 Driver 有自己一套 efun ，这些函数 Driver 已经替你声明过了， 
也已经定义好了。efun 通常执行起来要比你自己写的函数快一些。更进 
一步说，每个 MudLib 有一些特殊的函数看起来非常象 efun，他们也已 
经声明过了，定义好了。他们被叫做 simul_efun，或者 sefun，或者叫 
做 模拟 efun。对于 efun 的帮助，通常在 /doc/efun，或者 
/help/wizard/efun，等类型的目录底下。很多 Mud 会提供一个指令，比 
如 "man" 或 "help" 来提供在线帮助。 
 
  注意： 
  有些 Driver 对函数的类型检查不严格，甚至忽略你对函数那些特殊 
的声明，比如 private 等等。但是不要管这个，你应该这样一个良好的 
习惯，对函数的类型有严格声明。为什么？ 
 
    1)对别的人(包括以后你自己)能更容易读你的程序，更快理解你这 
      部分代码想描述什么。这个对调试程序也是很有用的，大部分的 
      程序错误(包括 () 和 {} 的丢失)通常是数据类型不匹配。 
    2)因为这个被认为是个好的编程习惯。 
 
 






发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc的继承简介 
发信站: 广州网易BBS站 (Mon Apr 13 23:29:33 1998), 转信

⑿湃 maht (Trill), 信区: creator 
标　题: Lpc的继承简介 
发信站: 天地创造 (Fri Oct 31 10:30:16 1997), 转信 
 
 
                   第四章 Lpc的继承简介 
 
                        第一节 前言 
  通过上一章的阅读，你应该知道Lpc的函数的基本工作方式。你应该学 
会如何声明和调用函数。更进一步，你应该有能力自己写函数定义，即 
使你是首次接触Lpc。你知道了函数调用是如何执行的，函数必须有一个 
返回值，除非是 void 类型的。 
  下面我们来看一个例子。看看巫师的工作室，可能几乎都有类似下面 
的一段代码。(这个不同的 MudLib 是不一样的) 
 
----- 
 
void create(); 
 
inherit "/std/room"; 
 
void create() 
{ 
    room::create(); 
 
    set( "short", "巫师工作室"); 
    set( "long", "这是一片未曾开垦的土地，需要巫师的画笔去描绘。\n" ); 
 
    set( "exits", ([ 
        "trill"     :   "/u/trill/workroom", 
    ]); 
 
    create_door("south", "时空之门", "north", 0); 
    setup(); 
} 
 
----- 
 
  如果你理解了前面的几章，你应该能立刻指出几点： 
    1)  create() 是一个函数的定义部分。"void create();" 是它的函数 
        声明，却有在 create() 里面有个 room::create() 没有声明。 
    2)  它调用了set(), create_door(), setup()，在代码中却没有声明。 
    3)  在第三行，既不是变量声明也不是函数声明，更不是函数定义。 
  这一章就是回答以下问题的： 
    1)  room::create()是什么东西？ 
    2)  函数 set()，create_door()，在哪里声明、哪里定义？ 
    3)  第三行是什么东西？ 
 
                    第二节 面对对象编程(OOP)的继承 
 
  继承是面对对象编程的一个基本特性。它使你能写一些基本的代码能 
以不同的方式被不同的程序使用。一个 MudLib 做的就是创建一些基本 
的 Object ，在此基础上你能用他们创建自己特殊的 Object。 
  如果不得不写那些定义巫师工作室所必须的所有代码，你可能要写超 
过 1000 行的代码，这样你才可能有这个 room 所需要的所有函数。如果 
每个 room 都必须这么做的话，首先是浪费硬盘空间；其次，你写的代 
码可能根本无法和别人的写的代码组合在一起，每个人对 room 扩展的 
特性的各种函数搞出每个人自己的标准。比如，你可能把 room 的长描 
述的接口函数叫做 GetLong()，别的巫师可能喜欢把它命名为 query_long()。 
这个就是各个 MudLib 不兼容的根本问题，因为它们 Object 相互继承、 
调用上使用不同的协议。 
  OOP 能克服上面的问题。在上面的例子中，你直接使用的函数都定义 
在继承的叫做 "/std/room.c" 的 Object 里面了。它有所有那些 room 常 
用的函数的定义。当你写创建一个特别的 room，你通过继承得到 room 
的的基本的功能，通过加入你自己写的函数，这样就把它变成了一个独 
特的 room 了。 
  OOP 通过继承不只是提高了代码的可重用性，而且它可以是你只关心 
继承基本的 Object 后，那些特性需要改变的，而不用考虑那些东西是 
需要的，就是说当你看到别人写好的 Object 时，发现已经很象你心中 
所想的东西，就只有一点点不符合，那你所要做只是继承原先的 Object， 
然后加入和想的那一点点不同的东西就可以了。 
  OOP 当然不只是继承，还有别的一些。但是对于我们现在是谈论继承 
的最基本的概念。 
 
                第三节 继承是如何工作的？ 
 
  还记得最开始提的三个问题吗？在这一节里面，将解答这些问题。 
  首先最后一个问题： 
  例子中的第三行： 
----- 
inherit "/std/room"; 
----- 
是做什么？ 
  我想到了现在，你应该你猜出来这一行确保你写的巫师工作室继承了 
标准的 room: "/std/room.c" 的特性。通过继承获得标准 room 的功能， 
你就可以使用那些已经在 "/std/room.c" 底下声明和定义好的所有函数。 
这就是为什么你可以直接使用 set(), create_door() 这些函数的原因了。 
在你自己写的函数 create()，你只要设置那些你要的值就可以了。这些 
值使得你的 room 与众不同，但确保和别的 Object 能相互作用。 
  那 room::create() 是做什么的？ 
  你可能看到类似的东西比如： ::create()、::init()等。可能现在完全 
理解有些困难。你只要有个印象就可以了。 
  在你继承的那些 Object 中，可能会有 create() 函数，但是在你写的 
的 Object 中也有 create() 了，那么 Driver 将只调用你写 create() 来 
完成一些初始化工作，一般情况下这是你所希望的。可是，有时候，你 
发现你继承的 Object，比如 /std/room.c 中的 create()，也是有用的， 
那怎办？那么你可以这么做： 
    在你写的 room 的 create() 当中加入 
 
----------------- 
    room::create(); 
----------------- 
 
这一行，"::"这个操作符加在一个函数 example() 的前面，那么它就调 
用它继承的 Object 中的那个函数，如果 "::"的前面没有任何限定，那 
么就调用所有继承的当前的一级的 Object 中叫做 example() 的函数， 
如果有限定，那么就只调用给定的 Object 的叫做 example() 的函数， 
在我们的例子就调用 /std/room.c 的 create() 函数。 
  "::"这个操作符，是用于调用继承的 Object 一个特殊的函数。通常 
把 "::" 操作符叫做 域访问操作符 。 
  一个对比例子： 
 
----- 
#1 
 
inherit "/std/room"; 
 
void create() 
{ 
    create(); 
} 
 
----- 
 
----- 
#2 
 
inherit "/std/room"; 
 
void create() 
{ 
    room::create(); 
} 
 
----- 
 
  例子 1 的后果是灾难性的。当它被装入内存中，Driver 调用 create()， 
接着 create() 调用 create()，create() 还接着调用 create()，如此往 
复永无穷尽。就是说，create() 持续不断的调用自己，除非 Driver 发 
现这是一个太深的递归，将其强制退出。 
  例子 2 是对的，但是除了浪费内存之外没有任何作用，因为它和 room.c 
的功能没有区别。对于这个 Object，Driver 调用它的 create()，在它的 
create() 通过 room::create() 返回去调用 room.c 中的 create()。 
 
  总的来说，每个 MudLib 都是不同的，每个自己有一套标准函数的集 
合，它们对于一些基本的功能都能完成。有些功能特性多有些，有些少 
一些，有些在一些特殊方面完成特殊的工作，比如 Es2 带了对中文的处 
理。但是如果这个 MudLib 如果比较完善的话，它一般要提供一些标准 
的 Object 的特性和功能的说明。一般可能在 "/doc/build" 底下，可能 
叫做 "room_prop"、"monster_prop" 等。看看这些，你会得到你继承的 
Object 的有的函数，它们的输入参数，返回的数据类型，以及能完成 
什么工作。 
 
                    第四节  小结 
 
  实际上复杂对象的继承是很复杂的，就这么简单解释是远远不够的。 
这篇简介只是在你创建一些简单的 Object 能使用继承。更全面的讨论 
我们将在 Lpc进阶 里面看到。 
  现在你应该知道： 
    1)  每个 MudLib 有自己的一套基本 Object 的库，有着一些基本的 
        功能，巫师们通过继承能更容易创建自己的 Object，不同的 
        Object 之间的交互作用也更容易一些。 
    2)  每个 MudLib 有自己一套标准。通常有自己一些文档说明标准的 
        Object 是什么，有什么样的功能。 
    3)  你可以用这样继承别的 Object 的功能： 
 
----- 
inherit "filename"; 
----- 
 
        这里的 filename 是被继承的 Object 的文件名。inherit "xxx" 
        要在你的写的代码的开头部分。 
 
 





发信人: dewey (公子), 信区: Mud_Wizard
标题: Lpc变量控制和流程控制 
发信站: 广州网易BBS站 (Mon Apr 13 23:30:34 1998), 转信

发信人: maht (Trill), 信区: creator 
标　题: Lpc变量控制和流程控制 
发信站: 天地创造 (Tue Nov 11 17:10:25 1997), 转信 
 
 
                    第五章   变量控制和流程控制 
 
                    第一节  序言 
  到今天，你应该能自己编写一些简单的 Object 了，通过使用你所在 
的 Mud 的标准的 Object 库。 你应该知道什么是继承了，还有你应该 
能自己写一些函数来表现你自己的 Object 特有的性质了。在这一章， 
你将知道 Lpc 的最基本的一些元素，比如：表达式、操作符等，在你 
自己写的函数通过一些基本元素的组合来操纵变量，通过流程控制来使 
的你的函数更为复杂，而不是线性执行到最后。 
 
                    第二节 变量 
 
2.1 值和变量 
  从根本来说，任意两个 Object 不同的地方有两个方面： 
1) 一些函数不同 
2) 各个变量的值不同 
 
  举个例子，所有的玩家 Object 有完全一样的函数。因此他们不同的 
只是保持的值不同。比如，玩家名字叫做 "falcon" 和 "trill" 不同， 
其最根本的不同的是因为变量 "dbase/id" 这个值不同，一个是"falcon" 
另一个是 "trill" 。 
  因此，在 Mud 中的变化就是各个 Object 中的值的变化。函数是用来 
命名那些可以改变操纵变量的特殊的一些过程。举个例子，函数 create() 
就是这样一个过程：初始化一个 Object 的各个变量的值的一个过程。 
在函数里面，有个特别的东西叫做 指令 用于直接改变操纵变量。 
 
2.2 局部和全局变量 
 
  和大多数的编程语言一样，Lpc 的变量可以被声明成一个函数的局部 
变量，也可以是全局变量(这个全局变量只在一个 Object 内部是全局的， 
对于其他 Object 的函数是不能访问的)。局部变量在一个函数定义内部 
声明，只在这个函数内部使用。别的函数无法得知它的存在。全部变量 
则只要所在的 Object 存在时间内，可以被这个 Obejct 声明的任何函数 
调用，就是它一直在内存中存在。看看下面的两个例子： 
 
----- 
int x; 
 
int GetX() 
{ 
    return x; 
} 
 
void SetX( int y ) 
{ 
    x = y; 
} 
----- 
 
----- 
void SetX( int y ) 
{ 
    int x; 
 
    x = y; 
    write( "x is set to x" + x + " and will now be forgotten.\n" ); 
} 
----- 
 
在第一个例子里面，x 在所有的函数的外部声明，所以它可以被它后面 
的任何函数使用。在这个例子中，x 就是一个全局变量。 
在第二个例子中，x 在函数 SetX() 的内部声明。它只有在函数 SetX() 
被执行时才存在。然后，它就消失了。在这个例子中，x 是局部变量。 
 
2.2 表达式和操作符 
 
  在 Lpc 中表达式和操作符的意义和 C/C++ 的完全一样，如果你在这个 
方面一无所知，那就去找一本 C/C++ 的入门的书。 
  在本章的附录中列出 Lpc 常用的各个操作符的用法，以及 MudOS提供 
的一些特别操作符。 
 
2,3 小结 
  现在你应该知道在不同地方声明一个变量的区别了。我们在上面说的 
全局变量和一般的 C/C++ 中的全局变量的意义不同。一般的，在 C/C++ 
的全局变量在这个程序执行整个过程一直存在，但是 Lpc 的全局变量实 
际是“类变量”，或者说是“Ojbect 变量”，一个 Object 的全局变量， 
在 Object 存在时存在，而没有真正的永远存在的“全局变量”。通过 
对操作符，变量、常量和函数的组合，你能得到一个很复杂的表达式， 
比如： 
    i = ( x = sizeof( users() ) > 0 ) ? 
        x -- : ( ( sizeof( children( NPC ) - 1 ); 
  现在大家应该能知道上面的表达式的值了吧？其中 efun users() 返回 
所有的玩家，children( NPC ) 返回所有 NPC 复制的 Object, sizeof()能 
得知一个 array 或者 mapping 的大小。 
 
                    第三节 流程控制 
 
2.1 流程控制 
  在上一节的例子中： 
 
----- 
void SetX( int y ) 
{ 
    int x; 
 
    x = y; 
    write( "x is set to x" + x + " and will now be forgotten.\n" ); 
} 
----- 
 
  你可能只想在 y > 1 时候把这个值赋予 x。怎么办，那就是流程控制 
的。流程控制，就是控制程序的执行过程。Lpc 和流程控制和 C/C++ 
几乎可以说是完全一样。 
 
2.2 流程控制语句 
  由于 Lpc 的流程控制实际上 C 的一样，我就不再介绍了。在本章的 
附录二中，列出了 Lpc 的流程控制语句。 
  强调一点，switch 可以完全由 if else if else 替代，比如： 
 
    switch ( id ) 
    { 
    case "trill" : 
        write( "你是飞鸟。\n" ); 
        break; 
    case "falcon" : 
        write( "你是狐儿。\n" ); 
        break; 
    case "qyz" : 
        write( "你是小马。\n" ); 
        break; 
    default : 
        write( "你是谁？\n" ); 
        break; 
    } 
 
可以写成这样： 
 
    if ( id == "trill" ) 
    { 
        write( "你是飞鸟。\n" ); 
    } 
    else if ( id == "falcon") 
    { 
        write( "你是狐儿。\n" ); 
    } 
    else if ( id == "qyz" ) 
    { 
        write( "你是小马。\n" ); 
    } 
    else 
    { 
        write("你是谁？\n"); 
    } 
 
  但是 switch 语句更加贴近 CPU 的执行方式，也更自然。所以最好使 
用 switch 语句，而不是 if else if else 的形式。 
 
小结： 
  这一章介绍的实际是 C 的东西，所以如果你对于 C 语言不熟悉的话， 
就找一本入门的书，看看变量，常量和流程控制的介绍。如果你对 C 有 
写了解了，到现在，你应该能很容易的写一些简单的 room, npc 了。 
 
附录一 
Lpc 操作符 
 
  在这一附录，详细说明大部分的 Lpc 操作符。 
 
  Lpc 的操作符大概有下面这些： 
=   +   -   *   /   %   +=  -=  *=  /=  %= 
--  ++  ==  !=  >   <   >=  <=  !   &&  || 
->  ? :     >>  <<  <<= >>= &   |   ^   &= 
|=  ^=  ... 
 
  这些操作符的说明有点枯燥无味，但是最好还是看看，因为有些操作 
符的行为有些怪异。 
 
= 赋值操作符 ( assignment operator ): 
    例子: x = 5; 
    操作值 把等号右边表达式的值赋予左边的变量。等号左边只能是 
            变量，就是说你不能赋予一个表达式值。 
 
+ 加号 ( addition operator ): 
    例子: x + 7 
    操作值：把加号左右的两个表达式的值(如果都是数值)加在一起， 
            得到一个值。如果有一个表达式的值是 string 类型的，一 
            个是 int 或者 float 类型，就直接把 int 和 float 的数值 
            变成相应的字符串，接在 string 的对应的左边或右边。如 
            果两个都是数组，那么就把两个数组和在一起得到一个新的 
            数组。 
 
- 减号 (subtraction operator ): 
    例子: x - 7 
    操作值：这个和 + 刚好现反。 
 
* 乘号 ( multiplication operator ): 
    例子: x * 7 
    操作值：和数学上的乘法没有什么区别。 
 
/ 除法 ( division operator ): 
    例子: x / 7 
    操作值：和数学上的除法没有什么区别。 
 
+= additive assignment operator: 
    例子: x += 5 
    操作值：如果是 a += b，那就就是 a = a + b。 
 
-= subtraction assignment operator 
    例子: x -= 7 
    操作值：如果是 a -= b，那就就是 a = a - b。 
 
*= multiplicative assignment operator 
    例子: x *= 7 
    操作值：如果是 a *= b，那就就是 a = a * b。 
 
/= division assignment operator 
    例子: x /= 7 
    操作值：如果是 a /= b，那就就是 a = a / b。 
 
++ post/pre-increment operators 
    例子: i ++ or ++ i 
    操作值： 
      i ++ 返回的值是 i，然后在把 i 加 1 
      ++ i 返回的值是 i + 1 
 
-- post/pre-decrement operators 
    例子: i-- or --i 
    操作值： 
      i -- 返回的值是 i 
      -- i 返回的值是 i - 1 
 
== equality operator 
!= inequality operator 
＞ greater than operator 
< less than operator 
＞= greater than or equal to operator 
<= less than or equal to operator 
    例子: x == y x != y x > y x < y    x >= y    x <= y 
&& logical and operator 
|| logical or operator 
    例子: x && y      x || y 
! negation operator 
    例子: !x 
-> the call other operator 
    例子: this_player()->GetKeyName() 
    操作值：调用一个 Object 的一个成员函数，左边必须是一个 Object 
            右边是 Object 的一个函数，如果不存在，那么就返回 0。 
    例子：task->event 
    操作值：返回一个 class 类型的变量的中的一个变量 
 
? : conditional operator 
    例子: x ? y : z 
    返回值：如果 x 是为真，那么就返回 y 的值，为假，就返回 z 的值 
 
这些操作符和 C 语言的完全一样。 
 
下面的是高颁布的 MudOS 提供的： 
 
＞> << 
    循环右移和左移 
& | ^ 
    按位做 与 或 异或 
<<= >>= &= |=  ^= 
    如果是 a X= b 那么就是 a = a X b 
这些和 C 的也一样。 
 
... 
    返回一个数组的第一个元素。 
 
 
附录二 
流程控制语句： 
if ( 表达式 ) 
{ 
    指令集 
} 
 
if ( 表达式 ) 
{ 
    指令集 
} 
else 
{ 
    指令集 
} 
 
if( 表达式 ) 
{ 
    指令集 
} 
else if ( 表达式 ) 
{ 
    指令集 
} 
else 
{ 
    指令集 
} 
 
while ( 表达式 ) 
{ 
    指令集 
} 
 
 
switch ( 表达式 ) 
{ 
case ( 表达式 ): 
    指令集; 
    break; 
default: 
    指令集 
} 
 
for( 表达式; 表达式; 表达式 ) 
这些和 C 的完全一样 
 
return 
    结束这次函数的执行 
continue 
    忽略这次循环 
break 
    跳出循环体 
 
高颁布的 MudOS 新提供的： 
 
foreach()循环 
 
foreach ( 变量类型 in 数组 ) 
比如： 
 
void test() 
{ 
    foreach( object player in users() ) 
    { 
        tell_object( player, "hi..just a test." ); 
    } 
} 
users() 这个 efun 返回一个 Object 的数组，所有在线的玩家。 
 
foreach () 循环对数组中的每个元素，做一次循环。 







发信人: dewey (公子), 信区: Mud_Wizard
标题: 数据类型: object 
发信站: 广州网易BBS站 (Mon Apr 13 23:31:14 1998), 转信

发信人: maht (Trill), 信区: creator 
标　题: 数据类型: object 
发信站: 天地创造 (Wed Nov 12 16:57:36 1997), 转信 
 
                第六章  数据类型：object 
 
                    第一节  序言 
  这是 Lpc 入门的最后一节，到现在，如果前面几章都读过了，你应该 
能写出一些简单的 object，比如 room 。而且能给 room 写一些简单的 
特写，通过添加一些函数。 
 
                    第二节 作为数据类型的 object 
2.1 数据类型 object 
  在这一章中，你将了解更复杂一些的数据类型：object。一个 object 
类型的变量是指向一个装入内存的真正存在的 object 。象别的数据类 
型一样，你用 object 后面跟一个变量名来声明： 
 
    object oTest; 
 
  它有些特殊，它几乎没有什么操作符可以作用 object 这种数据类型。 
其实，也是的，让一个 room / npc 那是什么东西？而且一般的 efun， 
比如 write(), say() 也是不能操作 object 的，但是你会了解到，对于 
object 这中数据类型，有一些很重要的 efun。 
 
2.2 efun: this_object() 
  这个 efun 返回一个 object，当前被执行的函数所在的那个 object。 
也就是说，在一个文件中，this_object() 提交的 object 是这样的： 
如果这个文件写的 object 如果自己被复制装入内存单独存在，那么就 
是这个 object，如果这个文件被继承了，那么就是继承的文件进行上一 
个过程，看它是不是单独存在，也就是没有再继承的文件了。可能有些 
难也理解，让我们看这个例子，比如你写了一个 object 叫 name.c，它 
被 player.c 继承了，那么 this_object() 实际返回的是 player.c 那个 
object，而不是 name.c 。我们再来看一个使用 this_object() 的实例： 
 
  你想让一个 npc 走到一朵花旁边闻一下，但是条件是那个 npc 能自 
由运动，比如：没有晕倒，没有在战斗。 
  你可以这么做： 
 
void eventSmellFlower() 
{ 
    if( this_object()->can_act() ) 
    { 
        write( this_object()->query( "name" ) + "走到花的边上闻了闻。\n" ); 
        this_object()->add( "kee", 5 ); 
    } 
} 
 
  can_act() 在 npc.c 里面是没有定义的，而且可能它继承的任何 object 
中也没有定义。那么，如果写成这样： if ( can_act() )，driver 没有 
在当前文件和继承的所有文件里面找到 can_act()，那就会有个编译错 
误的，但是 this_object()->can_act() 就不会出现这样的错误，就算是 
can_act() 根本不存在，那么结果是返回 0 ，就是 if 测试的表达式的 
结果是假的。这样有人会问：那这个 efun 有必要存在吗？driver 在 
编译是会自动搜索它继承的所有 object，不是自然有 this_object 了 
吗？ 确实，但是有时，这个 object (暂时叫做 A )会被别的 object (B) 
继承的，别的 object (B)可能会有这个函数，而且有时候 B 可能会重载 
A 和 A 继承的函数，而你不想让那些重载的函数失去作用，那么就采用 
 this_object()-> 的形式。 
 
2.3 调用别的 Object 里面的函数 
  这就是我们引入 object 这种数据类型的最大好处了。它能让我们调 
用别的 Object 里面的函数。在上面的例子中，我们可以知道当前的 
Object 能不动。下面的例子，你可以增加一个 player 身上的钱。 
调用别的 object 里面的函数，可以用下面两种方法： 
 
object->function(parameters) 
call_other(object, "function", parameters); 
 
example: 
this_player()->add_money( "silver", 5 ); 
call_other( this_player(), "add_money", "silver", 5 ); 
 
  在某些时候( 很不精确的说法 )，mud 是一个由玩家命令触发的函数 
调用的链式反应。当一个 player 触发了一个函数调用链，那么这个 player 
就是 eufn this_player() 返回的那个 object。就是说，因为 this_player() 
依赖那个触发事件序列那个 player，那么你应该小心的使用 this_player() 
这个 efun，如果你把 this_player() 放在一些函数，这些函数别调用通 
常可能是 driver 来调用的，或者不一定是 player 来触发的，那么使用 
this_player() 最好不要出现在那些函数里面。比如 create(), setup()。 
 
2.4 lfun: init() 
  在任何时候，一个“活的”的 object，接近一个 object (进入一个新 
房间，或者另一个 object 进入同一个 room )，init() 将自动被调用。 
利用这个，你可以写一些自动触发的效果，比如，你进入一家客栈，小 
二和你打招呼，看看这样一个例子： 
 
void init() { 
    ::init(); 
    if ( this_object()->can_act() ) 
    { 
        this_object()->eventGreet( this_player() ); 
    } 
} 
 
eventGreet() 可以是下面那样： 
 
void eventGreet( object ob ) 
{ 
    if ( !ob || environment( ob ) != environment( this_object() ) ) 
    { 
        return ; 
    } 
    else 
    { 
        write( "小二说道：这位客官，您想要点什么？\n" ); 
    } 
} 
 
efun environment() 返回一个 object 所在的环境，比如一个 player 所 
在的 room，一把剑所在的 player 等等。 
 
2.5 在你的 room 里面放入一个 object 
  看下面的一个例子，你可以在一个 player 所在的房间放入一条蛇： 
 
void test() 
{ 
    object env, snake; 
 
    env = environment( this_player() ); 
    if ( objectp( env ) ) 
    { 
        snake = clone_object( "/clone/monster/snake" ); 
        if ( objectp( snake ) ) 
        { 
            snake->move( env ); 
        } 
    } 
} 
 
例子中用到了一个 efun objectp ，这个 efun 判断传入的参数是不是一 
个真实的 object，如果是就返回真，否则就是假。efun clone_object() 
则试着把一个文件调入内存，变成一个 object。最后一个函数 move， 
这个显然不是一个 efun，但是总的来说，一般会调用 efun move_object 
把一个 object 放到另一个 object 的内部。 
 
小结 ( 也是 lpc 入门的小结 ) 
  在本章，我们了解作为数据类型的 object。同时也了解一些 efun： 
this_object(), this_player() environment() clone_object() objectp() 
的使用。还有 init() 这个特别的函数的使用。 
  到了现在，你应该有足够的知识编写一些漂亮的东西了。当然，我强 
调过一点，你必须了解你为之开发的 mud，了解它的 mudlib，它的风格， 
多多阅读 mud 带的文档和说明。当然只是了解 lpc 入门 介绍的东西， 
对 lpc 的了解是很基础的，如果你想更进一步，最好的方法是，多读 
程序，多写程序，积累经验。Lpc 只是 C 的很小的一个子集，有一点点 
的特殊的东西，是很容易掌握的。 
 
maht( trill ) 
11/12/97 3:53PM 
trill@XO (tm) 
