《Lua程序设计》之 函数
六、函数
函数的参数,一般都要圆括号括起来(),只有一个情况例外,就是当函数只有一个参数且该参数是字符串常量或表构造器时,括号是可选,如下:
print "Hello, World!" <--> print("Hello, World!")
dofile 'a.lua' <--> dofile('a.lua')
print [[a multi-line message]] <--> print([[ a multi-line message]])
f{a=10,b=20} <--> f({a=10,b=20})
type{} <--> type({})
Lua语言标准库中所有的函数都是使用C语言编写的,无论一个函数使用Lua语言编写的还是用C语言编写的,在调用它们时都没有任何区别。
调用参数时使用的参数个数可以与定义函数时使用的参数个数不一致。Lua语言会通过抛弃多余参数和将不足的参数设为nil的方式来调整参数的个数。
6.1 多返回值
三个例子函数
> function foo0() end
> function foo1() return 'a' end
> function foo2() return 5,4 end
a. 当函数被作为一条单独语句调用时,其所有返回值会被丢弃
> if 1 then
>> foo2()
>> end
>
单独执行foo2()函数没有任何返回值
b. 当函数被作为表达式(例如,加法的操作数)调用时,将只保留函数的第一个返回值
> foo2()+6
11
> 6+foo2()
11
c. 只有当函数调用是一系列表达式中(多重赋值、函数调用时传入的实参列表、表构造器和return语句)的最后一个表达式(或是唯一一个表达式)时,则其所有的返回值才能被获取到
- 多重赋值的最后一个表达式
> a,b = foo2()
> a,b
5 4
> c = foo2()
> _,d = foo2()
> c,d
5 4
> _,d = foo2(),10 --多重赋值但不是最后一个表达式
> d
10
- 函数调用时传入的实参列表的最后一个表达式
> print(foo2())
5 4
> print(foo2(),1) --这里不是最后一个表达式,就只返回一个值
5 1
> print(foo2() .."x")
5x
表构造器会完整的接收函数调用的所有返回值,也只有当函数调用是表达式列表中的最后一个时才有效
> t = {foo2()}
> t[1],t[2],t[3]
5 4 nil
> t = {foo2(),88,00}
> t[1],t[2],t[3]
5 88 0
将函数用一对圆括号括起来可以强制其中返回一个值
> print((foo2()))
5
所以return后面的内容是不需要加括号的,无论f返回几个值,return (f(x))就只会返回一个值
6.2 可变长参数函数
可变长参数表达式,Lua语言使用三个点表示 ... ,看如下例子,解决传入参数的列表为非序列时,怎么忽略nil,继续往下循环加
function add( ... )
print('add() param is ', ...)
local sum = 0
for _,v in ipairs({...}) do
sum = sum + v
end
return sum
end
print('sum = ',add(4,5,6,4))
print('sum = ',add(4,nil,6,4))
function add2( ... )
print('add2() param is ', ...)
local arg = table.pack(...)
print('arg.n',arg.n)
local sum = 0
for i=1,arg.n do
sum = sum + (arg[i] or 0)
end
return sum
end
print('sum = ',add2(4,5,6,4))
print('sum = ',add2(4,nil,6,4))
function add3( ... )
print('add3() param is ', ...)
local sum = 0
for i=1,select('#', ...) do
sum = sum + (select(i, ...) or 0)
end
return sum
end
print('sum = ',add3(4,5,6,4))
print('sum = ',add3(4,nil,6,4))
function add4( ... )
print('add4() param is ', ...)
local sum = 0
local t = {...}
for i=1,#t do
sum = sum + (t[i] or 0)
end
return sum
end
print('sum = ',add4(4,5,6,4))
print('sum = ',add4(4,nil,6,4))
结果是:
add() param is 4 5 6 4
sum = 19
add() param is 4 nil 6 4
sum = 4
add2() param is 4 5 6 4
arg.n 4
sum = 19
add2() param is 4 nil 6 4
arg.n 4
sum = 14
add3() param is 4 5 6 4
sum = 19
add3() param is 4 nil 6 4
sum = 14
add4() param is 4 5 6 4
sum = 19
add4() param is 4 nil 6 4
sum = 14
要遍历 ... 需要把这个包含在列表中 {...},从例子中我们可以看出,ipairs({...})循环遇到nil就结束,所以没有办法继续往下加,理论上我们是希望忽略掉nil,继续往下加,针对这个问题,有了add2/add3/add4 三个解决方案。
add2 使用了table.pack,该函数向{...}一样保存所有的参数,然后将其放在一个表中返回,但这个表还有一个保存了参数个数的额外字段"n"
add3 使用了select(selector,...)函数,select总是具有一个固定的参数selector,selector的值可以为"#"和任意非0数值,为'#'时,只返回参数个数,为数值时,返回从这个数值索引开始的所有参数,看例子
> select("#",'a','b','c','d')
4
> select(1,'a','b','c','d')
a b c d
> select(3,'a','b','c','d')
c d
> select(4,'a','b','c','d')
d
> select(-1,'a','b','c','d')
d
> select(0x3,'a','b','c','d')
c d
> select(8,'a','b','c','d')
> select(0,'a','b','c','d')
stdin:1: bad argument #1 to 'select' (index out of range)
stack traceback:
[C]: in function 'select'
stdin:1: in main chunk
[C]: in ?
> select(100,'a','b','c','d')
>
6.3 函数table.unpack
该函数的参数是一个数组,返回值为数组内的所有元素,和table.pack功能正好相反,通常该函数使用长度操作符获取返回值的个数,因而该函数只能用于序列。
print(string.find("Hello","ll"))
--等价于
f = string.find
t = {"Hello","ll"}
print(f(table.unpack(t)))
--指定返回元素的范围
print(table.unpack({"Sun","Mon","Tue","Wed"},2,3))
返回值
3 4
3 4
Mon Tue
使用递归实现unpack的功能
function unpack(t,n,m)
n = n or 1
m = m or #t
if n <= m then
return t[n],unpack(t,n+1,m)
end
end
print(unpack({1,3})) --1 3
6.4 正确的尾调用
尾调用(tail call),就是当一个函数的最后一个动作是调用另外一个函数而没有再进行其他工作时,就形成了尾调用,例如
function f(x)
x = x+1
return g(x)
end
在进行尾调用时不使用任何额外的栈空间,我们就将这种实现称为尾调用消除。所以尾调用的数量可以无限。在Lua语言中,只有形如return func(args)的调用才是尾调用,如以下的就不是
function f(x) g(x) end -- f在返回前不得不丢弃g返回的所有结果
return g(x)+1 -- 必须进行加法
return x or g(x) -- 必须把返回值限制为1个
return (g(x)) -- 必须把返回值限制为1个
return x[i],foo(x[j] + a*b,i+j) --这个是尾调用