Lua学习笔记06:深入函数(二)

一、非全局变量:Lua的函数不仅可以存储在全局的变量中,还可以存储在局部变量或者table的字段中(大部分的Lua库都采用了此种机制, 例如io.read,math.sin).
如需创建这种函数,只需将常规的函数语法和table语法结合起来即可
方法一:
Lib = {}
Lib.foo = function (x, y) return x+y end 
Lib.goo = function (x,y) return x- y end
方法二:
Lib ={foo = function (x, y) return x + y end,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
这三种方法都可以将方法存进table中
将一个函数存储在一个局部变量中,即可得到一个局部函数(local function)。也就是说,该函数只能在特定的作用域使用。
1、对于程序包而言,这种函数定义比较有用,因为Lua是将每个函数作为程序块来处理的,所以在一个程序块中声明的函数就是局部函数,这些函数只在该程序块中可见。该函数的作用域为其程序包中
例:

local f1= function(<参数>)
<函数体>
end

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

local g1= function(<参数>)
  <函数体>
  f1(实参) --f1在这里是可用的
  <函数代码>
end
2、在创建局部函数的时候,要明确其所在的作用域,看例子:
local fun = function(x)
          if 0 == n then
              return 1
          else
              return n*fun(n-1) --error,在调用时,编译器会直接先调用fun的全局函数。
          end
end

上面的fun是个局部函数,在调用fun(n - 1)时未定义,所以这里是直接调用全局的fun函数,而不是上面我们定义的局部函数fun.。解决这个问题的办法是,先定义一个**局部变量**,再定义**函数本身**。

local fun --这里先定义个局部变量
fun = function(x) --然后将函数赋值给局部变量,使其成为一个局部函数
     if 0 == n then
          return 1
     else
          return n*fun(n - 1)
     end
end
-------------------------------------------------------------------------
为什么上面的代码就不出错,因为对于局部函数的定义:
local function fun(<参数>) 
<函数体>
end

Lua在运行时将它展开为:
local fun
fun = function(<参数>)
<函数体>
end

因此,针对上面的出错的函数,可以这么写:
local function fun(n) --这里直接创建了局部函数
     if n == 0 then
         return 1
     else
         return n*fun(n-1)
      end
end
该方法可以保证创建的递归函数有效,但是这种方法对间接递归函数无效
2、对于间接递归而言必须使用一个明确的前向声明:
local  f, g  --前向声明
function g ()
   <代码块>
   f()     
   <代码块>
 end
 function f ()
  <代码块>
   g()    
    <代码块>
 end
 注意:别把第二个函数定义写为“local function f”.如果那样的话,Lua会创建一个全新的局部变量f,而将原来声明的f置于未定义的状态

二、正确的函数尾调用:Lua中的“尾调用”就是一种类似于goto的函数调用,当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。
例:function f (x) return g(x) end
也就是说,当f调用完g之后就再无其他事情可做了。因此在这种情况下,程序就不需要返回那个“尾调用”所在的函数了。所以在“尾调用”之后,程序也不需要保存任何关于该函数的栈(stack)信息了。当g返回时,执行控制权可以直接返回到调用f的那个点上。有一些语言实现(例如Lua解释器)可以得益于这个特点,使得在进行“尾调用”时不耗费任何栈空间。将这种实现称为支持“尾调用消除”。
由于“尾调用”不会耗费栈空间,所以一个程序可以拥有无数嵌套的“尾调用”。
例如,在调用以下函数时,传入任何数字作为参数都不会造成栈溢出:
function foo (n)
    if n > 0 then return foo(n-1) end
end
1、如果想要受益于尾调用消除的话,必须要确定当前的调用是一条尾调用;判断当前的调用是一条“尾调用”的准则:一个函数在调用完另一个函数之后,是否就无其他事情需要做了。
例如,下面的代码就不是一条“尾调用”:
function f (x) g(x) end
这个示例的问题在于,当调用完g后,f并不能立即返回,它还需要丢弃g返回的临时结果。类似的,以下所有调用也都不符合上述准则:
return g(x) + 1        -- 必须做一次加法
return x or g(x)    -- 必须调整为一个返回值
return (g(x))        -- 必须调整为一个返回值
2、在Lua中,只有“return 函数名(参数)”这样的调用形式才算是一条“尾调用”。Lua会在调用前对函数及其参数求值,所以它们可以是任意复杂的表达式。举例来说,下面的调用就是一条“尾调用”:
return x[i].foo(x[j] + a*b , i + j)
3、“尾调用”类似一条goto语句。在Lua中“尾调用”的应用就是编写“状态机(state machine)”。这种程序通常以一个函数来表示一个状态,改变状态就是goto(或调用)到另一个特定的函数。

举一个简单的迷宫游戏的例子来说明这个问题。例如,一个迷宫有几间房间,每间房间中最多有东南西北4扇门。用户在每一步移动中都需要输入一个移动的方向。如果在某个方向上有门,那么用户可以进入相应的房间;不然,程序就打印一条警告。游戏目标就是让用户从最初的房间走到最终的房间。
这个游戏就是一种典型的状态机,其中当前房间就是一个状态。可以将迷宫中的每间房间实现为一个函数,并使用“尾调用”来实现从一件房间移动到另一间房间。在以下代码中,实现一个具有4间房间的迷宫:

function room1 ()
    local move = io.read()
    if move == "sourth" 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 == "sourth" 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

若没有“尾调用消除”的话,每次用户的移动都会创建一个新的栈层(stack level),移动若干步之后就有可能会导致栈溢出。而“尾调用消除”则对用户行动的次数没有限制。这是因为每次移动实际上都只是完成一条goto语句到另一个函数,而非传统的函数调用。
对于这个简单的游戏而言,或许会觉得将程序设计为数据驱动的会更好玩一点,其中将房间和移动记录在一些table中。不过,如果游戏中的每间房间都有各自特殊的情况的话,采用这种状态机的设计会更为合适。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值