			基础 LPC
		作者: Descartes of Borg
		第一版: 23 april 1993
		第二版: 22 june 1993

第四章: 函式 (functions)

4.1 回顾

现在, 你应该了解 LPC  物件由许多处理变数的函式所组成. 函式执行时就处理
变数, 而经由“呼叫”执行这些函式. 在一个档案里, 函式之间的前后顺序是无
关紧要的. 变数在函式里面被处理, 变数储存在电脑的记忆体中, 而电脑把它们
当作 0  与 1  来处理. 利用定义资料型态这种方法, 这些 0  与 1  被转换成
可使用的输出及输入结果. 字串 (string) 资料型态告诉 driver , 让你看到或
你输入的资料应该是许多字元及数字的形式. 整数 (int)  型态的变数对你来说
就是整数值. 状况 (status) 型态对你来说就是 1  或 0. 无 (void) 资料型态
对你或对机器而言都没有值, 并不是用于变数上的资料型态.

4.2 什么是函式 ?

就像数学函式, LPC 函式获得输入值, 然后传回输出值. 像 Pascal 语言把程序
(procedure) 和函式 (function) 区分开来. 但是 LPC  不这样做, 而知道这种
区分也是有用的. Pascal  称为程序的东西, 在 LPC  就是无传回值 (void) 型
态的函式. 也就是说, 程序或无传回值函式没有传回输出值. Pascal  称为函式
的东西, 就是有传回输出值的. 在 LPC  里, 最短的正确函式是:

-----
void do_nothing() { }
-----

这个函式不接受输入, 没有任何指令, 也不传回任何值.

要写出正确的 LPC  函式有三个部分:
1)  宣告 (declaration)
2)  定义 (definition)
3)  呼叫 (call)

就像变数一样, 函式也要宣告. 这样一来, 让 driver 知道: 1)  函式输出的资
料是什么型态 2) 有多少个输入的资料以及它们的型态为何. 比较普通的讲法称
这些输入为参数 (parameter).
所以, 宣告一个函式的格式如下:
传回值型态  函式名称 (参数 1, 参数 2, ...,  参数 N);
底下宣告一个 drink_water()  的函式, 它接受一个字串输入, 而输出一个整数:

-----
int drink_water(string str);
-----

str 是输入的变数名称, 会用于函式之中.

函式定义是描述函式实际上如何处理输入值的程式码. 

呼叫则是其他函式之中, 呼叫并执行此函式的地方. 对 write_vals() 和 add()
两个函式来说, 你可能会有这些程式码:

-----
/*  首先, 是函式宣告. 它们通常出现在物件码的开头.
*/
void write_vals();
int add(int x, int y);

/*  接着是定义 write_vals() 函式. 我们假设这函式将会在物件以外被呼叫.
*/
void write_vals() {
    int x;

    /*  现在我们指定 x  为呼叫 add()  的输出值. */
    x = add(2, 2);
    write(x+"\n");
}

/*  最后, 定义 add() */
int add(int x, int y) {
    return (x + y);
}
-----

请记得, 哪一个函式定义在前都没有关系. 这是因为函式并不是由前往后连续执
行的. 函式只有被呼叫时才会执行. 唯一的要求是, 一个函式的宣告必须出现在
函式的定义之前, 而且也必须在任何函式定义呼叫它之前.

4.3 外部函式 (efuns)

也许你已经听过有人提过外部函式. 它们是外部定义的函式. 跟名称一样, 它们
由 mud driver 所定义. 如果你已经撰写 LPC  程式码很久, 你大概已经发现你
听到的一些式子, 像是 this_player(), write(), say(), this_object()...  
等等, 看起来很像函式. 这是因为它们是外部函式. 外部函式的价值在于它们比
LPC 函式要快得多, 因为它们早已经以电脑了解的二进位格式存在着.

在前面的 write_vals() 函式里, 呼叫了两个函式. 第一个是 add()  函式, 是
你宣告及定义的函式. 第二个, 则是称做 write()  的外部函式. driver  早就
帮你宣告并定义这个函式. 你只需要呼叫它.

创造外部函式是为了处理普通的、每天都用得到的函式呼叫、处理 internet
socket  的输出与输入、其他用 LPC  难以处理的事. 它们是在 game driver 
内以 C  写成的, 并与 driver 一起编译在 mud  开始之前, 让它们执行起来快
得多. 但是对你来说, 外部函式呼叫就像对你的函式呼叫一样. 不过, 任何外部
函式还是要知道两件重要的事: 1)  它的传回值是什么, 2)  它要什么参数.

外部函式的详细资料, 像是输入参数和传回值, 常常可以在你的 mud 中的
/doc/efun 目录找到. 我没有办法在这里详细介绍外部函式, 因为每种 driver
的外部函式都不相同. 但是, 你常常可以藉由“man” 或“help”指令 (视
mudlib  而定) 找到详细的资料. 例如指令“man write” 会给你 write  外部
函式的详细资料. 如果都不行, “more /doc/efun/write”也可以.

看过 write  的详细资料之后, 你应该找到 write  是宣告成这样:

-----
void write(string);
-----

这样告诉你, 要正确呼叫 write  不应该期待它有传回值, 而且要传入一个字串
型态的参数.

4.4 定义你自己的函式

虽然在档案中, 你的函式次序谁先谁后都没有关系, 但是定义一个函式的程式码
的先后顺序就非常重要. 当一个函式被呼叫时, 函式定义中的程式码按照出现的
先后顺序执行. 先前的 write_vals() 中, 这个指令:
    
-----
x = add(2, 2);
-----

如果你想看到 write()  使用正确的 x  值, 就必须把它放在 write()  呼叫之前.

当函式要传回一个值时, 由“return”指令之后跟着与函式相同资料型态的值所
完成. 在先前的 add()  之中, 指令“return (x+y);” 把 (x+y)  的值传回给
write_vals()  并指定给 x. 在更普通的层次上来说, “return”停止执行函式
, 并传回程式码执行的结果给呼叫此函式的函式. 另外, 它将跟在它后面任何式
子的值传回呼叫的函式. 要停止执行失去控制的无传回值函式, 使用 return;
而后面不用加上任何东西. 请再次记得, 使用“return”传回任何式子的资料型
态“必须”与函式本身的资料型态相符合.

4.5 本章总结

定义 LPC  物件的档案是由函式所组成的. 函式依次由三个部分组成:
    1)  宣告
    2)  定义
    3)  呼叫
函式宣告通常出现在档案的最前面, 在任何定义之前. 不过函式只要求在函式定
义之前以及任何函式呼叫它之前宣告它.
函式定义可以任何顺序出现在档案里, 只要它们都放在宣告之后. 另外, 你不可
以再一个函式里面定义另一个函式.
函式呼叫则出现在其他任何函式中, 任何程式码想执行你的函式的地方. 呼叫也
可以出现在自己的函式定义中, 但是这种做法并不建议给新手去做, 因为它很容
易变成无穷回圈.

函式定义依序由底下的部分所组成:
    1)  函式传回值型态
    2)  函式名称
    3)  一个左小括号 (  接着列出参数再加上一个右小括号 )
    4)  一个左大括号 {  指示 driver 从这里开始执行
    5)  宣告只用在这个函式中的任何变数
    6)  指令、式子、视需要呼叫其他函式
    7)  一个右大括号 }  描述函式码在此结束. 对于无传回值函式来说, 如果
        在此还没有碰到“return”指令 (只适用于无传回值函式) , 会如同有
        碰到“return”指令一样回到原来呼叫的函式执行.

最短的函式是:

-----
void do_nothing() {}
-----

因为这个函式不接受任何输入, 不做任何事, 也不传回任何输出.

任何无传回值型态以外的函式“必须”传回一个与函式资料型态相同的值.

每一种 driver 都有一套早已经帮你定义好的函式, 它们叫做外部函式. 你不需
要宣告或定义它们, 因为它们早已经帮你做好这些事. 更深入一点, 执行这些函
式比起执行你的函式要快得多, 因为外部函式是 driver 的一部份. 再者, 每一
个 mudlib 都有特殊函式像是外部函式一样, 早已经为你宣告并定义好. 但是不
同的是, 它们用 LPC  定义在 mudlib 里面. 它们叫做模拟外部函式
(simul_efuns, 或 simulated efuns). 在大多数的 mud  里, 你可以在
/doc/efun 目录底下找到关于它们的详细资料. 另外, 很多 mud  有称作
“man ”或“help”的命令, 让你可以方便地叫出这些资料档案.

程式风格的注解:

有些 driver 可能不会要求你宣告函式, 有些不会要求你指定函式的传回值型态. 
无论如何, 底下有两个理由劝你不要省略以上这些动作:
    1)  对其他人来说 (还有你自己过了一段时间之后) , 会比较容易读懂你的
        程式码并了解程式码的意义. 这对除错时特别有用, 有很多错误 (除了
        放错地方的各种括号) 发生在资料型态上 (有没有碰过“Bad arg 1 to
         foo() line 32”? (程式第三十二行, 呼叫 foo() 时的第二个参数有错) ).
    2)  大家认为这样子写程式是个好习惯.

翻译:
	Spock of Final Frontier		98.Jan.25.
