二十四、Lua 学习笔记之二(进阶话题)

进阶话题

1.函数闭包

1、1 实例代码

    function createCountdownTimer(second)
        local ms = second * 1000  --ms为countDown的Upvalue
        local function countDown()
            ms = ms -1
            return ms
        end
        return countDown
    end
    local timer1 = createCountdownTimer(1) 
    for i = 1, 3 do
        print(timer1()) 
    end

输出结果:

999

998

997

1、2 关于函数闭包描述

  • Upvalue

一个函数所使用的定义在它的函数体之外的局部变量(external local variable)称为这个函数的upvalue。 在前面的代码中,函数countDown使用的定义在函数createCountdownTimer 中的局部变量ms就是countDown的upvalue,但ms对createCountdownTimer而 言只是一个局部变量,不是upvalue。 Upvalue是Lua不同于C/C++的特有属性,需要结合代码仔细体会。

  • 函数闭包

一个函数和它所使用的所有upvalue构成了一个函数闭包。

  • Lua函数闭包与C函数的比较

Lua函数闭包使函数具有保持它自己的状态的能力,从这个意义上说,可以 与带静态局部变量的C函数相类比。但二者有显著的不同:对Lua来说,函数 是一种基本数据类型——代表一种(可执行)对象,可以有自己的状态;但 是对带静态局部变量的C函数来说,它并不是C的一种数据类型,更不会产生 什么对象实例,它只是一个静态地址的符号名称。

2. 基于对象的实现方式

2、2 实例代码

    local function create(name ,id )
        local data = {name = name ,id = id}  --data为obj.SetName,obj.GetName,obj.SetId,obj.GetId的Upvalue
        local obj = {}  --把需要隐藏的成员放在一张表里,把该表作为成员函数的upvalue。
        function obj.SetName(name)
            data.name = name 
        end
        function obj.GetName() 
            return data.name
        end
        function obj.SetId(id)
            data.id = id 
        end
        function obj.GetId() 
            return data.id
        end
        return obj
    end

输出结果:

mycreate’s id:1mycreate’s name:Sam

mycreate’s id:1mycreate’s name:Lucy

2、2 有关对象实现的描述

实现方式: 把需要隐藏的成员放在一张表里,把该表作为成员函数的upvalue。

局限性: 基于对象的实现不涉及继承及多态。但另一方面,脚本编程是否需要继承和多态要视情况而定。

3.元表

3、1 实例代码(1):

    local t = {}
    local m = {a = "and",b = "Li Lei", c = "Han Meimei"}
    setmetatable(t,{__index = m})  --表{ __index=m }作为表t的元表
    for k,v in pairs(t) do  --穷举表t
        print("有值吗?")
        print(k,"=>",v)
    end
    print("-------------")
    print(t.b, t.a, t.c)

输出结果:


LiLeiandHan Meimei

3、2 实例代码(2):

    local function add(t1,t2)
        --‘#’运算符取表长度
        assert(#t1 ==t2)
        local length =t1
        for i = 1,length do
            t1[i] = t1[i] + t2[i]
        end
        return t1
    end
    --setmetatable返回被设置的表
    t1 = setmetatable({1,2,3},{__add = add})
    t2 = setmetatable({10,20,30},{__add = add})
    for k,v in  pairs(t1) do
        print(k,"=>",v)
    end
    for k,v in  pairs(t2) do
        print(k,"=>",v)
    end
    print("---------两元表相加--------------")
    t1 = t1 + t2
    for i = 1 ,#t1 do
        print(t1[i])
    end

输出结果:

1=>1

2=>2

3=>3

1=>10

2=>20

3=>30

-——–两元表相加————–

11

22

33

3、3 有关元表的描述:

定义 :

元表本身只是一个普通的表,通过特定的方法(比如setmetatable)设置到某个对象上,进而影响这个对象的行为;一个对象有哪些行为受到元表影响以及这些行为按照何种方式受到影响是受Lua语言约束的。比如在前面的代码里,两个表对象的加法运算,如果没有元表的干预,就是一种错误;但是Lua规定了元表可以“重载”对象的加法运算符,因此若把定义了加法运算的元表设置到那两个表上,它们就可以做加法了。元表是Lua最关键的概念之一,内容也很丰富,请参考Lua文档了解详情。

元表与C++虚表的比较:

如果把表比作对象,元表就是可以改变对象行为的“元”对象。在某种程度上,元表可以与C++的虚表做一类比。但二者还是迥然不同的:元表可以动态的改变,C++虚表是静态不变的;元表可以影响表(以及其他类型的对象)的很多方面的行为,虚表主要是为了定位对象的虚方法(最多再带上一点点RTTI)。

4. 基于原型的继承

4、 1实例代码;

    local Robot = { name = "Sam", id = 001 }
    function Robot:New(extension)
        local t = setmetatable(extension or { }, self) 
        self.__index = self
        return t
    end
    function Robot:SetName(name)
        self.name = name 
    end
    function Robot:GetName() 
        return self.name
    end
    function Robot:SetId(id)
        self.id = id end
    function Robot:GetId() 
        return self.id
    end
    robot = Robot:New()
    print("robot's name:", robot:GetName()) 
    print("robot's id:", robot:GetId()) 
    print("-----------------")
    local FootballRobot = Robot:New({position = "right back"})
    function FootballRobot:SetPosition(p) 
        self.position = p
    end
    function FootballRobot:GetPosition()
        return self.position 
    end
    fr = FootballRobot:New()
    print("fr's position:", fr:GetPosition()) 
    print("fr's name:", fr:GetName()) 
    print("fr's id:", fr:GetId()) 
    print("-----------------")
    fr:SetName("Bob")
    print("fr's name:", fr:GetName()) 
    print("robot's name:", robot:GetName())

输出结果:

robot’s name:Sam

robot’s id:1


fr’s position:right back

fr’s name:Sam

fr’s id:1


fr’s name:Bob

robot’s name:Sam

4、2 相关描述:

prototype模式一个对象既是一个普通的对象,同时也可以作为创建其他对象的原型的对象(即类对象,class object);动态的改变原型对象的属性就可以动态的影响所有基于此原型的对象;另外,基于一个原型被创建出来的对象可以重载任何属于这个原型对象的方法、属性而不影响原型对象;同时,基于原型被创建出来的对象还可以作为原型来创建其他对象。

5.包

5、1 实例代码:

hello.lua

    local pack = require "mypack"  --导入包[注:包的名字与定义包的文件的名字相同(除去文件名后缀,在前面的代码中,就是“mypack”)]
    print(ver or "No ver defined!")
    print(pack.ver)
    pack.aFunInMyPack()
    print(aFunInMyPack or "No aFunInMyPack defined!")
    aFuncFromMyPack()

mypack.lua

module(..., package.seeall) --定义包
ver = "0.1 alpha"
function aFunInMyPack() 
    print("Hello!")
end
_G.aFuncFromMyPack = aFunInMyPack

输出结果:

Nover defined!

0、 1alpha;

Hello!

NoaFunInMyPack defined!

Hello!

5、2有关包的描述:

  • 定义

包是一种组织代码的方式。

  • 实现方式

一般在一个Lua文件内以module函数开始定义一个包。module同时定义了一个新的包的函数环境,以使在此包中定义的全局变量都在这个环境中,而非使用包的函数的环境中。理解这一点非常关键。以前面的代码为例, “module(…, package.seeall)”的意思是定义一个包,包的名字与定义包的文件的名字相同(除去文件名后缀,在前面的代码中,就是“mypack”),并且在包的函数环境里可以访问使用包的函数环境(比如,包的实现使用了print,这个变量没有在包里定义,而是定义在使用包的外部环境中)。

  • 使用方式

一般用require函数来导入一个包,要导入的包必须被置于包路径(packagepath)上。包路径可以通过package.path或者环境变量来设定。一般来说,当前工作路径总是在包路径中。

  • 其他

请参考Lua手册进一步了解包的详细说明。

参考文献《C/C++程序员的Lua快速入门》