《lua程序设计》读书笔记 第六章:深入函数

在Lua中,函数是一种“第一类值”,它们具有特定的词法域。“词法域”是什么意思呢?这是指一个函数可以嵌套在另一个函数中,内部的函数可以访问外部函数中的变量。
在Lua中有一个容易混淆的概念是,函数与所有其他值一样是匿名的,即它们都没有名称。当讨论一个函数名时,实际上是在讨论一个持有某函数的变量。函数定义实际上是一个表达式:
function foo(x) return 2*x end其实际是foo = function(x) return 2*x end, 其实其是一条赋值语句。
可以将表达式“function(x) < body > end”视为一个函数的构造式,将构造式的结果称为一个匿名函数。
一个函数可以作为另一个函数的参数,接受另一个函数作为实参的函数,称为高阶函数。

    network = {{name = "grauna", IP="210.26.30.34"}
               {name = "lua", IP = "210.26.23.12"}}
    table.sort(network, function(a, b) return (a.name > b.name) end)  -->传入一个匿名函数为参数

6.1 closure(闭包函数)

若将一个函数写在另一个函数内,那么这个位于内部的函数可以访问外部函数中的局部变量,这项特征被称为“词法域”。为什么在Lua中允许这种访问呢?原因在于函数是“第一类值”。看如下代码:

function newCounter()
    local i = 0
    return function()
        i = i + 1
        return i
    end
end
local c1= newCounter()
c1()    -->1
c1()    -->2

简单来讲,一个closure就是一个函数加上这个函数访问的所有“非局部变量”。如果再次调用newCounter,那么它会创建一个新的closure。

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

从技术上讲,Lua中只有closure而没有函数,函数只是一种特殊的closure。
closure还有一些常用的用法,比如重定义一个预设的函数:

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

可以使用同样的技术创建一个安全的运行环境,即所谓的“沙盒”。

do
    local oldOpen = io.open
    local access_OK = function(filename, mode)
    < 检查一些访问权限 >
    end
    io.open = function(filename, mode)
        if access_Ok(filename, mode) then
            return oldOpen(filename, mode)
        else
            return nil, "access denied"
        end
    end
end

6.2 非全局的函数

在Lua中,函数通常存储在一个table中,而不是作为全局变量使用:

    Lib1 = {}
    Lib1.foo = function(x, y) return x + y end
    Lib2 = {foo = function(x,y) return x + y end}

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

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

对于这种局部函数的定义,Lua还支持一种特殊的“语法糖”

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

另外在定义递归的局部函数有一点要特别注意。看以下代码:

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

这样定义的错误的,关键在于其先计算右边的表达式,然后再定义左边变量,而在调用fact(n-1)时,fact变量尚未定义完毕,因此其实际会调用全局的fact,而非其本身。
正确的写法如下,应先定义局部变量fact:

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

而对于local function foo<参数> <函数体> end的定义方式,Lua将其展开为

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

所以使用这种方式定义递归函数不会有错误。
当然,这个技巧对于间接递归的函数是无效的,你必须使用一个明确的前置声明。

    local f,g
    function g()
        f()
    end
    function f()          -->注意不要用local function,否则其会创建一个f覆盖前面的变量
        g()
    end

6.3正确的尾调用

Lua支持“尾调用消除”。所谓“尾调用”就是一种类似于goto的函数调用。当一个函数调用时另一个函数的最后一个动作时,该调用才算是一条“尾调用”。

function f(x) return g(x) end

在上述调用中,当f调用完g后便无事可做了,因此程序不在需要保留函数f的堆栈信息,所以在进行尾调用时不耗费任何栈空间,这种实现便是“尾调用消除”。由于“尾调用”不会消耗栈空间,所以一个程序可以拥有无数嵌套的“尾调用”:

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

在Lua中“尾调用”的一大应用就是编写“状态机”。这种程序以一个函数来表示一个状态。

你可能感兴趣的:(《lua程序设计》读书笔记 第六章:深入函数)