Q:Lua中如何定义以及调用函数?
A:
function foo(arg1, arg2, ...)
dosomething
return ret1, ret2, ... or nothing
end
-- add all elements of array 'a'.
function add (a)
local sum = 0
for i,v in ipairs(a) do
sum = sum + v
end
return sum
end
-- call it.
a = {1, 2, 3, 4}
foo(a)
Q:如何以面向对象的方式调用函数?
A:o:foo(x)
,其等同于o.foo(o, x)
。
Q:Lua编写的函数如何返回多个返回值?
A:在return的后面写多少个值,就可以让函数返回多少个值。
function maximum (a)
local mi = 1 -- maximum index
local m = a[mi] -- maximum value
for i,val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end
Q:Lua如何控制函数的返回值?
A:
1、当你以一条语句的形式调用函数时,lua忽略函数所有的返回值(因为也没有变量接收)。
foo()
2、当函数作为一系列表达式中最后一个表达式被调用时,lua保留函数所有的返回值。这一系列表达式包括:一个表达式对多个变量赋值,函数的参数传递,表的构造以及作为另一个函数的返回值。
function foo() return 'a','b' end -- returns no results
-- 一个表达式对多个变量赋值
x, y = foo() --> x = 'a', y = 'b'
x, y, z = foo() --> x = 'a', y = 'b', z = nil
x, y, z = 10, foo() --> x = 10, y = 'a', z = 'b'
x, y, z = foo(), 10 --> x = 'a', y = 10, z = nil -- foo()不是表达式的最后,所以只有第一个返回值被保存。
-- 函数的参数传递
print(foo()) --> a b
print(1, foo()) --> 1 a b
print(foo(), 1) --> a 1
-- 注意!这里有个特殊情况:
print(foo() .. "x") --> ax
print("x" .. foo()) --> xa -- 注意这里也只保留了第一个返回值。
-- 推测:".."右边只接收一个参数,foo()所提供的多余的参数被丢弃了。
-- 表的构造
a = {1, foo()} --> a[1] = 1, a[2] = 'a', a[3] = 'b'
a = {foo(), 1} --> a[1] = 'a', a[2] = 1
-- 作为另一个函数的返回值
function foo1()
return 5, foo()
end
function foo2()
return foo(), 5
end
print(foo1()) --> 5 a b
print(foo2()) --> a 5
3、其他情况下,lua只会保留函数的第一个返回值(在上面已经看到了一些情况)。
Q:如何强制限定函数只返回第一个返回值?
A:使用”()”将函数调用括起来,
function foo() return 'a','b' end
print((foo())) --> a
Q:如何定义以及使用可变参数函数?
A:使用...
,
-- 用lua自己实现C语言中的printf()。
function printf(fmt, ...)
--[[ select (index, ···)
如果index是一个数字,那么返回可变参数中第index个参数之后的所有参数值。
负的index代表从可变参数中最后一个参数开始计算index(-1是最后一个参数)。
否则,如果index只能是"#",select()会返回可变参数的总个数。]]
for i = 1, select('#', ...) do -- select()返回可变参数的总个数。
-- 这里select()返回了第i个参数之后所有的参数值,但多余的值被抛弃了。
arg[i] = select(i, ...)
end
-- string.format()格式化字符串;io.write()向标准输出写。
return io.write(string.format(fmt, table.unpack(arg)))
end
Q:Lua中的函数是一种”first-class values”,什么是”first-class values”?
A:与传统的变量(比如数字和字符串)拥有相同的权限。可以被存储在变量中,可以被存储在表中,可以作为参数传递,可以作为函数的返回值(C语言中的”函数指针”就有这些特性)。在Lua中函数被看作一种值,Lua中所说的函数名实际上是存储函数的变量的名字。
Q:Lua中的函数具有”lexical scoping”特性,什么是”lexical scoping”?
A:函数可以访问包裹它的函数的值,
function foo()
a=1
foo1() -- foo1()中可以访问a。
end
Q:什么是”Proper Tail Calls”?
A:”Proper Tail Calls”是一种特性,实现方式类似于C语言中”goto”调用。当一个函数的最后一个动作是调用另一个函数时,被调用的函数就具有”Proper Tail Calls”特性。
--[[ g()就是f()的"Proper Tail Calls"。
当在f()中调用完g()后,没有必要再返回到f()中,因为f()没有任何事要做了。
函数调用就是入栈出栈的过程,支持这种特性可以在递归函数的调用中节省大量的栈空间。]]
function f(x)
return g(x)
end
Q:”Proper Tail Calls”的实际应用?
A:一个解谜小游戏,
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开始游戏。
room1()
south
west
invalid move
east
congratulations!
Q:什么是”Closures”?
A:一个匿名函数,他能够访问包含他的”chunk”中的局部变量。”Closures”用到了”lexical scoping”以及”Proper Tail Calls”特性。
function newCounter ()
local i = 0
-- 只有返回匿名函数才会有这种特性,如果返回一个已定义的函数,不会有这种特性。
return function ()
i = i + 1
return i
end
end
c1 = newCounter() -- c1 is a "closure".
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter() -- c2 is another "closure".
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
Q:什么是”factory”?
A:创建”Closures”的函数。上面的newCounter()
就是一个”factory”。
Q:什么是”sandbox”?
A:利用”Closures”所创建的安全的lua运行环境。从Internet上获取的lua程序在读懂它的源码前,我们不清楚它的安全性,不清楚它是否在其内部有一些危险的实现(比如读取隐私文件,删除系统文件等)。”Closures”的优势就在于它可以允许我们访问包含匿名函数的”chunk”中的局部变量,所以我们可以利用这一特性对一些敏感的函数做一些限制,从而创建一个安全的”sandbox”。
do
local oldOpen = io.open
io.open = function (filename, mode)
--[[ access_OK()中可以列出允许访问的文件名,以及这些文件所允许的访问方式。
只有access_OK()返回"true"财允许调用原先的io.open()打开文件。]]
if access_OK(filename, mode) then
-- 可以访问包含匿名函数的"do-end"中的局部变量oldOpen。
return oldOpen(filename, mode)
else
return nil, "access denied"
end
end
end
-- [[这样做的强大之处在于,将原先的io.open()封装在内部,
外部程序调用的是我们创建的"sandbox"中安全的io.open(),
而外部程序想要访问原先的io.open(),也只能通过调用我们规定的io.open(),别无它法。]]
Q:如何将函数存储在table中?
A:
-- 使用table构造的形式。
Lib = {
foo = function (x,y) return x + y end,
goo = function (x,y) return x - y end
}
-- 使用赋值table中元素的形式。
Lib = {}
Lib.foo = function (x,y) return x + y end
Lib.goo = function (x,y) return x - y end
-- 使用创建函数的方式。
Lib = {}
function Lib.foo (x,y)
return x + y
end
function Lib.goo (x,y)
return x - y
end
Q:如何创建本地函数?
A:使用local
关键字。
local function f(arg1, arg2, ...)
dosomething
return ret1, ret2, ... or nothing
end
Q:如何定义一个本地的递归函数?
A:
-- 直接递归函数。
-- 方式1
local fact
fact = function (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
-- 方式2
local function fact (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
-- 间接递归函数。
local f, g -- 定义间接递归函数时,必须先声明本地变量。
function g ()
... f() ...
end
function f ()
... g() ...
end
附加:
1、lua中的函数如果只有一个参数,并且这个参数是字符串常量或者是一个表的构造,则函数调用时括号可以不写:
print "Hello World" <--> print("Hello World")
dofile 'a.lua' <--> dofile ('a.lua')
print [[a multi-line <--> print([[a multi-line
message]] message]])
f{x=10, y=20} <--> f({x=10, y=20})
type{} <--> type({})
建议写上比较好,又规范,又不容易混乱。
2、调用函数时,如果实参的数量多于形参的数量,则多余的实参会被忽略;而如果实参的数量少于形参的数量,剩余的形参值为nil。
function foo(a, b)
print(a, b)
end
foo(5, 6, 7) --> 5 6
foo(5) --> 5 nil
3、注意,当函数return一个表达式时,并不需要像C语言那样为了规范而加上一个括号,
function foo() return 'a','b' end
-- 这样些只能得到foo()的第一个返回值,这也许是你想要的,也许不是。
function foo1() return (foo()) end
4、通过table实现可选参数函数。
--[[ 对于参数很多的函数,有时很难记住参数的名字和参数的顺序以及哪些参数是可选的。
通过table让你在调用这类函数时可以随意指定参数的顺序,并且可以只传递需要设定的参数。]]
function create_window(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
_create_window(options.title,
options.x or 0, -- default value is 0
options.y or 0, -- default value is 0
options.width, options.height,
options.background or "white", -- default is "white"
options.border -- default is false (nil)
)
end
5、既然function被看作一种可以被变量存储的值,那么也就可以按照变量赋值的操作来创建以及使用函数:
foo = function (x) return 2*x end
print(foo(5)) --> 10
6、一些被误认为是”Proper Tail Calls”的情况:
-- 在调用g()之后,还需要丢弃g()的返回才能return。
function f()
g()
return
end
-- 在调用g()之后,还需要做加法。
function f()
return g() + 1
end
-- 在调用g()之后,还需要做"or"操作。
function f()
return x or g()
end
-- 在调用g()之后,还需要截取g()的第一个返回值。
function f()
return (g())
end
7、”Closures”的一个应用,将math.sin()转换为接收角度值,
do
local oldSin = math.sin -- 原先的math.sin()接收弧度值。
local k = math.pi/180 -- 弧度转角度系数。
math.sin = function (x) -- 新的math.sin()接收角度值。
return oldSin(x*k) -- 弧度转为角度调用原先的math.sin()。
end
end
8、函数存储在表中方便管理。比如可以将函数以及其所需数据都存储在一个table中,他们形成一个整体来管理。
9、以下定义本地的递归函数的方式是错误的,
local fact = function (n)
if n == 0 then return 1
else return n*fact(n-1) -- buggy
end
end
当Lua编译”n*fact(n-1)”时,”local fact”还并不存在。因此这样写的结果是使用了全局的”fact”,而非那个”local fact”。为了解决这个问题,需要”Q & A”中提到的正确的方式那样,先定义”local fact”。