• chapter6 深入了解函数


      Lua函数是具有特定词法域的第一类值,与其他传统类型的值(string and number)具有相同的权利。

    它可以保存在变量和table中,也可以把它当参数传递,也可以作为返回值。

      在Lua中有个容易混淆的概念,函数与所有其它值一样都是匿名的,即他们没有名称。

    当讨论一个函数名(print),实际上是讨论一个持有某函数的变量。

    这与其他变量持有各种值一个道理,可以用多种方式来操作这些变量:

    a  =  {p = print}
    a.p("Hello World")        -->Hello World
    print = math.sin          -->"print" 现在引用了sin函数
    a.p(print(1))             -->0.841470
    sin = a.p                 -->"sin" 现在引用了print函数
    sin(10,20)                -->10   20

    通常我们这样定义一个函数:

    function foo (x) return 2*x end

    这种写法仅仅是下面方式的一种简写:

    foo = function(x) return 2*x end

    实际上,函数定义就是创建了一个function类型的值,并把它赋值给一个变量。

    可以将上面的function(x) body end表达式叫做函数构造式,就像{}是一个table构造式一样。

    将这种函数构造式的结果叫作匿名函数。一些场合会用到匿名函数:

    table.sort可以接收不同的排序方式,升序、降序、数字顺序、字符排序......

    table.sort没有这些方式的函数实现,需要通过传入order function参数。

    order function接收两个l列表内元素为参数,当第一个元素需要排在第二个元素之前,返回真并有序返回这两个参数。比如有一个table:

    network = {
            {name = "grauna",    IP = "210.26.20.30"},
            {name = "arraial",   IP = "210.26.45.33"},
            {name = "lua",       IP = "210.45.34.56"},
            {name = "derain",    IP = "210.26.23.76"},
    }

    如果想以name字段按照反向的字符顺序进行排序,则:

    table.sort(network,function (a,b) return (a.name > b.name) end)
    --有点不好理解,a.name > b.name是ture 然后function是要排序还是不进行排序。
    --但是从测试结果去了解,是要让table.name按照d,c,b,a进行排序
    --索性就把(a.name > b.name )当成想要实现的结果,这样好理解一点

    把这样的sort函数称作高阶函数,然后用匿名函数来创建高阶函数需要的实参。

    另一个用高阶函数的例子,一个函数f在点x的导数(f(x+d)-f(x))/d ,其中d趋向无穷小:

    function derivative (f,delta)
        delta = delta  or  1e-4
        return function(x)
                   return (f(x +delta) -f(x))/delta
               end
        end

    对于特定函数f调用derivative(f)将(近似地)返回其导数:

    c = derivative(math.sin)
    print(math.cos(5.2),c(5.2))
    --> 0.46851667130038      0.46856084325086
    print(math.cos(10),c(10))
    --> -0.83907152907645    -0.83904432662041

    6.1 闭合函数

      把一个函数嵌入到另一个函数内,并且它具有访问闭合函数里的局部变量。

     有以下代码:

    names  = {"Peter", "Paul" , "Mary"}
    grades = {Mary=10,Paul=7, Peter=8}
    table.sort(name,function(n1,n2) return grades[n1] > grades [n2] end

    现在,用一个函数去实现这个功能:

    function sortbygrade (names,grades)
        table.sort(names,function (n1,n2) return grades[n1] > grades[n2] end
    end

    匿名函数function可以访问grades变量,grades变量是闭合函数sortbygrade的局部变量。

    对于匿名函数,grades既不是全局变量,也不是局部变量,我们称它为upvalue——上值。

    Why is this point so interesting? Because functions are first-class values,
    and therefore they can escape the original scope of their variables.

    这是原文,没有理解透彻。待以后再细细品尝。

    要对upvalue的深入了解,参考云风博客里的一篇译文:the_implementation_of_lua_50.pdf

     参考以下代码:的

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

     在这个代码中,匿名函数引用了一个非局部变量i去计数。

    当在调用匿名函数时,newCounter函数已经返回,i变量超出作用范围。

     对此,Lua用了一个叫做closure的概念去处理这种情况。

    closure是一个函数加上所有它需要访问的非局部变量。

    当再重新调用newCounter,它会创建一个新的局部变量i,从而得到一个新的closure。

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

    此时,c1和c2是两个不同的closure函数,各自有独立的局部变量i。

    从技术层面讲,Lua中只存在closure,不存在function。

    function本身仅仅是closure的原形。

    closure是一个很有用的工具,比如在高阶函数sort中作为参数传参。

    对创建其他函数的函数也很有用(比如沙盒函数)。

    这种机制使Lua程序可以融合那些在函数式编程世界中久经考验的编程技术。

    closure对于回调函数也很有用。

    一个典型的例子:当你调用传统的GUI 工具创建按钮时。每当用户按下按钮,就会触发一个

    回调函数。你希望的是不同的按钮按下时,只做一点轻微不同的操作。

    比如一个 十进制的计算器,需要10个相似的按钮,每个数字对应一个。

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

    该例子中,Button是一个创建按钮的函数,label是按钮标签,action是一个closur函数,每

    次有按钮按下就会触发。即使在digitButton函数返回,digit变量超出范围。action任然可以

    去访问这个digit变量。

     函数可以保存在一个变量里,使得它在很多不同环境使用起来很方便。

    像以下代码重定义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

    把原始的sin保存为一个私有变量,只有通过新版本的sin才可以访问到它。

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

    当运行一些不受信任的代码时会用到该功能。比如限制一个程序访问文件:

    do 
        local oldOpen = io.open
            local access_OK = function (filename ,mode)
            --check access authority
            end
        io.open = function(filename,mode)
                        if access_OK(filename,mode)
                            return oldOpen(filename,mode)
                        else
                            return nil,"access denied"
                        end
                end
    end

    这样,只能通过新授权的版本才能访问原来的不受限的open函数。

    对此Lua提供了一套元机制,可以根据特定的安全需要来创建一个安全的运行环境。

     6.2 非全局函数

      大多数的Lua库(io.read,math.sin)使用了这样的机制,把函数放在table字段里。

    在Lua中创建这样的函数,只需要将函数语法与table语法结合起来即可。

    Lib = {}
    Lib.foo = function (x,y) return x+y end
    Lib.goo = function (x,y) return x-y end
    print(Lib.foo(2,3),Lib.goo(2,3))    -->5    -1

    也可以这样写:

    Lib = {
        goo = function (x,y) return x+y end,
        foo = 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()
            <body>
    end
    
    local g = function()
        f()      --f在这里可见    
    end

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

    local  function f()
            <body>
    end

    当Lua在展开局部函数定义的语法糖时:并不是使用基本函数定义的语法。

    local function foo()   <body> end
    
    ---->展开后
    
    local foo;foo = function() <body> end

    在定义递归函数时,下面的代码是不能运行的:

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

    原因是,当Lua编译fact (n-1)时,local fact 还没有定义。此时该表达式将会去调用全局的fact,而不是local fact。

    解决办法:

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

     当函数运行时,fact已经有了确切的值,所以不会产生错误。

    当间接使用递归时,你必须明确地使用前向声明。

    local f , g
    function g()
        f()
    end
    
    function f()
        g()
    end

    注意,别把第二个函数定义为local function f,否则 g 中调用的 f 处于未定义状态。

    6.3 正确的尾调用

    Lua函数的另一个特性,Lua支持尾调用消除。

    当一个函数调用是另一个函数的最后一个动作,该调用就是一个尾调用。

    有点类似于goto语句。

    比如:

    function f(x) return g(x) end

    当g返回时,控制权直接返回到调用f的那个点上。

    尾调用消除这个特点,使得在进行 “尾调用” 时不消耗任何栈空间。

    所以一个程序可以有无数嵌套的“尾调用”

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

    该函数支持任意的数字,都不会出现栈溢出。

    想要用尾调用的好处,就必须要保证是尾调用。

    以下代码都不是尾调用。

    function f(x)  g(x)  end        --在调用g后,f还要丢弃g的返回值
    return g(x) + 1                 --g返回后,还要再加
    return x or g(x)                --g返回后,还要再或
    return (g(x))                   --g返回后,还要调整为一个返回值

    在Lua中,只有“return func()” 才是尾调用,Lua会在调用前对func和它的参数求值。

    比如下面的代码就是尾调用:

    return x[i].foo(x[j] + a*b, i+j)
  • 相关阅读:
    BZOJ3098 Hash Killer II 【概率】
    BZOJ4010 [HNOI2015]菜肴制作 【拓扑排序 + 贪心】
    洛谷P4364 [九省联考2018]IIIDX 【线段树】
    洛谷P4363 [九省联考2018]一双木棋chess 【状压dp】
    洛谷P2664 树上游戏 【点分治 + 差分】
    BZOJ1189 [HNOI2007]紧急疏散evacuate 【二分 + 网络流】
    BZOJ1068 [SCOI2007]压缩 【区间dp】
    BZOJ4033 [HAOI2015]树上染色 【树形dp】
    BZOJ4819 [Sdoi2017]新生舞会 【01分数规划 + 费用流】
    排序
  • 原文地址:https://www.cnblogs.com/daiker/p/5798665.html
Copyright © 2020-2023  润新知