第四章 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" 要在你的写的代码的开头部分。