前面已经接触到函数了,几乎所有语言都会引入函数的概念。在Lua中,函数是一种对语句和表达式进行抽象的主要机制。
函数既可以完成某项特定的任务,也可以只做一些计算并返回结果。
第一种情况,一句函数调用被视为一条语句;而在第二种情况中,则将其视为一句表达式。
函数一般有三部分构成:函数名、参数和函数体,Lua中用function来定义一个函数。
有的函数有返回值,有的则没有返回值。Lua中与其他语言不同的是:函数的返回值可以有多个。
例如:
function getData( a )
return 1, 2, 3 , a
end
与其他语言一样,函数名后面跟一对括号,参数放在括号中,返回值用return,
最后用end表示该函数结束,这个不能忘记,否则就会导致错误。
1.参数有默认值的情况
function inccount( n )
n = n or 1
count = count + n
end
对于inccount函数,如果没有传入参数,那么n就使用默认值 1,这样就用 or 实现了参数有默认值的功能。
2.省略括号的情况
当函数的参数只有一个,且传入的是字面字符串或者table构造式,那么就可以把括号省去不写:
print “ Hello lua ”
print { 2, 3, 4, 5}
dofile ' a.lua '
3.多重返回值
上面已经看到了多重返回值的情况,再讨论一下不同的使用情况。
当函数的调用作为一条单独语句时,Lua会丢弃所有的返回值。若将函数作为表达式的一部分来
调用时,Lua只保留函数的第一个返回值。只有当一个函数调用是一系列表达式中的最后一个元素时,
才能获得它的所有返回值。
对于函数: function func1( ) return a, b end, 调用时:x, y = funct1( ), 可以得到函数返回的两个值,
如果:x,y = func1( ), 20, 这样调用就只能得到第一个返回值。也就是说,只要函数的调用不是在最后,
就只能得到一个返回值。所以,x, y, z= func1, 20, 即使这样调用,x = a,y = 20, z = nil,也只能得到a,
且 z 没有被赋值。
4.变长参数(variable number of arguments)
Lua中可以接收不同数量的实参,C++中也有这种功能,用三个点(...)来表示所有的参数。
function foo( ... )
print(" parameters: ", ... )
end
上面这个函数就是打印所有的变长参数。也可以在变长参数前加任意个数的固定参数。函数select可以
访问变长参数的个数,调用select函数时必须传入一个固定实参selector和一系列变长参数。如果selector
为数字n,纳闷select返回它的第n个可变实参;否则,select只能为字符“#”,这样select会返回变长参数的总数。
for i = 1, select ( ' # ', ... ) do
local arg = select ( i, ... )
print( arg )
end
上面演示了循环打印出所有的变长参数。
注意:select ( ' # ', ... )会返回所有的变长参数,其中包括 nil。
5.具名实参(named arguments)
看一行Objective-C的代码:
[ button addTarget: self action:@selector(doAction) forControlEvents:UIControlEventTouchUpInside ]
这是给一个按钮添加一个点击回调事件的方法,每个参数前都有一个部分方法名对应,
很容易区分各个参数的意义,Lua没有这个功能。但是,我们可以一些细微的改变来
获得相同的效果。这时要用到当参数只有一个table构造式时,可以省略圆括号。
rename { old = " oldname.lua ", new = " newname.lua " }
6.匿名函数
很多时候我们会使用匿名函数,最常用的就是回调函数,例如 table.sort 这个函数中会传入一个定义
排序规则的次序函数就用到了匿名函数:
table.sort( tab, function ( a, b ) return ( a.name > b.name ) end )
7.闭包函数 (closure)
将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,
这项特征称之为”词法域“,也称这个局部变量为“非局部变量(non-local variable),那么闭包就
是一个函数加上该函数所需访问的所有“非局部变量”。考虑以下代码:
function newCounter ( )
local i = 0
return function ( )
i = i + 1
return i
end
end
调用函数:
c1 = newCounter ( )
print( c1( ) ) -------> 1
print( c1( ) ) -------> 2
c2 = newCounter ( )
print( c2( ) ) -------> 1
print( c1( ) ) -------> 3
print( c2( ) ) -------> 2
咋一看,得到的结果很神奇,c1( )第二次调用还会使用上一次调用 i 保存的值,这个变量 i 就是“非局部变量”,
也称“upvalue”。
用一个C++的例子来解释:
class Counter {
public:
Counter() : i(0) {}
int operator () () {
return i++;
}
int i;
};
类Counter中的成员变量 i 就相当于newCounter中的 i ,只要实例化一个Counter对象,那么每次调用
operator ( ) 都会对同一个i 加1,和闭包的原理是类似的。
8. 非全局函数(non-global function)
由于函数是“第一类值”,所以很明显,函数不仅可以存储在全局变量中,还可以存储在table的字段中和局部变量中。
Lib = { }
function Lib.foo( x, y ) return x + y end
function Lib.goo( x, y ) return x - y end
这相当于:
Lib = {
foo = function( x, y ) return x + y end
goo = function( x, y ) return x - y end
}
注意:Lua为面向对象式的调用也提供了一种特殊的语法——冒号操作符。所以,Lib.foo(x,y)
也可以写成:Lib:foo( x, y )。后面会在面向对象编程中详细介绍。
9.递归函数
一个错误的递归函数定义:
local fact = function ( n )
if n == 0 then return 1
else return n * fact( n - 1 )
end
end
想想为什么?
改成:
local fact
fact = function ( n )
if n == 0 then return 1
else return n * fact( n - 1 )
end
end
现在就对了。
也可改成:
local function fact ( n )
if n == 0 then return 1
else return n * fact( n - 1 )
end
end
这样也是没问题的。
10.尾调用(tail call)
所谓“尾调用”就是一种类似goto的函数调用。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。
如:
function f ( x ) return g ( x ) end
只有这种形式才算“尾调用”。
“尾调用”的作用是“尾调用消除(tail-call elimination)”,也就是在“尾调用”之后,程序不需要保存任何关于该函数的栈信息。
关于函数的内容全部总结完毕!