函数(function)是闭包(closure)的一种特殊形式,Lua closure 的内容将在下章提到(TODO补充链接)。
函数的构成:名称,参数表,函数体。
o.foo(o, x) 可以写成 o:foo(x),这种写法隐式地将 o 作为函数的第一个参数。——这个和 Lua OO 有些联系。将在未来展开 TODO
Lua 可以调用 Lua 编写的函数,也可以调用 C 编写的函数(怎么调用?TODO)
函数在使用时,必须把参数放在函数名后的圆括号中,如果参数个数为0,就要写一对空的圆括号。但这个规则有个例外,如果函数只有一个参数,而且这个参数是字面字符串或 table,那么这对圆括号也可以不写。这也就是为什么 HelloWorld 还可以写成 print "hello, world!"
的原因。
参数分为:
函数调用时,如果发生 paramenter 和 argument 个数不一致的情况,则按如下规则赋值:
例如,定义一个函数并按上述两种方法使用:
f = function (a, b, c)
print(tostring(a))
print(tostring(b))
print(tostring(c))
end
f(1, 2, 3, 4, 5) -- 实参多于形参
f("hello", "world") -- 实参少于形参
这段代码的输出是:
1
2
3
hello
world
nil
可以看出,第一个调用,把多余的实参4和5丢了,而第二个调用,用 nil 补足了第三个缺少的参数。
Lua 中的函数相对其他语言,比较有颠覆性,它能返回多个值,这个特性甚至颠覆了函数的传统数学定义。自定义的一个多值返回函数,求数组中最大的元素的索引和值:
function getMax(t)
local max_index = 1
local max_value = t[1]
for i, v in ipairs(t) do
if v > max_value then
max_index = i
max_value = v
end
end
return max_index, max_value
end
hello = {4543, -11, 9, 0, 34.9}
mi, m = getMax(hello)
print(mi)
print(m)
输出结果:
1
4543
其实,这个例子中还用了 Lua 内建的一个多值函数 ipairs.
返回值数量调整
在使用 Lua 多值函数时,根据调用情况不同,Lua 会调整函数的返回值数量。举例说明如下,
function foo0() end -- 无返回值
function foo1() return "a" end -- 返回1个结果
function foo2() return "a", "b" end -- 返回2个结果
如果一个函数调用是最后的(或仅有的)一个表达式,那么 Lua 会保留尽可能多的返回值,用于匹配赋值变量
x, y = foo2() -- x="a", y="b"
x = foo2() -- x="a", "b" 被丢弃
x, y, z = 10, foo2() -- x=10, y="a", z="b"
如果一个函数没有返回值或没有足够多的返回值,则用 nil 补齐
x, y = foo0() -- x=nil, y=nil
x, y = foo1() -- x="a", y=nil
x, y, z = foo2() -- x="a", y="b", z=nil
如果一个函数调用不是一系列表达式中最后一个,那么函数只返回第一个值
x, y = foo2(), 20 -- x="a", y=20
x, y = foo0(), 20, 30 -- x=nil, y=20, 30被丢弃
当一个函数调用作为另一个函数调用的最后一个(或仅有的)实参时,第一个函数的所有返回值都将作为实参传入第二个函数,否则只返回第一个值
print(foo0()) --> nil(打印出的结果是空的)
print(foo1()) --> a
print(foo2()) --> a b
print(foo2(), 1) --> a 1
print(foo2() .. "x") --> ax
可以将一个函数放入一对括号中,迫使它只返回一个值
print((foo2()))
unpack 接受数组作为参数,并从下标1返回该数组的所有元素,可以用 Lua 来实现 unpack
function unpack(t, i)
i = i or 1
if t[i] then
return t[i], unpack(t, i+1)
end
end
这段代码很巧妙,其中用到了 closure 技术,将在下章讲述。
Lua 支持变长参数(variable number of arguments, also called varargs),以三个点来表示变长参数。变长参数和固定参数可混用,但固定参数需放到变长参数之前,这点和 Java 是一样的。下面的求和函数使用了变长参数:
function add(...)
local s = 0
for i, v in ipairs{...} do
s = s+v
end
return s
end
多值恒定式函数
多值恒定函数(multi-value identity),是一个利用变长参数写的很特殊的一个函数:
function id(...) return ... end
这个函数的原理对编写调试函数很有用,如:
function foo1(...)
print("calling foo: ", ...)
return foo(...)
end
上面这个函数给被调试的函数 foo 在调用前加了一行日志打印。
如果传入的变长参数的实参中,有 nil,则需要 select 函数来访问变长参数。
for i=1, select("#", ...) do
local arg = select(i, ...) -- 得到第i个参数
<循环体>
end
另,select("#", ...)
会返回所有变长参数的总数,包括 nil
多值返回时,如果值太多,使用上很不方便,此刻可以考虑把返回值封装到一个 table 中,然后返回这个 table 就可以了。如下面的例子:
fetchPersionalInfo = function ()
local persionInfo = {}
persionInfo.locale = "Shanghai"
persionInfo.name = "Iridium"
persionInfo.sex = "male"
return persionInfo
end
print(fetchPersionalInfo().name)
如果不封装参数,就会这样子:
fetchPersionalInfo = function ()
return "Shanghai", "Iridium", "male"
end
local locale, name, sex = fetchPersionalInfo()
print(name)
它们的输出都是:
Iridium
这种技术,其实在其他语言中也早有应用。如 Java Web 开发中,浏览器提交的表单往往有相当多的值,我们一般都封装到一个 DTO 中进行传递。又如在 C 中,如果返回值过多,往往会封装成一个结构体返回。