0x05 函数
定义与调用形式
定义:函数是对语句和表达式的抽象。
函数的调用形式:无论是语句或者表达式,都需要将所有参数放到一对圆括号中。即使调用没有参数也必须写出一对空括号。
注:此条规则对一种情况例外—一个函数只有一个参数,并且此参数是一个字面字符串或者table构造式(见下列代码)
print "Hello World" --只有一个参数且为字面字符串
f{x = 20, y = 30} --只有一个参数且为table构造式
冒号操作符 Lua为面向对象式的调用提供了一种特殊的语法—冒号表达式 如
o.foo(o,x)
和o:foo(x)
是等价的
关于函数中的参数数量:调用函数提供的实参数量可以与形参数量不同
function f(a, b) return a or b end
--调用函数 形参
f(3) a=3 b=nil
f(3,4) a=3 b=4
f(3,4,5) a=3 b=4 5被抛弃
Lua中函数的调用规则与多重赋值的规则相似
参数规则 “实参多于形参,则舍弃多余实参;实参少于形参,则多余形参初始化为nil”
多重返回值
Lua允许函数返回多个结果
function morevals()
return "Hello", "World"
end
print(morevals()) --"Hello" "World"
可以直接将多个值在函数中进行返回
Lua将会根据实际情况进行返回参数的调整:
假如有以下函数:
function foo0() end
function foo1() return "a" end
function foo2() return "a","b" end
- 若一个函数的调用是最后的一个表达式,那么Lua会保留尽可能多的返回值
x,y = foo2() x="a" y="b"
x,y,z = 10,foo2() x=10 y="a" z="b"
- 如果一个函数没有返回值或者没有足够多的返回值,那么Lua会用nil来代替缺失的值
x,y = foo0() x=nil y=nil
x,y = foo1() x="a" y=nil
- 如果一个函数不是一系列表达式的最后一个元素,那么只返回一个值
x,y = foo2(),20 x="a" y=20
x,y = foo0(),20,30 x=nil y=20 30被抛弃
一系列表达式 : 多重赋值,函数调用时传入的实参列表,table的构造式和return语句
- 当一个函数调用作为另一个函数调用的最后一个参数时,第一个函数所有的返回值都将作为实参传入第二个函数
print (foo0())
print (foo1()) a
print (foo2()) a b
print (foo2(),10) a 10 (出现在表达式中时,Lua会将其返回值数量调整为1)
table构造式可以完整接收所有返回的参数而不会有任何的参数调整
t = {foo0()} t={} (nil)
t = {foo1()} t={"a"}
t = {foo2()} t={"a","b"}
不过这种情况是只有当函数调用作为最后一个参数时才会发生,如果是在其他位置则只会返回一个值
- return语句:
return f()
将会返回f()
中的所有返回值
变长参数
Lua中的函数可以接收不同数量的实参,看下面代码
function add(...)
local s = 0
for i,v in ipairs{...} do
s = s + v
end
return s
end
print(add(1,2,3,4)) -->10
参数表中的...
表示可接收不同数量的实参。当这个函数被调用时,表示所有的参数会被收集到一块。函数中使用时还依然需要使用...
但此时是作为一个表达式来使用的–>{...}
表示一个有所有参数构造的数组。
表达式...
的行为类似一个具有多重返回值的函数。
selector 用于操作变长参数
如果selector为数字n,则返回它的第n个可变实参;否则selector只能为字符串#
,这样select会返回变长参数的总数。下面的代码演示了如何用selector
来遍历所有的参数
for i=1, select('#', ...) do
local arg = select(i, ...)
<循环体>
end
--Lua5.0的代码如下
function foo(a, b, ...)
local arg = {...};
arg.n = select("#", ...)
end
具名实参
通过名称来指定实参
例如一个函数os.rename,用于更改文件名
rename{old = "temp.lua", new = "temp1,lua"}
作用:当一个函数拥有大量的参数,而其中大部分参数是可选的话,具名实参会非常有用。
w = Window{ x=0, y=0, width=200, height=100, title="lua"}
函数是第一类值,这表示函数可以与其他传统类型的值一样存储到变量中
函数还具有词法域:指一个函数可以嵌套在另外一个函数中,内部函数可以访问外部函数
对于函数print(),其实是指一个存储着可以打印字符串函数的变量print,这个变量可以存储其它的函数
a = print
a("Hello World") --Hello World
print = math.sin --现在print变量存储了math.sin引用的函数
a(print(1.0)) --调用了正弦函数,打印出结果 0.8414709848079
函数可以被当作一种类型
function foo() return "a" end
等价于
foo = function () return "a" end
表达式
function(x) <body> end
可以被当作一种函数的构造式 如 table构造式一般
function
表达式的应用,table.sort
接受一个table并对其进行排序,其中一个次序函数就可以在需要时直接使用function表达式的创建
network = {
{name = "a", IP = "1.2.3.4"},
{name = "b", IP = "2.4.21.3"},
{name = "c", IP = "2.32.24.33"}
}
--接下来调用table.sort
tabel.sort(network, function(a, b) return(a.name > b.name) end)
闭合函数
若将一个函数写在另外一个函数里面,那么这个位于内部的函数便可以访问外部函数中的局部变量。
names = {"Peter", "Paul", "Mary"}
gradees = {Mary = 10, Paul = 7, Peter = 8}
table.sort(names, function(n1, n2)
return grades[n1] > grades[n2]
end)
上面代码中的grades
在这个匿名函数中既不是全局变量也不是局部变量,称为 “非局部的变量”
看一下以下的代码,说明一下非局部的变量存在的合理性:
function newCounter()
local i = 0
return function()
i = i + 1
end
end
c1 = newCounter()
print(c1()) --1
print(c1()) --2
c2 = newCounter()
print(c2()) --1
Lua将以上的情况视为Closure
,一个Closure
代表一个函数加上这个函数所访问的非局部的变量。函数c1
c2
是同一个函数创建的不同的Closure
(闭合函数),他们拥有不同的非局部的变量i
非全局的函数
将函数与table
结合起来
test = {}
test.foo1 = function(a, b) return a+b end
test.foo2 = function(a, b) return a-b end
test = {
foo1 = function(a,b) return a+b end,
foo2 = function(a,b ) return a-b end
}
--此外 Lua还提供了另外一种方式
test = {}
function test.foo1(a, b)
return a + b
end
function test.foo2(a, b)
return a - b
end
只要将一个函数存储在一个局部变量中,就得到一个局部函数
,该函数只能在特定的作用域中使用
局部函数的定义
local f = function(参数)
<函数体>
end
local function f(参数)
<函数体>
end
递归函数中会较多的使用到局部函数:
local fact = function(n)
if(n==0) then return 1
else return fact(n-1) --Error
end
end
在使用fact(n-1)
时局部的fact
的定义尚未完成,因此此时调用的是全局的fact
函数。可以先定义一个局部变量,保存这个函数:
local fact
fact = function(n)
if(n==0) then return 1
else return fact(n-1)
end
end
尾调用
当一个函数调用是另一个函数的最后一个动作时,该调用是一条“尾调用” 尾调用不会耗费任何栈空间(尾调用消除)
function f(x) return g(x) end
--由于有尾调用消除的特性,下列函数不管传入什么数字都不会造成栈溢出
function foo (n)
if n >0 then return foo(n-1) end
end
判断是否符合尾调用消除的原则:
一个函数在调用完另外一个函数后,是否就无其他事情需要做了?
在Lua中只有return <func>(<args>)
这样的调用形式才算是一条尾调用