Lua支持面向对象,操作符为冒号‘:’。o:foo(x) <==> o.foo(o, x).
Lua程序可以调用C语言或者Lua实现的函数。Lua基础库中的所有函数都是用C实现的。但这些细节对于lua程序员是透明的。调用一个用C实现的函数,和调用一个用Lua实现的函数,二者没有任何区别。
函数的参数跟局部变量一样,用传入的实参来初始化,多余的实参被丢弃,多余的形参初始化为nil。
count=
function incCount(n)
n=n or
count=count+n end
incCount()
print(count) incCount()
print(count)
多返回值
不同于常规函数,Lua的函数可以返回多个返回值。一些Lua中预定义的函数可以返回多个返回值。例如string.find函数,在string中匹配一个sub-string,string.find返回sub-string的起始位置和结束位置。利用多赋值语句来获取函数的多个返回值。
s,e=string.find("hello Lua","Lua")
print(s,e)
7 9
function maximum(a)
local mi= --index
local m=a[mi] for i,val in ipairs(a) do
if val>m then
mi=i;m=val
end
end return m,mi
end print(maximum({,,,,}))
ua会根据实际情况来使函数的返回值个数适应调用处的期望。
1)如果一个函数调用作为一条语句,所有的返回值都被丢弃
2)如果一个函数调用作为一个表达式的一部分时,返回值只保留第一个
3)只有当一个函数调用是一系列表达式中的最后一个元素(或仅有一个元素)时,才能获取它的所有返回值。这里的
“一系列表达式”在lua中表现为4中情况:
多重赋值,函数调用时传入的实参列表,,table的构造式,return语句中,。
在多重赋值中,若一个函数调用是最后的(或仅有的)一个表达式,那么Lua会保留尽可能多的返回值,用于
匹配赋值变量。
1.
function foo0() end
function foo1() return "a" end
function foo2() return "a","b" end
x,y=foo2()
x=foo2() x="a",b被丢弃。
x,y,z=10,foo2() ->x=10,y="a",z="b"
2.
如果一个函数调用不是一系列表达式的最后一个元素,那么将只产生一个值:
x,y=foo2(),"c"
print(x,y) a,c
当一个函数调用作为另一个函数调用的最后一个(或仅有的)实参时,第一个函数的所有返回值都将作为实参传入第二个函数。
这样的例子已经见到很多了,如print。:
print(foo2()) --a b
print(foo2(),1) --a 1
3. table构造式可以完整接受一个函数调用的所有结果。
t={foo2()}
print(#t)
4.
最后一个情况是return语句,诸如return f()这样的语句,将返回f的所有返回值。
也可以将一个函数调用放入一对圆括号中,从而迫使它只返回以结果:
print((foo2())) --a
请主语return语句后面的内容是不需要圆括号的,如果写成
return (f(x))
将只返回一个值。
关于多重返回值还有介绍一个特殊函数--unpack,他接受一个数组作为参数,并从下标1开始返回该数组的所有元素:
print(unpack{10,20,30})
a,b=unpack{10,20,30} --a=10,b=20
An important use for unpack
is in a generic call mechanism泛型调用. A generic call mechanism allows you to call any function, with any arguments, dynamically. In ANSI C, for instance, there is no way to do that. You can declare a function that receives a variable number of arguments (with stdarg.h
) and you can call a variable function, using pointers to functions. However, you cannot call a function with a variable number of arguments: Each call you write in C has a fixed number of arguments and each argument has a fixed type. In Lua, if you want to call a variable function f
with variable arguments in an array a
, you simply write
f(unpack(a))
The call to unpack
returns all values in a
, which become the arguments to f
. For instance, if we execute
f = string.find
a = {"hello", "ll"}
then the call f(unpack(a))
returns 3 and 4, exactly the same as the static call string.find("hello", "ll")
.
Although the predefined unpack
is written in C, we could write it also in Lua, using recursion: 使用递归实现unpack。
function unpack (t, i)
i = i or 1
if t[i] ~= nil then
return t[i], unpack(t, i + 1)
end
end
The first time we call it, with a single argument, i
gets 1. Then the function returns t[1]
followed by all results from unpack(t, 2)
, which in turn returns t[2]
followed by all results from unpack(t, 3)
, and so on, until the last non-nil element.
变参
Lua中的一些函数接受可变数量的参数,例如print函数。print函数是用C来实现的,但是我们也可以用Lua来实现变参函数。下面是一个示例:
function add(...)
local s=
for i,v in ipairs{...} do
s=s+v
end
return s
end print(add(,,))
参数中的3个点(...)表示该函数可接受不同数量的实参,调用时3个点代表了实参。
表达式"..."的行为类似于一个具有多重返回值的函数,他返回的是当前函数的所有变长参数:
local a,b=...
上例用第一个和第二个变长参数来初始化这两个局部变量,实际上,还可以通过变长参数来模拟Lua中的
普通的参数传递机制。
如:
function foo(a,b,c)
等价于
function foo(...)
local a,b,c=...
end
对于那些喜爱Perl参数传递机制的人来说,可能会倾向于第二种形式。
如有这样一个函数:
function id(...)
return ...
end
他只是简单的返回所有市场。这是一个“多值恒定式(multi-value identity)"函数。
下面的这个函数行为非常类似于直接调用foo,但在调用foo()前先调用print:
function foo(...)
print("calling foo:",...)
return foo(...)
end
这种技巧对于跟踪某个特定函数调用很有帮助。
具有参数named argu
(lua函数调用特殊语法,当实参只有一个table时,可以省略圆括号)。
w = Window{ x=0, y=0, width=300, height=200,
title = "Lua", background="blue",
border = true
}
indow函数可以检查必须的参数,并且给可选参数赋予默认值等。假设_Window函数可以用来创建一个新窗口,但是它必须要全部的参数。那我们就可以重新定义一个Window函数如下:
function Window (options)
-- check mandatory options
if type(options.title) ~= "string" then
error("no title")
elseif type(options.width) ~= "number" then
error("no width")
elseif type(options.height) ~= "number" then
error("no height")
end -- everything else is optional
_Window(options.title,
options.x or , -- default value
options.y or , -- default value
options.width, options.height,
options.background or "white", -- default
options.border -- default is false (nil)
)
end
二、深入函数:
在Lua中有一个容易混淆的概念是,函数与所有其他值一样都是匿名的,即他们都没有名称。当讨论一个函数名时(例如print)。实际上是在讨论持有某函数的变量,这与其他变量持有各种值一个道理,下面这个实例足以说明
a = { p = print }
a.p("Hello World")
b = print
b("Hello World")
function foo(x) return 2 * x end
这只是一种所谓的”语法糖“而已,实际等价于:
foo = function(x) return 2 * x end
因此,一个函数定义实际上就是一条语句(更准备说是赋值语句)。这条语句创建了一种类型为”函数“的值。可以将表达式"function(x)<body> end"视为一种函数的构造式,就像table的构造式{}一样。将这种函数构造式的结果称为一个”匿名函数”。
下面的示例显示了匿名函数的方便性,它的使用方式有些类似于Java中的匿名类,如:
table.sort(test_table,function(a,b) return (a.name > b.name) end)
像sort这样的函数,接受另一个参数作为实参的,称其是一个”高阶函数“,高阶函数是一种 强大的编程机制,应用匿名函数来创建高阶函数所需的实参则可以带来更大的灵活性。但请记住,高阶函数并没有什么特权。Lua强调将函数视为”first-class valeu",所以,高阶函数只是基于该观点的应用体现而已。
1. closure(闭合函数):闭包
若将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,见如下示例:
function newCounter()
local i =
return function() --匿名函数
i = i +
return i
end
end
c1 = newCounter()
print("The return value of first call is " .. c1())
print("The return value of second call is " .. c1())
--输出结果为:
--The return value of first call is 1
--The return value of second call is 2
简单来讲,一个closure就是一个函数加上该函数所需访问的所有“非局部变量”。
在上面的示例中,我们将newCounter()函数称为闭包函数。其函数体内的局部变量i被称为"非局部变量",和普通局部变量不同的是该变量被newCounter函数体内的匿名函数访问并操作。再有就是在函数newCounter返回后,其值仍然被保留并可用于下一次计算。再看一下下面的调用方式。
在上面的示例中,我们将newCounter()函数称为闭包函数。其函数体内的局部变量i被称为"非局部变量",和普通局部变量不同的是该变量被newCounter函数体内的匿名函数访问并操作。再有就是在函数newCounter返回后,其值仍然被保留并可用于下一次计算。再看一下下面的调用方式。
function newCounter()
local i =
return function() --匿名函数
i = i +
return i
end
end
c1 = newCounter()
c2 = newCounter()
print("The return value of first call with c1 is " .. c1())
print("The return value of first call with c2 is " .. c2())
print("The return value of second call with c1 is " .. c1())
--输出结果为:
--The return value of first call with c1 is 1
--The return value of first call with c2 is 1
--The return value of second call with c1 is 2
由此可以推出,Lua每次在给新的闭包变量赋值时,都会让不同的闭包变量拥有独立的"非局部变量"。下面的示例将给出基于闭包的更为通用性的用法:
do
--这里将原有的文件打开函数赋值给"私有变量"oldOpen,该变量在块外无法访问。
local oldOpen = io.open
--新增一个匿名函数,用于判断本次文件打开操作的合法性。
local access_OK = function(filename,mode) <检查访问权限> end
--将原有的io.open函数变量指向新的函数,同时在新函数中调用老函数以完成真正的打开操作。
io.open = function(filename,mode)
if access_OK(filename,mode) then
return oldOpen(filename,mode)
else
return nil,"Access denied"
end
end
end
这个示例的精彩之处在于,经过重新定义后,一个程序就只能通过新的受限版本来调用原来那个未受限的open函数了。示例将原来不安全的版本保存到closure的一个私有变量中。从而使得外部再也无法直接访问原来的版本了。
2. 非全局函数:
从上一小节中可以看出,Lua中的函数不仅可以直接赋值给全局变量,同时也可以赋值给其他类型的变量,如局部变量和table中的字段等。事实上,Lua库中大多数table都带有函数,如io.read、math.sin等。这种写法有些类似于C++中的结构体。如:
Lib = {}
Lib.add = function(x,y) return x + y end
Lib.sub = function(x,y) return x - y end
或者是在table的构造式中直接初始化,如:
Lib = { add = function(x,y) return x + y end,
sub = function(x,y) return x - y end
}
除此之外,Lua还提供另外一种语法来定义此类函数,如:
Lib = {}
function Lib.add(x,y) return x + y end
function Lib.sub(x,y) return x - y end
对于Lua中的局部函数,其语义在理解上也是非常简单的。由于Lua中都是以程序块作为执行单元,因此程序块内的局部函数在程序块外是无法访问的,如:
do
2 local f = function(x,y) return x + y end
3 --do something with f.
4 f(4,5)
5 end
对于这种局部函数,Lua还提供另外一种更为简洁的定义方式,如:
local function f(x,y) return x + y end
该写法等价于:
local f
f = function(x,y) return x + y end
3. 正确的尾调用:
在Lua中支持这样一种函数调用的优化,即“尾调用消除”。我们可以将这种函数调用方式视为goto语句,如:
function f(x) return g(x) end
由于g(x)函数是f(x)函数的最后一条语句,在函数g返回之后,f()函数将没有任何指令需要被执行,因此在函数g()返回时,可以直接返回到f()函数的调用点。由此可见,Lua解释器一旦发现g()函数是f()函数的尾调用,那么在调用g()时将不会产生因函数调用而引起的栈开销。这里需要强调的是,尾调用函数一定是其调用函数的最后一条语句,否则Lua不会进行优化。然而事实上,我们在很多看似是尾调用的场景中,实际上并不是真正的尾调用,如:
function f(x) g(x) end --没有return语句的明确提示
function f(x) return g(x) + 1 --在g()函数返回之后仍需执行一次加一的指令。
function f(x) return x or g(x) --如果g()函数返回多个值,该操作会强制要求g()函数只返回一个值。
function f(x) return (g(x)) --原因同上。
在Lua中,只有"return <func>(<args>)"形式才是标准的尾调用,至于参数中(args)是否包含表达式,由于表达式的执行是在函数调用之前完成的,因此不会影响该函数成为尾调用函数。
在之前提到了,一条“尾调用”就好比是一条“goto语句”。因此在Lua中”尾调用“的一大应用就是编写”状态机(state machine)"。这种程序通常以一个函数来表示一个的状态,改变状态就是goto到另一个特定的函数。举一个简单的迷宫游戏的例子来说明这个问题。例如,一个迷宫有几间房间,每间房间中最多有东南西北4扇门。用户在每一步移动中都需要输入一个移动的方向。如果在某个方向上有门,那么用户可以进入相应的房间;不然,程序就打印一条警告。游戏目标是让用户从最初的房间走到最终的房间。
这个游戏就是一种典型的状态机,其中当前房间是一种状态。可以将迷宫中的每间房间实现为一个函数,并使用”尾调用“来实现从一间房间移动到另一间房间。在以下代码中,实现一个具有4间房间的迷宫:
function room1 ()
local move = io.read()
if move == "south" then return room3()
elseif move == "east" then return room2()
else print("invalid move")
return room1() -- stay in the same room
end
end function room2 ()
local move = io.read()
if move == "south" then return room4()
elseif move == "west" then return room1()
else print("invalid move")
return room2()
end
end function room3 ()
local move = io.read()
if move == "north" then return room1()
elseif move == "east" then return room4()
else print("invalid move")
return room3()
end
end function room4 ()
print("congratulations!")
end
通过调用初始房间来开始这个游戏:
room1()
若没有”尾调用消除“的话,每次用户的移动都会创建一个新的stack,移动若干步之后就有可能导致栈溢出。而”尾调用消除“则对用户移动的次数没有任何限制。这是因为每次移动实际上都只是完成一条goto语句到另一个函数,而非传统的函数调用。
对于这个简单的游戏而言,或许会觉得将程序设计为数据驱动的会更好一点,其中将房间和移动记录在一些table中。不过,如果游戏中的每间房间都有各自特殊情况的话,采用这种状态机的设计则更为合适。
转自;programming in lua.