• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    Lua教程(四):函数详解

    一、函数:

        在Lua中函数的调用方式和C语言基本相同,如:print("Hello World")和a = add(x, y)。唯一的差别是,如果函数只有一个参数,并且该参数的类型为字符串常量或table的构造器,那么圆括号可以省略,如print "Hello World"和f {x = 20, y = 20}。
        Lua为面对对象式的调用也提供了一种特殊的语法--冒号操作符。表达式o.foo(o,x)的另一种写法是o:foo(x)。冒号操作符使调用o.foo时将o隐含的作为函数的第一个参数。
        Lua中函数的声明方式如下:
     

    复制代码 代码如下:

        function add(a)
            local sum = 0
            for i, v in ipairs(a) do
                sum = sum + v
            end
            return sum
        end
     

        在以上声明中,包含了函数名(add),参数列表(a),以及函数体。需要说明的是,Lua中实参和形参的数量可以不一致,一旦出现这种情况,Lua的处理规则等同于多重赋值,即实参多于形参,多出的部分被忽略,如果相反,没有被初始化的形参的缺省值为nil。

        1. 多重返回值:

        Lua支持返回多个结果值。如:

    复制代码 代码如下:

    s,e = string.find("Hello Lua users","Lua")
    print("The begin index is " .. s .. ", the end index is " .. e .. ".");
    -- The begin index is 7, the end index is 9.

        以上的代码示例只是演示了如何获取Lua函数的多个返回值,下面的示例将给出如何声明返回多个值的Lua函数。如:
    [code]
    function maximum(a)
        local mi = 1
        local m = a[mi]
        for i, val in ipairs(a) do
            if val > m then
                mi,m = i,val
            end
        end
        return m,mi
    end
    print(maximum{8,10,23,12,5})
    --23   3

    Lua会调整一个函数的返回值数量以适应不同的调用情况。若将函数调用作为一条单独语句时,Lua会丢弃函数的所有返回值。若将函数作为表达式的一部分来调用时,Lua只保留函数的第一个返回值。只有当一个函数调用是一系列表达式中的最后一个元素时,才能获得所有返回值。这里先给出三个样例函数,如:

    复制代码 代码如下:

        function foo0() end
        function foo1() return "a" end
        function foo2() return "a","b" end

     最后一个需要介绍的是Lua中unpack函数,该函数将接收数组作为参数,并从下标1开始返回该数组的所有元素。如:
     

    复制代码 代码如下:

        /> lua
        > print(unpack{10,20,30})
        10  20  30
        > a,b = unpack{10,20,30}
        > print(a,b)
        10  20
        > string.find(unpack{"hello","ll"})  --等同于string.find("hello","ll")

        在Lua中unpack函数是用C语言实现的。为了便于理解,下面给出在Lua中通过递归实现一样的效果,如:
    复制代码 代码如下:

    function unpack(t,i)
        i = i or 1
         if t[i] then
             return t[i], unpack(t,i + 1)
         end
    end

    2. 变长参数:
        Lua中的函数可以接受不同数量的实参,其声明和使用方式如下:
     

    复制代码 代码如下:

     function add(...)
        local s = 0
        for i, v in ipairs{...} do
            s = s + v
        end
        return s
    end
    print(add(3,4,5,6,7))
    --输出结果为:25
     

      解释一下,函数声明中的(...)表示该函数可以接受不同数量的参数。当这个函数被调用时,所有的参数都被汇聚在一起,函数中访问它时,仍需用3个点(...)。但不同的是,此时这3个点将作为表达式来使用,如{...}表示一个由所有变参构成的数组。在含有变长参数的函数中个,同样可以带有固定参数,但是固定参数一定要在变长参数之前声明,如:
     

    复制代码 代码如下:

        function test(arg1,arg2,...)
            ...
        end
     

        关于Lua的变长参数最后需要说明的是,由于变长参数中可能包含nil值,因此再使用类似获取table元素数量(#)的方式获取变参的数量就会出现问题。如果要想始终获得正确的参数数量,可以使用Lua提供的select函数,如:
    复制代码 代码如下:

    for i = 1, select('#',...) do  --这里'#'值表示让select返回变参的数量(其中包括nil)。
        local arg = select(i, ...) --这里的i表示获取第i个变参,1为第一个。
         --do something
    end

    3. 具名实参:

        在函数调用时,Lua的传参规则和C语言相同,并不真正支持具名实参。但是我们可以通过table来模拟,比如:
     

    复制代码 代码如下:

        function rename(old,new)
            ...
        end
     

        这里我们可以让上面的rename函数只接收一个参数,即table类型的参数,与此同时,该table对象将含有old和new两个key。如:
     
    复制代码 代码如下:

        function rename(arg)
            local old = arg.old
            local new = arg.new
            ...
        end
     

        这种修改方式有些类似于JavaBean,即将多个参数合并为一个JavaBean。然而在使用时,Lua的table存在一个天然的优势,即如果函数只有一个参数且为string或table类型,在调用该函数时,可以不用加圆括号,如:
     
    复制代码 代码如下:

        rename {old = "oldfile.txt", new = "newfile.txt"}

    二、深入函数:

        在Lua中函数和所有其它值一样都是匿名的,即它们都没有名称。在使用时都是操作持有该函数的变量,如:
     

    复制代码 代码如下:

        a = { p = print }
        a.p("Hello World")
        b = print
        b("Hello World")
     

        在声明Lua函数时,可以直接给出所谓的函数名,如:
     
    复制代码 代码如下:

        function foo(x) return 2 * x end
     

        我们同样可以使用下面这种更为简化的方式声明Lua中的函数,如:
     
    复制代码 代码如下:

        foo = function(x) return 2 * x end
     

        因此,我们可以将函数理解为由语句构成的类型值,同时将这个值赋值给一个变量。由此我们可以将表达式"function(x) body> end"视为一种函数的构造式,就想table的{}一样。我们将这种函数构造式的结果称为一个"匿名函数"。下面的示例显示了匿名函数的方便性,它的使用方式有些类似于Java中的匿名类,如:
     
    复制代码 代码如下:

        table.sort(test_table,function(a,b) return (a.name > b.name) end)

        1. closure(闭合函数):
        若将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,见如下示例:
    复制代码 代码如下:

    function newCounter()
        local i = 0
        return function() --匿名函数
            i = i + 1
            return i
        end
    end
    c1 = newCounter()
    print("The return value of first call is " .. c1())
    print("The return value of second call is " .. c1())
    --输出结果为:
    --The return value of first call is 1
    --The return value of second call is 2

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

    复制代码 代码如下:

    function newCounter()
        local i = 0
        return function() --匿名函数
            i = i + 1
            return i
        end
    end
    c1 = newCounter()
    c2 = newCounter()
    print("The return value of first call with c1 is " .. c1())
    print("The return value of first call with c2 is " .. c2())
    print("The return value of second call with c1 is " .. c1())
    --输出结果为:
    --The return value of first call with c1 is 1
    --The return value of first call with c2 is 1
    --The return value of second call with c1 is 2

    由此可以推出,Lua每次在给新的闭包变量赋值时,都会让不同的闭包变量拥有独立的"非局部变量"。下面的示例将给出基于闭包的更为通用性的用法:

    复制代码 代码如下:

    do
        --这里将原有的文件打开函数赋值给"私有变量"oldOpen,该变量在块外无法访问。
        local oldOpen = io.open
        --新增一个匿名函数,用于判断本次文件打开操作的合法性。
        local access_OK = function(filename,mode) 检查访问权限> end
        --将原有的io.open函数变量指向新的函数,同时在新函数中调用老函数以完成真正的打开操作。
        io.open = function(filename,mode)
            if access_OK(filename,mode) then
                return oldOpen(filename,mode)
            else
                return nil,"Access denied"
            end
        end
    end

    上面的这个例子有些类似于设计模式中装饰者模式。

        2. 非全局函数:

        从上一小节中可以看出,Lua中的函数不仅可以直接赋值给全局变量,同时也可以赋值给其他类型的变量,如局部变量和table中的字段等。事实上,Lua库中大多数table都带有函数,如io.read、math.sin等。这种写法有些类似于C++中的结构体。如:
     

    复制代码 代码如下:

        Lib = {}
        Lib.add = function(x,y) return x + y end
        Lib.sub = function(x,y) return x - y end
     

        或者是在table的构造式中直接初始化,如:
     
    复制代码 代码如下:

        Lib = { add = function(x,y) return x + y end,
                   sub = function(x,y) return x - y end
                 }

        除此之外,Lua还提供另外一种语法来定义此类函数,如:
     
    复制代码 代码如下:

        Lib = {}
        function Lib.add(x,y) return x + y end
        function Lib.sub(x,y) return x - y end
     

        对于Lua中的局部函数,其语义在理解上也是非常简单的。由于Lua中都是以程序块作为执行单元,因此程序块内的局部函数在程序块外是无法访问的,如:
    复制代码 代码如下:

    do
         local f = function(x,y) return x + y end
         --do something with f.
         f(4,5)
    end 

     对于这种局部函数,Lua还提供另外一种更为简洁的定义方式,如:
     

    复制代码 代码如下:

        local function f(x,y) return x + y end
     

        该写法等价于:
     
    复制代码 代码如下:

        local f
        f = function(x,y) return x + y end
     

        3. 正确的尾调用:

        在Lua中支持这样一种函数调用的优化,即“尾调用消除”。我们可以将这种函数调用方式视为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 func>(args>)"形式才是标准的尾调用,至于参数中(args)是否包含表达式,由于表达式的执行是在函数调用之前完成的,因此不会影响该函数成为尾调用函数。

    您可能感兴趣的文章:
    • Lua教程(四):在Lua中调用C语言、C++的函数
    • Lua进阶教程之闭包函数、元表实例介绍
    • Lua基础教程之赋值语句、表达式、流程控制、函数学习笔记
    • Lua教程(一):简介、优势和应用场景介绍
    • Lua教程(二):基础知识、类型与值介绍
    • Lua教程(三):表达式和语句
    上一篇:Lua教程(三):表达式和语句
    下一篇:Lua教程(五):迭代器和泛型for
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    Lua教程(四):函数详解 Lua,教程,四,函数,详解,Lua,