元表里面有元方法
设置元表的方法:
setmetatable(tab, metatable
__index元方法
prototype = {x = 0, y = 0, width = 100, height = 100}
local metatable = {} -- 元表
metatable.__index = function(_, key) --元表里面有__index元方法
return prototype[key]
end
-- 构造函数
function new(o)
setmetatable(o, metatable)
return o
end
w = new{x=10, y=20}
print(w.width)
w没有width这个字段,但是有一个带有__index
方法的元表,于是Lua会将w
和width
一起传入__index
方法
当元方法是函数,Lua会以表和不存在的键作为参数调用该函数。
当元方法是一个表,Lua会访问这个表。也就是说,将4到6行替换成:
metatable.__index = prototype
可以得到相同的结果。
当元方法是一个函数,开销会比表更昂贵,但是更灵活,例如可以通过函数实现多继承、缓存等特性。
__newindex元方法
__index
用于表的查询
__newindex
用于表的更新
对表中不存在的键赋值,Lua会查找__newindex
元方法,如果该原方法是一个函数,则调用它,不赋值。如果该方法是一个表,则在此表赋值,而不是在原始的表中赋值。
组合使用这两种元方法,可以实现只读的表、具有默认值的表、继承。
应用1:具有默认值的表
function SetDefaultValue(t, d)
local metatable = {__index = function() return d end}
setmetatable(t, metatable)
end
tab = {x=10, y=20}
print(tab.x, tab.z) -- 10 nil
SetDefaultValue(tab, 0)
print(tab.x, tab.z) -- 10 0
调用了SetDefaultValue之后,访问tab中不存在的z字段,会先调用其__index
元方法,该方法返回0,所以有默认值。
上述写法开销较大,因为这会为每一个表都创建一个新的闭包和一个新的元表。
以下写法能够使得所有的表都使用一个元表:
local metatable = {__index = function (t) return t.xxx end}
function SetDefaultValue(t, d)
t.xxx = d
setmetatable(t, metatable)
end
tab = {x=10, y=20}
print(tab.x, tab.z) -- 10 nil
SetDefaultValue(tab, 0)
print(tab.x, tab.z) -- 10 0
如果担心命名冲突,可以使用唯一的键:
local key = {}
local metatable = {__index = function (t) return t[key] end}
function SetDefaultValue(t, d)
t[key] = d
setmetatable(t, metatable)
end
tab = {x=10, y=20}
print(tab.x, tab.z) -- 10 nil
SetDefaultValue(tab, 0)
print(tab.x, tab.z) -- 10 0
参考资料:《Lua程序设计 第四版 Programming in Lua, Fourth Edition》