05 - 函数
争取两三周入门 Lua!
函数(Function)是对语句和表达式进行抽象的主要方式。
函数的定义与调用
函数的定义如下:
function 函数名(参数列表) 函数体 return xxx end
调用函数时使用的参数个数可以与定义函数时使用的参数个数不一致,Lua 会抛弃多余参数并将不足的参数设为 nil
:
function f(a, b) print(a, b) end f() --> nil nil f(1) --> 1 nil f(1, 2) --> 1 2 f(1, 2, 3) --> 1 2 (3被丢弃)
这样的特性可以实现函数的默认实参:
function incCount(n) n = n or 1 -- 如果n为nil, 那么n为1 globalCounter = globalCounter + n end
多返回值
Lua 允许一个函数返回多个结果,例如 string.find()
:
s, e = string.find("hello Lua", "Lua") --> 7 9
要让自定义函数返回多个值,只需在 return
后列出所有要返回的值即可:
--- 找到最大元素及其位置 function getMax(a) local idx = 1 local val = a[idx] for i = 1, #a do if a[i] > val then idx = i val = a[i] end end return idx, val end
条件
Lua 会根据函数的被调用情况调整返回值的数量:
- 函数作为一条单独语句调用时,所有返回值都会被丢弃;
- 函数被表达式调用时(例如作为加法的操作数),将只保留第一个返回值;
- 函数作为最后一个表达式使用时(多重赋值、函数调用时传入的实参列表、表构造器和
return
语句),获取所有返回值。
这里看看最后一条:
多重赋值:
function foo() return 1, 2 end x, y = foo() -- 1 2 x, y, z = foo() -- 1 2 nil x, y = foo(), 5 -- 1 5, 这里foo()不是最后一个表达式, 因此只保留第一个返回值
函数调用时传入的实参列表:
print(foo()) -- 1 2 print(foo(), 5) -- 1 5
表构造器:
a = {foo()} -- a[1] = 1, a[2] = 2 a = {foo(), 5} -- a[1] = 1, a[2] = 5
return
语句:function foo2() return foo() end print(foo2()) -- 1 2 print((foo2())) -- 1
table.unpack()
该函数可以将一个表转换成一组返回值:
table.unpack({1,2,3}) -- 1 2 3 a, b = table.unpack({1, 2, 3}) -- 1 2 (3被丢弃)
该函数的重要用途之一体现在泛型调用机制中,该机制允许我们调用具有任意参数的任意函数。例如下面两个写法是等价的,但第二种泛型调用写法更通用:
string.find("hello", "ll") f = string.find a = {"hello", "ll"} f(table.unpack(a))
此外,该函数还有两个参数用来显式限制返回元素的范围:
table.unpack({1, 2, 3}, 2, 3) -- 2 3
可变长函数参数
遍历参数
使用...
表示该函数的参数是可变长的,例如一个求和函数:
function add(...) local sum = 0 for _, v in ipairs{...} do sum = sum + v end return sum end print(add(1, 2, 3)) -- 6
要想使用参数列表,需要用 {...}
获取由所有可变长参数组成的列表,然后才能遍历。如果参数列表中包含 nil
,就不能这样做。需要使用 table.pack(...)
,该函数获取所有可变长参数,将其打包入表中,然后添加一个额外字段”n”,内容是表的长度。
另一种遍历参数的方法是使用函数 select
,它有一个固定参数 selector
以及可变参数,有两种用法:
-- selector为数值n, 返回第n个参数和它后面的参数 print(select(2, "a", "b", "c")) -- b c -- selector为"#", 返回额外参数的总数 print(select("#", "a", "b", "c")) -- 3
可以这样遍历上面的 add(...)
:
function add(...) local sum = 0 for i = 1, select("#", ...) do sum = sum + select(i, ...) end return sum end
不过这样做性能消耗有点大,不如 for 循环。
应用
也能使用变长参数来模拟普通的参数传递机制:
function foo(a, b, c) -- 可写成 function foo(...) local a, b, c = ...
如果想要格式化输出文本,需要使用 string.format
和 io.write
函数,我们可以用可变长函数参数将它俩结合起来:
function fwrite(fmt, ...) return io.write(string.format(fmt, ...)) end