2.5.9 - 函数定义

函数定义的语法如下:

	function ::= function funcbody
	funcbody ::= `(´ [parlist1] `)´ block end

另外定义了一些语法糖简化函数定义的写法:

	stat ::= function funcname funcbody
	stat ::= local function Name funcbody
	funcname ::= Name {`.´ Name} [`:´ Name]

这样的写法:

     function f () body end

被转换成

     f = function () body end

这样的写法:

     function t.a.b.c.f () body end

被转换成

     t.a.b.c.f = function () body end

这样的写法:

     local function f () body end

被转换成

     local f; f = function () body end

注意,并不是转换成

     local f = function () body end

(这个差别只在函数体内需要引用 f 时才有。)

一个函数定义是一个可执行的表达式, 执行结果是一个类型为 function 的值。 当 Lua 预编译一个 chunk 的时候, chunk 作为一个函数,整个函数体也就被预编译了。 那么,无论何时 Lua 执行了函数定义, 这个函数本身就被实例化了(或者说是关闭了)。 这个函数的实例(或者说是 closure(闭包)) 是表达式的最终值。 相同函数的不同实例有可能引用不同的外部局部变量, 也可能拥有不同的环境表。

形参(函数定义需要的参数)是一些由实参(实际传入参数)的值初始化的局部变量:

	parlist1 ::= namelist [`,´ `...´] | `...´

当一个函数被调用, 如果函数没有被定义为接收不定长参数,即在形参列表的末尾注明三个点 ('...'), 那么实参列表就会被调整到形参列表的长度, 变长参数函数不会调整实参列表; 取而代之的是,它将把所有额外的参数放在一起通过变长参数表达式传递给函数, 其写法依旧是三个点。 这个表达式的值是一串实参值的列表,看起来就跟一个可以返回多个结果的函数一样。 如果一个变长参数表达式放在另一个表达式中使用,或是放在另一串表达式的中间, 那么它的返回值就会被调整为单个值。 若这个表达式放在了一系列表达式的最后一个,就不会做调整了(除非用括号给括了起来)。

我们先做如下定义,然后再来看一个例子:

     function f(a, b) end
     function g(a, b, ...) end
     function r() return 1,2,3 end

下面看看实参到形参数以及可变长参数的映射关系:

     CALL            PARAMETERS
     
     f(3)             a=3, b=nil
     f(3, 4)          a=3, b=4
     f(3, 4, 5)       a=3, b=4
     f(r(), 10)       a=1, b=10
     f(r())           a=1, b=2
     
     g(3)             a=3, b=nil, ... -->  (nothing)
     g(3, 4)          a=3, b=4,   ... -->  (nothing)
     g(3, 4, 5, 8)    a=3, b=4,   ... -->  5  8
     g(5, r())        a=5, b=1,   ... -->  2  3

结果由 return 来返回(参见 §2.4.4)。 如果执行到函数末尾依旧没有遇到任何 return 语句, 函数就不会返回任何结果。

冒号语法可以用来定义方法, 就是说,函数可以有一个隐式的形参 self。 因此,如下写法:

     function t.a.b.c:f (params) body end

是这样一种写法的语法糖:

     t.a.b.c.f = function (self, params) body end