第四章: 函式 (functions)

MudOS v21c2

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

回上一页