如果说table这种数据结构是Lua的利剑,那么metatable就是Lua的强盾了。metatable可以辅助table完成许多难以实现的功能。
一.什么是metatable
metatable根据其英文翻译就是元表的意思。metatable允许我们改变table的行为,每个行为关联了对应的元方法。
其中就有两个很重要的函数来处理元表:
- setmetatable(table,metatable):对指定table 设置元表(metatable),如果元表中含有键__metatable , setmetatable则会失败。记得 返回值是table
- getmetatable(table):返回对象的元表(metatable)。
二.metatable的运用
设置元表
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把mymetatable 设为mytable 的元表
以上代码也可以直接写成一行:
mytable = setmetable({} , {})
返回对象元表:
getmetatable(mytable) -- 返回mymetatable
三.元方法
metatable作为一个容器,其中的元方法才是真正发挥真正效果的法宝。
1.__index 元方法
这是metatable最常用的键。
当你通过键来访问table的时候,如果这个键没有对应的值,Lua会去判断table是否有元表,如果没有元表就返回nil。如果有元表,则会去调用__index方法,如果__index是一个table,则会去table中查找该键所对应的值。下面具体演示一下
-- 继续承接上面的代码
mymetatable.__index={ foo = 3 } -- 设置元方法__index为table
print(mytable.foo)
-- 输出
-- 3
等同于下面这段
mytable = setmetatable( {} , { __index = { foo = 3} } ) -- 第一个{} 返回给mytable 后面定义了metatable中的__index元方法
print(mytable.foo)
-- 输出
-- 3
当然__index也可以当做函数处理,有着固定的格式(建议)
__index = function(table,key) -- table就是你的普通表 key就是普通表传来的键
-- 这里等于给__index传了一个匿名函数
举个例子:
mytable = { key1 = "C#" }
mymetatable = { __index = function(table,key)
if(key == "key2") then
return "Java"
else
return "Lua"
end
end}
setmetatable(mytable , mymetatable)
print(mytable.key1 , mytable.key2 , mytable.key3)
-- 输出
-- C# Java Lua
上面的代码稍微解释一下。
mytable.key1因为是mytable中就有的键,所以直接找到相应的值为C#。
mytable.key2因为mytable中没有搜索到该键,所以Lua就去找mytable的元表mymetatable中调用__index元方法看是否能找到,function中table传入的就是"mytable",key参数传入的就是搜索的"key2",所以if语句判断成功返回Java。
最后mytable.key3根据上面的推理,由else语句返回Lua。
- Lua并不是一个面向对象的语言,但是利用__index就可以实现简易的继承功能
local test = {}
function test : new ()
self.__index = self
return setmetatable({} , self);
end
function test : say ()
print("你好")
end
local t1 = test:new();
t1.say();
function t1 : say()
print("Hello")
end
local t2 = t1:new();
t2.say();
-- 输出
-- 你好
-- Hello
2.__newindex 元方法
__newindex元方法用来对表更新,__index则用来对表访问。
当你给表的一个缺少的索引赋值,编译器就会查找__newindex元方法:如果存在则调用这个函数而不进行赋值操作。
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print(mytable.key1)
mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)
mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)
-- 输出
-- value1
-- nil 新值2
-- 新值1 nil
以上实例中表设置了元方法 __newindex,在对新索引键(newkey)赋值时(mytable.newkey = “新值2”),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。
3.__call 元方法
__call元方法允许给table调用一个值时调用。
mytable = setmetatable({} , {
__call = function (mytable,key)
if(key == 1) then
print("111")
else
print("222")
end
end
})
mytable(1)
mytable(2)
-- 输出
-- 111
-- 222
当给mytable传入参数1时,这里会调用其元表中的__call元方法。元方法中定义的table就是代指普通表mytable,而key指的就是调用table时传入的参数,这里就是1。后面的逻辑就能搞懂了。
4.__tostring 元方法
__tostring 元方法用于修改表的输出行为。
mytable = setmetatable( {10 , 20 , 30 } , {
__tostring = function (table)
sum = 0
for _ , v in ipairs(table) do
sum = sum + v
end
return "表中所有元素之和"..sum
end
})
print(mytable)
-- 输出
-- 表中所有元素之和60
更多的元方法
模式 | 描述 |
---|---|
__add | 对应的运算符 ‘+’ |
__sub | 对应的运算符 ‘-’ |
__mul | 对应的运算符 ‘*’ |
__div | 对应的运算符 ‘/’ |
__mod | 对应的运算符 ‘%’ |
__unm | 对应的运算符 ‘-’ |
__concat | 对应的运算符’…’ |
__eq | 对应的运算符’==’ |
__lt | 对应的运算符’<’ |
__le | 对应的运算符’<=’ |