Lua function 函数

时间:2024-03-26 15:35:44

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时,可以省略圆括号)。

Lua function 函数

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.