Lua 函数function

来源:互联网 发布:js判断ie版本是否大于8 编辑:程序博客网 时间:2024/04/30 14:51

Lua 函数function

note 目录

  • lua函数定义,声明,调用
  • lua函数参数
  • lua函数返回值
  • 深入lua函数

1:Lua 函数定义和声明

【定义】
在lua中,函数是一种对语句和表达式进行封装抽象的主要机制。函数既可以完成默写特定的任务,也可以只做一些计算并返回结果。

【声明】

function func(arg1,arg2)    ...    函数体    ...    return(可选) end

【调用】
函数的调用与C语言基本上一样的。唯一有差别的是如果函数参数只有一个参数,并且这个参数是字符串常量或是table的构造器,那么圆括号就可以省略圆括号。

example:print "hello world"          <--->       print("hello wrold")func {x = 10 , y = 20 }      <--->       func({x = 20 , y = 30})

2:Lua 函数参数

  • 按值传递
  • 按引用传递
  • 变长参数
  • 具名参数
2.1 按值传递

2.1.1:形参和实参个数一致

lua中的按值传递和C语言的基本是一样的,实参传递给形参,实参和形参就没有关系了,形参的改变不会影响到实参,典型的就是swap函数。

local function swap(a,b)   local temp = a   a = b   b = temp   print(a,b)endlocal x = 1local y = 2print("before swap")print(x,y)print("swap")swap(x,y)print("after swap")print(x,y)output:before swap:12swap:21after swap12

2.1.2:实参和形参个数不一致,lua会自动调整实参个数,调整规则分为2种情况。

【1】实参个数 > 形参个数

从左向右,多余的实参会被忽略。

【2】实参个数 < 形参个数

从左向右,没有被实参初始化的形参会被赋值为nil

local function func1(a,b)    print(a,b)endlocal function func2(a,b,c,d)    print(a,b,c,d)endlocal x =1local y = 2local z = 3print("实参个数 > 形参个数")func1(x,y,z)print("实参个数 < 形参个数")func2(x,y,z)output:实参个数 > 形参个数12实参个数 < 形参个数123nil
2.2 按引用传递

当函数参数是table类型的时候,传递进来的是实参的引用,此时,函数内部改变了table里面的值,会该改变调用者传递进来的实际参数。

local function ChangeTable(tab)   tab.x = tab.x + 10   tab.y = tab.y + 20endlocal a = {x = 10,y = 20}print("before changeTab")print(a)ChangeTable(a)print("after changeTab")print(a)output:before changeTab1020after changeTab2040
2.3 变长参数

lua中可以接受不定长度的参数。用...标识,表示该函数可以接受不同长度的参数。

local function func(...)   local tempTab = {...}   local str = table.contat(tempTab , " ")   print(str)endfunc(1,2,3)func(1,2,3,4,5)output:1 2 3 1 2 3 4 5
2.4 具名参数

传递参数时,使得实参具有名称的实际参数。

【引入具名参数概念的目的】
Lua中参数传递机制是通过位置来传递的。也就是说早调用一个函数时,实参通过他在参数中位置来与形参匹配起来的。第一个实参值与第一个形参值匹配,依次类推。这种方式缺点就是在外界调用的时候不能自定义实参的位置,一定要按照形参的意义来。通常会忘记第一参数和第二个参数…第n个参数分别代表什么。因此会希望这个函数能接受具有名称的实参。例如:

--无效的演示代码rename(old = "temp.lua" , new = "temp1.lua")

【可以用到具名参数的2个条件】

(1)lua中并不能支持这种语法,但可以通过细微的改变来获得相同的效果。主要是所有的实参组织到一个table中,并将这个table作为唯一的实参传给函数。

(2)另外,还需要用到一种Lua中特殊函数调用语法,就是当实参只有一个table构造式时,函数调用中的圆括号是可有可无的。

rename{old = "temp.lua" , new = "temp1.lua"}

【具名参数的函数定义】
可以用具名参数的函数在定义时。形参只能定义一个参数,并且这个参数是table类型。

function rename(arg)    return os.rename(arg.old , arg.new)end

3:Lua 函数的返回值

lua具有一项非常与众不同的特性,允许函数返回多个结果。Lua中的一些库函数就是如此的。

example:
使用库函数 string.find,在源字符串中查找目标字符串,若查找成功,则返回目标字符串在源字符串中的起始位置和结束位置的下标。

local s, e = string.find("hello world", "llo")print(s, e)output35

【返回多值定义】
只需要在return关键字后列出所有的返回值即可。

example:查找一个数组中的最大元素,并返回该元素的位置

function Max(a)   local mi = 1         --最大值的索引   local m = a[mi]   for i , value in ipairs(a) do      if val > m then          mi = i          m = value      end     end   return m,miendprint(Max({8,10,23,12,5}))output:233

【根据实际调用来返回对少个值】
Lua会调整一个函数的返回值数量来适用不同的情况。

(1)若将函数调用作为一条单独语句时,Lua会丢弃函数的所有返回值。
(2)若将函数作为表达式的一部分来调用时,Lua只保留函数的第一返回值。
(3)只有当一个函数调用是一系列表达式中的最后一个元素(或仅有一个元素)时,才能获得它的所有返回值。这里的所谓的“一系列”在Lua中表现为4种情:
接下来分别介绍这4种情况,首先,定义这些函数:

function foo0()end                        --无返回结果function foo1() return "a" end            --返回1个结果function foo2() return "a" ,"b" end       --返回2个结果

a:在多重赋值中(分为3种情况)

若一个函数调用是最后的(或仅有的)一个表达式,那么Lua会保留其尽可能多的返回值,用于匹配赋值变量。

x,y = foo2()        --> x = "a" , y = "b"x = foo2()          --> x = "a" , "b"被丢弃x,y,z = 10,foo2()   --> x = 10, y = "a" , z = "b"

若一个函数没有返回值或者没有足够多的返回值,那么Lua会用nil来补充缺失的值

x,y = foo0()        --> x = nil , y = nilx,y = foo1()        --> x = "a" , y = nilx,y,z = foo2()      --> x = "a" , y = "b" , z = nil

若一个函数调用不是一系列表达式的最后一个元素,那么将只会产生一个值。

x,y = foo2(),20        --> x = "a" , y = 20x,y = foo0() , 20 ,30  --> x = nil , y = 20 , 30被丢弃

b:当一个函数调用作为另外一个函数调用的最后一个(或仅有的)实参时,第一个参数的所有返回值都将作为实参传入第二个函数。

print(foo0())               -->print(foo1())               -->aprint(foo2())               -->a bprint(foo2(),1)             -->a 1print(foo2() .. "x")        -->ax

当foo2出现在一个表达式中,Lua会将返回值调整为1,因此在上面的最后一行中,只有”a”参与了字符串的连接的操作。

c:table构造式可以完整地接收一个函数调用的所有的结果,即不会有任何数量方面的调整:

t = { foo0() }           --> t = {}  (一个空的table)t = { foo1() }           --> t = {"a"}t = { foo2() }           --> t = {"a" , "b"}

不过,这种table的行为只有当一个函数调用作为最后一个元素是才会发生,而在其他位置上的函数调用总是产生一个结果值。

t = {foo0(),foo2(),4}       --> t[1] = nil , t[2] = "a" , t[3] = 4

d:最后一种情况是return语句,如return f()这样的语句将返回f的所有值:

function foo(i)    if i == 0 then foo0()    elseif i == 1 then return foo1()    elseif i == 2 then retrun foo2()    endendprint(foo(1))       --> aprint(foo(2))       --> a bprint(foo(0))       --> 无返回值print(foo(3))       --> 无返回值

【unpack函数】

unpack(tab)

它接收一个数组作为参数,并从下标1开始返回该数组的所有的元素:

print(unpack{10,20,30}) --> 10,20,30
a,b = unpack{10,20,30} --> a = 10 , b =20 30被丢弃

4:深入lua函数

在Lua中,函数与所有的其他值一样的都是匿名的,即他们都没有名称,操作的都是持有该函数的变量。

a = {p = print}a.p("hello world")     -->hello worldprint = math.sin       -->print现在引用了正弦函数a.p(print(1))          -->0.841470sin = a.p              -->sin现在引用了print函数sin(10 , 20)           -->10 20

最常见的函数编写方式:直接给出函数名

function foo(x)     return 2*xend

另外一种是在声明函数时赋值给一个变量

local foo = funtion(x) return 2*x end

因此,一个函数定义实际就是一条语句(一条赋值语句),这条语句创建了一种类型为“函数”的值,并将这个值赋予一个变量。
可以将表达式function(x)<body>end视为一种函数的构造式,就像table的构造式{}一样。将这种函数构造式的结果称为一个“匿名函数”。

4.1 closure(闭合函数)

将一个函数写在另一个函数之内,那么位于内部的函数便可以访问外部函数中的局部变量,这项特征称为“词法域”。

function newCounter()    local i = 0    return function()     --匿名函数             i = i + 1             return i        end     endlocal c1 = newCounter()print(c1())            -->output : 1print(c1())            -->output : 2

在上面的示例中,我们将newCounter()函数称为闭包函数。其函数体内的局部变量i被称为”非局部变量”,和普通局部变量不同的是该变量被newCounter函数体内的匿名函数访问并操作。再有就是在函数newCounter返回后,其值仍然被保留并可用于下一次计算。再看一下下面的调用方式。

local c2 = newCounter()print(c2())       --> 1print(c1())       --> 3print(c2())       --> 2

由此可以推出,Lua每次在给新的闭包变量赋值时,都会让不同的闭包变量拥有独立的”非局部变量”。

4.2 non-global function(非全局的函数)

函数不仅可以存储在全局变量中,还可以存储在table字段中和局部变量中。
大部分Lua库也采用了这种机制(io.read , math.sin)。
若要在lua中创建这种函数,只需要将常规的函数与table结合起来使用即可:

Lib = {}Lib.foo = function (x , y) return x + y endLib.goo = function (x , y) return x - y end

也可以使用table的构造式

Lib = {foo = = function (x , y) return x + y end,goo = function (x , y) return x - y end}

除此之外,lua还提供了另外一种语法来定义这类函数

Lib = {}function Lib.foo = function (x , y) return x + y endfunction Lib.goo = function (x , y) return x - y end

只要将一个函数存储到一个局部变量中,即得到了一个“局部函数(local function)”,也就是该函数只能在某个特定的作用域中使用。不加local的函数就会添加到全局表_G里面。

局部函数语法:

local function foo(<参数>) <函数体> end

Lua将其展开为:

local foofoo = function (<参数>) <函数体> end 
4.3 proper tail call(正确的尾调用)

Lua中的函数有一个有趣的特征,就是Lua支持“尾调用消除(tail-call elimination)”。
所谓“尾调用(tail call)“就是一种类似与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 ()”形式才是标准的尾调用,至于参数中(args)是否包含表达式,由于表达式的执行是在函数调用之前完成的,因此不会影响该函数成为尾调用函数。