Lua

Lua元表

介绍元表和class设计

Posted by Bob on October 18, 2018

简单介绍Lua元表和class设计

Lua的table是hashmap和array的结合体。如果是key-value访问table中一个不存在元素时,会触发lua的”查找机制”,利用这个特性可以模拟class。

--例1
local char = {}
--打印不存在的key:hp
print(char.hp)
--输出nil

getmetatable和setmetatable

lua中的每个值都可以拥有一个元表,只是table和userdata可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。

lua侧代码中只能设置table的元表,至于其他类型值的元表只能通过C代码设置。

多个table可以共享一个通用的元表,但是每个table只能拥有一个元表。

mytable = {}                          -- 普通表 
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表 
getmetatable(mytable)                 -- 返回mymetatable

元表(Metatables)

元表通过一个string key关联元方法(Metamethods),key用加有 ‘__’ 前缀的字符串来表示。

元表决定了一个对象在数学运算、位运算、比较、连接、 取长度、调用、索引时的行为。

元方法 作用
__add + 操作。 如果任何不是数字的值(包括不能转换为数字的字符串)做加法, Lua 就会尝试调用元方法。 首先、Lua 检查第一个操作数(即使它是合法的), 如果这个操作数没有为 “__add” 事件定义元方法, Lua 就会接着检查第二个操作数。 一旦 Lua 找到了元方法, 它将把两个操作数作为参数传入元方法, 元方法的结果(调整为单个值)作为这个操作的结果。 如果找不到元方法,将抛出一个错误。
__sub 运算符-操作
__mul 运算符 *
__ div 运算符 /
__mod 运算符 %
__unm 运算符 -(取反)
__concat 运算符 ..
__eq 运算符 ==
__lt 运算符 <
__le 运算符 <=
__tostring 转化为字符串
__call 函数调用操作 func(args)。 当 Lua 尝试调用一个非函数的值的时候会触发这个事件 (即 func 不是一个函数)。 查找 func 的元方法, 如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。
__index 调索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。此时,会读出 table 相应的元方法。管名字取成这样, 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table 和 key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。 (这个索引过程是走常规的流程,而不是直接索引, 所以这次索引有可能引发另一次元方法。)
__newindex 索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。同索引过程那样, 这个事件的元方法即可以是函数,也可以是一张表。 如果是一个函数, 则以 table、 key、以及 value 为参数传入。 如果是一张表, Lua 对这张表做索引赋值操作。 (这个索引过程是走常规的流程,而不是直接索引赋值, 所以这次索引赋值有可能引发另一次元方法。

__index 元方法

--例2
local char = {mp=2010,life=100}
local hero = {money=999}
--打印不存在的key:life
print(hero.life)
--输出nil
setmetatable(hero, char) --把hero的metatable设置为char
print(hero.life)
--输出nil

如果hero的元表是设置为char,按照定义在hero中找不到life,应该去char里找life,虽然char存在life,但是仍然输出nil。这是因为char的__index元方法没有赋值。 元素只是提供一组操作行为指南。定义了元表,只是拥有了操作指南,但无法在操作指南里查找元素。

__index元方法为一个表

--例3
local char = {mp=2010,life=100}
char.__index = char
local hero = {money=999}
--打印不存在的key:life
print(hero.life)
--输出nil
setmetatable(hero, char) --把hero的metatable设置为char
print(hero.life)
--输出100

执行步骤: 1.当访问hero.life时,表里没有该元素,找到返回。本例找不到继续找。 2.判断hero表是否有元表,如果没有元表,返回nil(第一个print输出nil),有元表继续。 3.判断元表有没有__index元方法,如果__index元方法为nil,则返回nil(例子2),如果__index方法是一个表,则重复1、2、3(例子3 char只执行了第1步);如果__index方法是一个函数,则返回该函数的返回值。

__index元方法为一个方法

当找不到某个key时,通过方法返回一个默认值

--例4
-- define a enumeat class, which has a _Default member, thus can always return a desired value.
function DefEnum(enumDef, defaultVal)
	setmetatable(enumDef, {__index = function(t, key) return defaultVal end})
    return enumDef
end

IdolType = DefEnum({
    Dance = 1,
    Vocal = 2,
    Perf = 3,
},1)

rawget可以让你直接获取到表中索引的实际值,而不通过元表的__index元方法。

local char = {}
char.__index = {life=100}
local hero = {}
setmetatable(hero,char)
print(hero.life)
--通过rawget直接获取hero中的life索引
print(rawget(hero,"life"))
--输出100 nil

__newindex 元方法

__newindex元方法为一个表

__newindex是一个table时,为t中不存在的索引赋值会将该索引和值赋到__newindex所指向的表中,不对原来的表进行改变。

--例5
local char = {mp=2010,life=100}
char.__newindex = char
local hero = {money=999}
setmetatable(hero, char) --把hero的metatable设置为char
print(char.sp,hero.sp)
hero.sp = 666
print(char.sp,hero.sp)

--输出 nil nil 666 nil

__newindex元方法为一个方法

当为table中一个不存在的索引赋值时,会去调用元表中的__newindex元方法

--例6
local vtbl = {}
local char = {mp=2010,life=100}
-- t是hero表,index是key:hp,value是值100
-- 这里没有对t表做任何修改
char.__newindex = function(t,index,value)
  print("index:"..index)
  print("value:"..value)
  vtbl[index]=value
end
local hero = {money=999}
setmetatable(hero, char) 
print(hero.money)
hero.hp = 100
print(hero.hp)
char.__index = function(t,k) 
 return vtbl[k] 
end
print(hero.hp)

--输出 999 index:hp value:100 nil 100

rawset可以让你直接为表中索引的赋值,而不通过元表的__newindex元方法。


local char = {mp=2010,life=100}
char.__newindex = char
local hero = {money=999}
setmetatable(hero, char) --把hero的metatable设置为char
print(char.sp,hero.sp)
rawset(hero,"sp",666)
print(char.sp,hero.sp)

--输出 nil nil nil 666

__call 元方法

__call 元方法在 Lua 调用一个值时调用

--例7
local char = {mp=2010,life=100}
char.__call = function(mytable,...)
    local sum = 0
    for _,v in ipairs{...} do
        print(v)
        sum =  sum  + v
    end
    return sum
end
local hero = {money=999}
setmetatable(hero, char) 
local result = hero(10,20,30)
print(result)
--输出 10 20 30 60

__tostring 元方法

__tostring 元方法用于修改表的输出行为

--例8
local char = {}
char.__tostring = function(mytable)
    local sum = 0
    for _,v in pairs(mytable) do
        sum =  sum  + v
    end
    return "求和为:"..sum
end
local hero = {10,30,40}
setmetatable(hero, char) 
print(hero)
--输出 求和为:80

__add 元方法

相当于C++的+操作符号重载

--例8
local metatable = {}
metatable.__add = function(table1,table2)
    local temp = {}
    for _,v in pairs(table1) do
        table.insert(temp,v)
    end
    for _,v in pairs(table2) do
        table.insert(temp,v)
    end
    return temp
end

local charList = {1,2,3}
local npcList = {4,5,6}
setmetatable(charList,metatable)

local showList = charList + npcList
for _,v in pairs(showList) do
        print(v)
end

执行步骤: 1.查看charList是否有元表,若有,则查看charList的元表是否有__add元方法,若有则调用。(本例执行该步骤) 2.查看npcList是否有元表,若有,则查看npcList的元表是否有__add元方法,若有则调用。 3.若都没有则会报错。

简单class设计

-- define a simple class which  does not derive other class, nor is derived by any class.
-- e.g.  DramaMessage = DefSimpleClass({ font=4 })       DramaWIndow = DefSimpleClass(nil)
function DefSimpleClass(classDef)
	classDef = classDef or {}
	classDef.new = function(...)
        local classInstance = {}
        setmetatable(classInstance, { __index = classDef })
        if classDef.ctor then
            classDef.ctor(classInstance, ...)
        end
        return classInstance
    end
    return classDef
end

定义一个class

SuitModel = DefSimpleClass(nil)

function SuitModel:SetData(displaySuitMeta)
    if displaySuitMeta ~= nil then
        self.displaySuitMeta = displaySuitMeta;
    end
end

function SuitModel:ctor()
    self.gameTime = 5
    self.sqrA = 135 * 135
    self.sqrB = 50 * 50
    self.checkValue = self.sqrA * self.sqrB
end

function SuitModel:HideGainReward(  )
    --UIControlWrap.SetActive(self.wnd, self.spGainReward, false);
    --UIControlWrap.SetActive(self.wnd, self.spTalk, false);
end

function SuitModel:GainReward(type,callback)
    -- ....etc
    if callback ~= nil then callback() end
end

local suitModel = SuitModel.new()
suitModel:HideGainReward()

复杂Class设计

local _class={}
function class(super)
    local class_type = { ctor = false, super = super }    -- 'ctor' field must be here
    local vtbl = {}
    _class[class_type] = vtbl
  
    -- class_type is one proxy indeed. (return class type, but operate vtbl)
    setmetatable(class_type, {
        __newindex= function(t,k,v) vtbl[k] = v end,            
        __index = function(t,k) return vtbl[k] end,
    })
    -- when first invoke the method belong to parent,retrun value of parent
    -- and set the value to child
    if super then
        setmetatable(vtbl, { __index=
            function(t, k)
                if k and _class[super] then
                    local ret = _class[super][k]
                    vtbl[k] = ret                      -- remove this if lua running on back-end server
                    return ret
                else return nil end
            end
        })
    end
    
    class_type.new = function(...)
        local obj = { class = class_type }
        setmetatable(obj, { __index = _class[class_type] })
        
        -- deal constructor recursively
        local inherit_list = {}
        local class_ptr = class_type
        while class_ptr do
            if class_ptr.ctor then table.insert(inherit_list, class_ptr) end
            class_ptr = class_ptr.super
        end
        local inherit_length = #inherit_list
        if inherit_length > 0 then
            for i = inherit_length, 1, -1 do inherit_list[i].ctor(obj, ...) end
        end
        
        obj.class = class_type              -- must be here, because some class constructor change class property.
        return obj
    end
    
    class_type.is = function(self_ptr, compare_class)
        if not compare_class or not self_ptr then return false end
        local raw_class = self_ptr.class
        while raw_class do
            if raw_class == compare_class then return true end
            raw_class = raw_class.super
        end
        return false
    end
    
    return class_type
end


成员方法的定义

function obj:method(a1, a2,...)end--等价于

function obj.method(self, a1, a2,...)end--等价于

obj.method = function(self, a1, a2,...)end

成员方法的调用

obj:method(a1, a2,)--等价于

obj.method(obj, a1, a2,...)