Lua 函数function

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)
end

local x = 1
local y = 2

print("before swap")
print(x,y)

print("swap")
swap(x,y)

print("after swap")
print(x,y)

output:

before swap:
1
2

swap:
2
1

after swap
1
2

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

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

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

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

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

local function func1(a,b)
    print(a,b)
end

local function func2(a,b,c,d)
    print(a,b,c,d)
end

local x =1
local y = 2
local z = 3

print("实参个数 > 形参个数")
func1(x,y,z)

print("实参个数 < 形参个数")
func2(x,y,z)

output:

实参个数 > 形参个数
1
2

实参个数 < 形参个数
1
2
3
nil
2.2 按引用传递

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

local function ChangeTable(tab)
   tab.x = tab.x + 10
   tab.y = tab.y + 20
end

local a = {x = 10,y = 20}

print("before changeTab")
print(a)

ChangeTable(a)
print("after changeTab")
print(a)

output:

before changeTab
10
20

after changeTab
20
40
2.3 变长参数

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

local function func(...)
   local tempTab = {...}
   local str = table.contat(tempTab , " ")
   print(str)
end

func(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)

output
3
5

【返回多值定义】
只需要在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,mi
end

print(Max({8,10,23,12,5}))

output:

23
3

【根据实际调用来返回对少个值】
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 = nil
x,y = foo1()        --> x = "a" , y = nil
x,y,z = foo2()      --> x = "a" , y = "b" , z = nil

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

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

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

print(foo0())               -->
print(foo1())               -->a
print(foo2())               -->a b
print(foo2(),1)             -->a 1
print(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()
    end
end

print(foo(1))       --> a
print(foo(2))       --> a b
print(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 world
print = math.sin       -->print现在引用了正弦函数
a.p(print(1))          -->0.841470
sin = a.p              -->sin现在引用了print函数
sin(10 , 20)           -->10 20

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

function foo(x) 
    return 2*x
end

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

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     
end

local c1 = newCounter()
print(c1())            -->output : 1
print(c1())            -->output : 2

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

local c2 = newCounter()
print(c2())       --> 1
print(c1())       --> 3
print(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 end
Lib.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 end
function Lib.goo = function (x , y) return x - y end

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

局部函数语法:

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

Lua将其展开为:

local foo
foo = 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)是否包含表达式,由于表达式的执行是在函数调用之前完成的,因此不会影响该函数成为尾调用函数。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值