Lua中每个值都可具有元表。 元表是普通的table,定义了原始值在某些特定操作下的行为。你可通过在值的原表中设置特定的字段来改变作用于该值的操作的某些行为特征。例如,当数字值作为加法的操作数时,Lua检查其元表中的”__add”字段是否有个函数。如果有,Lua调用它执行加法。
我们称元表中的键为事件(event),称值为元方法(metamethod)。前述例子中的事件是”add”,元方法是执行加法的函数。
不能从Lua中改变其他类型的元表(除了使用调试库);必须使用C API才能做到。表和完整的用户数据具有独立的元表(尽管多个表和用户数据可共享元表);每种其他类型的所有值共享一个元表。所以,所有数字共享一个元表,字符串也是,等等。
一个 metatable 可以控制一个对象做数学运算操作、比较操作、连接操作、取长度操作、取下标操作时的行为, metatable 中还可以定义一个函数,让 userdata 作垃圾收集时调用它。 对于这些操作,Lua 都将其关联上一个被称作事件的指定健。 当 Lua 需要对一个值发起这些操作中的一个时, 它会去检查值中 metatable 中是否有对应事件。 如果有的话,键名对应的值(元方法)将控制 Lua 怎样做这个操作
每种操作都有元表(xx的元表__xx):sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call
其中,__index是取下标操作用于访问 table[key], __newindex是赋值给指定下标 table[key] = value, __call是当Lua调用一个值时调用
setmetatable & getmetatable
设置和查询元表值,setmetatable(只能用于table)和getmetatable(用于任何对象)
下面例子为一个table设置加操作
重载操作符
local mt = {}
function mt.__add(a, b)
return 'table + ' .. b
end
local t = {}
setmetatable(t, mt)
print(t + 1) -- table + 1
面向对象模拟
使用metatable可以模拟出面向对象
local Bird = {}
function Bird:new()
local b = {isDead = false}
setmetatable(b, self)
self.__index = self
return b
end
function Bird:fly()
print("fly ~~~")
end
local bird1 = Bird:new()
print(bird1:fly()) -- fly ~~~
print(bird1.isDead) -- false
这里利用index元表,当我们访问一个表中的元素不存在时,则会触发去寻找__index元方法。所以就可以模拟出类的封装。
Cocos2d-x-lua里有更完善的类和继承的class模拟
table保护
table通过对newindex元表的值处理,可以保护table不被修改
例如cocos2d-x里有个这样屏蔽全局变量的函数:
function cc.disable_global()
setmetatable(__g, {
__newindex = function(_, name, value)
error(string.format("USE \" cc.exports.%s = value \" INSTEAD OF SET GLOBAL VARIABLE", name), 0)
end
})
end
rawset & rawget
rawget 和 rawset 这两个函数,可以避免Lua使用 __index 和 __newindex。一般用在index,newindex元表中以避免死循环。
local Bird = {}
function Bird:new()
local b = {isDead = false}
setmetatable(b, self)
self.__index = function(t, key)return 1000 end
return b
end
w = Bird:new()
print(w["haha"]) -- 1000
print(rawget(w, w["haha"])) -- nil
w["haha"] = 10
print(w["haha"]) -- 10
print(rawget(w, w["haha"])) -- nil
rawset(w, w["haha"], 1)
print(w["haha"]) -- 10
print(rawget(w, w["haha"])) -- 1