简单介绍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,...)