Lua
语言中的函数可以是可变长参数(variadic),即可以支持数量可变的参数。例如: print
函数,虽然 print
函数是 C语言
中定义的,但也可以在 Lua
语言中定义可变长参数函数。如下所示:
function add(...) local s = 0 for _, v in ipairs {...} do s = s + v end return s end print(add(1, 2, 3, 4, 5, 6)) --> 21
参数列表中的三个点( ...
)表示该函数的参数是可变长的。当这个函数被调用时, Lua
内部会把它的所有参数收集起来,我们把这些被收集起来的参数称为函数的额外参数(extra argument)。当函数要访问这些参数时扔需要用到三个点,但不同的是此时这三个点是作为一个表达式来使用的。在上例中,表达式 {...}
的结果是一个由可变长参数组成的列表,该函数会遍历该列表来累加其中的元素。
我们将三个点组成的表达式称为可变长参数表达式,其行为类似于一个具有多个返回值的函数,返回的是当前函数的所有可变长参数。例如: print(...)
会打印出该函数的所有参数。
如下的代码创建了两个局部变量,其值为前两个可选的参数(如果参数不存在则为 nil
):
local a, b = ...
实际上,可以通过变长参数来模拟 Lua
语言中普通的参数传递机制,例如:
function foo (a, b, c)
可以写成:
function foo (...) local a, b, c = ...
形如下例的函数只是将调用它时所传入的所有参数简单地返回:
function id (...) return ... end
该函数是一个多值恒等式函数(multi-value identity function)。
下列函数的行为则类似于直接调用函数 foo
,唯一不同之处是在调用函数 foo
之前会先打印出传递给函数 foo
的所有参数:
function foo1 (...) print ("calling foo:",...) return foo(...) end
当跟踪对某个特定的函数调用时,这个技巧很有用。
Lua
语言提供了专门用于格式化输出的函数 string.format
和输出文本的函数 io.write
。我们会很自然地想到把这两个函数合并为一个具有可变长参数的函数:
function fwrite (fmt, ...) return io.write(string.format(fmt, ...)) end
注意,在三个点之前有一个固定的参数 fmt
。具有可变长参数的函数也可以具有任意数量的固定参数,但固定参数必须放在变长参数之前。 Lua
语言会先将前面的参数赋给固定参数,然后将剩余的参数(如果有)作为可变长参数。
遍历可变长参数
{...}
可以使用表达式 {...}
将可变长参数放在一个表中。不过,在某些罕见的情况下,如果可变长参数中包含无效的 nil
,那么 {...}
获得的表可能不再是一个有效的序列。此时,就没办法在表中判断原始参数究竟是不是以 nil
结尾。对于这种情况, Lua
提供了函数 table.pack
。该函数像表达式 {...}
一样保存所有的参数,然后将其放在一个表中返回,但是这个表还有一个保存了参数个数的额外字段 "n"
。
下面的函数使用了函数 table.pack
来检查参数中是否有 nil
:
function nonils (...) local arg = table.pack(...) for i = 1, arg.n do if arg[i] == nil then return false end end return true end print(nonils(1,2,nil)) --> false print(nonils(2,3)) --> true print(nonils()) --> true print(nonils(nil)) --> false
select 函数
函数 select
总是具有一个固定的参数 selector
,以及数量可变的参数。如果 selector
是数值 n
,那么函数 select
则返回第 n
个参数后的所有参数;否则, selector
应该是字符串 "#"
,以便函数 select
返回额外参数的总数。
print(select(1, "a", "b", "c")) --> a b c print(select(2, "a", "b", "c")) --> b c print(select(3, "a", "b", "c")) --> c print(select("#", "a", "b", "c")) --> 3
通常,我们在需要把返回值个数调整为 1
的地方使用函数 select
,因此可以把 select(n, ...)
认为是返回第 n
个额外参数的表达式。
function add (...) local s = 0 for i = 1, select("#", ...) do s = s + select(i, ...) end return s end
对于参数较少的情况,第二个版本的 add
更快,因为该版本避免了每次调用时创建一个新表。不过,对于参数较多的情况,多次带有很多参数调用函数 select
会超过创建表的开销,因此第一个版本会更好。
提示
由于迭代的次数和每次迭代时传入函数的个数会随着参数的个数增长,因此第二个版本的时间开销是二次代价的。