深入函数


在Lua中,你可以像使用number和string一样使用function。可以将function存储到变量中,存储到table中,可以当作函数参数传递,可以作为函数的返回值。

在Lua中,function跟其他值一样,也是匿名的。function被作为一个值存储在变量中,下面这个例子帮助理解:

a = {p = print}
a.p("Hello World")  --> Hello World
print = math.sin    -- 'print' now refers to the sin function
a.p(print(1))       --> 0.841470
sin = a.p           -- 'sin' now refers to the print function
sin(10, 20)         --> 10 20
lua常见的函数表达式编写:

function foo (x) return 2*x end
--实际上为:
foo = function (x) return 2*x end  

 --这只是一种简化的书写形式,因此一句函数就是一个表达式,这条语句创建一个一个为函数的值,并将这个值赋给一个变量。我们可以将function (x) body end 看成是function的构造函数,就像{}是table的构造函数一样。


table库有一个函数table.sort ,接受一个table,然后对table中的元素排序。排序可能有各种规则,升序,降序,数字或字母表顺序,根据table中的key排序等。该函数没有提供各种各样所有的排序选项,而是提供了一个单独的选项,即函数的第二个参数,order函数,order函数,接受两个元素,返回一个布尔值,来指示是否第一个元素排在第二个元素之前。看下面的例子:


network = {
           {name = "grauna", IP = "210.26.30.34"},
           {name = "arraial", IP = "210.26.30.23"},
           {name = "lua", IP = "210.26.23.12"},
           {name = "derain", IP = "210.26.23.20"},
          }
table.sort(network, function (a,b) return (a.name > b.name) end) -- 按照反向排序 

若想打印table列表,从网上找到的,打印出来的结构非常好。

从上可以看到匿名函数的便捷性。

再来个例子巩固一下吧:

f = function (a,b) return a + b  end
function add(a,b,f)
	return f(a,b)
end
print(add(3,4,f))  -- 7

闭合函数


先看一个示例,有两个table,一个table中是student names,另一个table里面有每个student的grade;现在要根据student的grade来对前一个table进行有高到底排序,如下咧:

names = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Paul = 7, Peter = 8}
table.sort(names, function (n1, n2)
<span style="white-space:pre">	</span>return grades[n1] > grades[n2] -- compare the grades
end)

现在假设单独创建一个函数来做这份任务,

function sortbygrade (names, grades)
	table.sort(names, function (n1, n2)
		return grades[n1] > grades[n2] -- 比较年级
	end)
end

上例有趣之处在于,sort中的匿名函数可以访问参数中的grades,grades是sortbygrade的局部变量。在该匿名函数中,grades既不是局部变量,也不是全局变量,将其称为‘非局部变量’。

function newcouner ()
	local i = 0
	return function() 
	    i = i +1
	    return i
        end
end

c = newcouner()
c2 = newcouner()

print(c()) --1
print(c2()) --1
print(c()) --2
print(c2()) --2
print(c2()) --3

在上面的代码中,匿名函数引用了非局部变量来记数。但是,调用这个匿名函数的时候,i已经不再在有效作用域了,因为创建的函数(newCounter )已经返回了。但是Lua可以使用闭合函数正确处理这种情况。简单地说,闭合函数就是一个函数加上要访问一个非局部变量所需要的所有元素。如果再调用一个newCounter ,它会重新创建一个新的非局部变量i, 得到一个新的闭合函数。如上面示例,c1和c2是两个基于用一个函数的闭合函数,二者有两个独立的非局部变量的实例。


闭合函数在很多场合都很好用。例如,在higer-order函数(如 sort  )中,可以作为参数;在newCounter这样的在自己函数体中创建其他的函数;作为回调函数。一个典型的例子就是,在传统的GUI工具箱中,创建按钮,每个按钮需要一个回调函数来响应按键动作。例如一个计算器,需要10个类似的按钮,每个数字一个。可以用下面的function来创建:

function digitButton (digit)
    return Button{ label = tostring(digit),
                   action = function ()
                               add_to_display(digit)
                            end
                 }
end

在上面的示例中,我们假设digitButton是一个工具箱函数,用来创建一个按钮,lable是按钮的label, action是回调函数,响应按键操作。这个回调函数可以在digitButton完成工作以后很久,局部变量digit超出作用域后再调用,但是它仍然能够访问变量digit。


由于函数是存储在普通的变量中,Lua可以方便的重新定义一些函数,甚至Lua预定义的函数。下面看一个示例,重新定义sin函数,将参数由原来的弧度,改成度数。新的函数一定要转换一下参数的值,然后调用原来的sin函数来实现功能。

oldSin = math.sin
math.sin = function (x)
return oldSin(x*math.pi/180)
end

--建议用下面这种
do
local oldSin = math.sin
local k = math.pi/180
math.sin = function (x)
               return oldSin(x*k)
           end
end

推荐使用第二种实现,我们将原来的函数保存在一个局部变量中中,访问它的唯一途径就是通过新版本的函数。通过这种技巧,可以构建沙箱安全环境。这种安全环境在你需要运行一些不被信任的代码(例如从internet上收到的代码)时是很有必要的。例如,为了严格限制程序可以访问的文件,可以重新定义函数io.open:

do
    local oldOpen = io.open
    local access_OK = function (filename, mode)
                          --here to add some code to restrict access
    					  <check access>
                      end
    io.open = function (filename, mode)
        if access_OK(filename, mode) then
            return oldOpen(filename, mode)
        else
            return nil, "access denied"
        end
    end
end
如下代码,重定义open函数后,程序没办法访问无限制版本的open函数,只能使用这个限制版本。通过这种方法,Lua可以简便灵活地构建安全的沙箱环境。


如下代码,重定义open函数后,程序没办法访问无限制版本的open函数,只能使用这个限制版本。通过这种方法,Lua可以简便灵活地构建安全的沙箱环境。

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
当我们将函数存储到一个局部变量中,我们就得到一个局部函数,只在给定的作用域中有效。这个特性在包中经常用到,可以在一个包中定义局部函数,这些函数只在该包中可见,包中的其他函数可以调用这些局部函数:
local f = function (<params>)
              <body>
          end
local g = function (<params>)
              <some code>
              f() -- 'f' is visible here
              <some code>
           end
递归 函数中有一点微妙,看下面两份代码。

local fact = function (n)
    if n == 0 then return 1
    else return n*fact(n-1) -- buggy
    end
end


--下面这个可以工作
local fact
fact = function (n)
    if n == 0 then return 1
    else return n*fact(n-1)
    end
end

再看下面这个局部函数定义展开后的形式:
local function foo (<params>) <body> end

--expands to

local foo
foo = function (<params>) <body> end
因此,上面的递归函数也可以写成,注意3中实现方式的不同。
local function fact (n)
    if n == 0 then return 1
    else return n*fact(n-1)
    end
end
但是,上面这个技巧在非直接递归函数中就不好使了啊。看下面的示例:

local f, g -- 'forward' declarations

function g ()
    <some code>
	f() 
	<some code>
end

--local f 如果这句放在这,那么上面的g()是引用不到正确的f函数的
function f ()
    <some code> 
	g() 
	<some code>
end

--调用一下,要把上面的代码补全哦
g()

不信,试试把local f这句放到fucntion g ()的定义后面看有什么后果。


3.强大的尾调用

程序员都知道,函数的调用会产生调用堆栈。但是在Lua中,当然也有调用堆栈啦。但是尾调用在Lua中就不同于其他编程语言啦。我们先通过几行代码来看下什么是尾调用tailor call


function f (x) return g(x) end

function foo (n)
    if n > 0 then 
        return foo(n - 1) 
    end
end

--下面这几个都不是
function f (x) 
    g(x)            --after calling g, f still has to discard occasional results from g before returning
end  
function f (x)
    return g(x) + 1 -- must do the addition
end

function f (x)
    return x or g(x) -- must adjust to 1 result
end

function f (x)
    return (g(x)) -- must adjust to 1 result
end
在Lua中,只用格式为return func(args)的调用才是尾调用。即使func和args都是复杂的表达式也没关系,因为Lua在调用之前算得到它们的值。所以下面这个也是尾调用
return x[i].foo(x[j] + a*b, i + j)
我们再来针对下面这行代码讲讲Lua中对尾调用处理的强大。
function f (x) return g(x) end

Lua是怎么处理尾调用的呢。像C语言的话,上面代码中,f对g调用后,g执行完毕后,会返回到g的被调用处。但是在Lua中,这是一个尾调用,g执行完毕之后,会直接返回到f的被调用处。这样的话,可以节省很多堆栈空间。因此像下面这个函数,就不需担心n太大的话会有溢出。


在Lua中,对尾调用的一个很好的应用是状态机。可以用一个函数表示一个状态,改变状态就是跳转到一个指定的函数。下面我们用一个简单的迷宫程序示例:迷宫有几个房间(我们这里是4个),每个房间有4扇门,通向东,南,西,北。每一步,玩家指定一个移动的方向,如果这个方向有门,那么就进入对于的房间;否则,程序给一个警告;目标是从开始的房间走到目标房间。

这个程序是一个典型的状态机,状态就是当前的房间,每个房间写一个函数。用尾调用来从一个房间移动到另一个房间。如果不用尾调用的话,每一次移动都要将堆栈升级一个level,一定数量的移动后,可能就会导致程序溢出了。使用尾调用的话,就不需要担心这个问题了。废话少说,下面是代码,已经验证过了,比较简单。

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

--写完上面的四个room,调用一下就可以了。
room1()

文章来自lua程序设计第二版
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值