元表与元方法的概念
Lua中每个值都可具有元表。元表是普通的Lua表,定义了原始值在某些特定操作下 的行为。
例如,当table作为加法的操作数时,Lua检查其"元表"中的"__add"字段是否有 个函数。如果有,Lua调用它执行加法。我们称"元表"中的"键(如__add)"为事件(event),称"值"为元方法(metamethod)。前述例子中的事件(event)是"__add",元方法(metamethod)是执行加法的函数。
又例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add "等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。
自己的理解:可以理解为"元方法(metamethod)"只存在与"元表"中。或者认为在元表中,元方法才有意义。
Lua创建新的table的同时不会创建元表,但是可以使用"setmetatable"函数为table设置原表
当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的元表(metatable)(假定有metatable)中的__index键。如果__index 包含一个表格,Lua会在表格中查找对应的键。如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。__index元方法查看表中元素是否存在,如果不存在,返回结构为nil;如果存在则由__index返回结果。
元方法
常用元方法
- __add:加法
- __sub:减法
- __mul:乘法
- __div:除法
- __unm:相反数
- __mod:取模
- __pow:乘幂
- __index:
- __newindex:
- 等
关系类的元方法
除了加法减法这些算术类的操作之外,大于小于等这些关系类的操作也是有元方法的:
- __eq:等于
- __lt:小于
- __le:小于等于
如果对两个具备不同"元表"的值 进行这些比较操作,就会报错,一定要注意,这和加减法的规则不一样。
测试用例
Lua
local mtab1 = {}
mtab1.Name = "mtab1 name"
-- 元方法:加法
mtab1.__add = function(t1, t2)
local val = "mtab1.__add"
-- print(val)
return val
end
-- 元方法:减法
mtab1.__sub = function(t1, t2)
local val = "mtab1.__sub"
-- print(val)
return val
end
-- 元方法:小于
mtab1.__lt = function(t1, t2)
local val = "mtab1.__lt"
print("call metamethod:", val)
math.randomseed(os.time())
local rval = math.random(1, 10)
-- for i = 1, 10 do
-- rval = math.random(1, 10)
-- end
return rval < 5
end
local mtab2 = {}
mtab2.Name = "mtab2 name"
-- 元方法:加法
mtab2.__add = function(t1, t2)
local val = "mtab2.__add"
-- print(val)
return val
end
-- 元方法:减法
mtab2.__sub = function(t1, t2)
local val = "mtab2.__sub"
-- print(val)
return val
end
local tab1 = {}
local tab2 = {}
local tab3 = {}
local tab4 = {}
-- 元方法:加法,如果没有将 tab4 这是为其它 table 的元表,好像没有意义!!
tab4.__add = function(t1, t2)
local val = "tab4.__add"
-- print(val)
return val
end
--此 table 没有元表
local tab5_nomet = {}
-- 元方法:加法
tab5_nomet.__add = function(t1, t2)
local val = "tab5_nomet.__add"
-- print(val)
return val
end
local tab6 = {}
local tab7 = {}
--此 table 没有元表
local tab8_nomet = {}
-- 为 table 表设置 元表
setmetatable(tab1, mtab1)
setmetatable(tab2, mtab1)
setmetatable(tab3, mtab2)
setmetatable(tab4, {__index = mtab1})
setmetatable(tab6, tab5_nomet)
setmetatable(tab7, tab4)
------------------------------------------------------
-- 输出table表的地址
print(tab1, tab2, tab3, tab4, mtab1, mtab2)
-- 输出tabled表的原表地址
-- getmetatable(tab1)与 getmetatable(tab2)的值相等,是因为 tab1 与 tab2 的 元表 都是 mtab1
print(
getmetatable(tab1),
getmetatable(tab2),
getmetatable(tab3),
getmetatable(tab4),
getmetatable(mtab1),
getmetatable(mtab2)
)
-- 输出表 key=Name 的值
print(
"tab1.Name=",
tab1.Name,
",tab2.Name=",
tab2.Name,
",tab3.Name=",
tab3.Name,
",tab4.Name=",
tab4.Name,
",mtab1.Name=",
mtab1.Name,
",mtab2.Name=",
mtab2.Name
)
------------------------------------------------------
------------------------------------------------------
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- 都没有元表。所以,报错:attempt to perform arithmetic on local 'mtab1' (a table value)
-- print("mtab1 + mtab2 =",mtab1 + mtab2)
------------------------------------------------------
-- 相同元表:执行元表中的元方法
-- 输出:tab1 + tab2 = mtab1.__add
print("tab1 + tab2 =", tab1 + tab2)
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- 输出:tab1 + tab3 = mtab1.__add
print("tab1 + tab3 =", tab1 + tab3)
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- 输出:tab1 + tab4 = mtab1.__add
print("tab1 + tab4 =", tab1 + tab4)
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- 输出:tab1 + tab5_nomet = mtab1.__add
print("tab1 + tab5_nomet =", tab1 + tab5_nomet)
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- 输出:tab5_nomet + tab1 = mtab1.__add
print("tab5_nomet + tab1 =", tab5_nomet + tab1)
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- 输出:tab4 + tab1 = mtab1.__add
print("tab4 + tab1 =", tab4 + tab1)
------------------------------------------------------
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- tab4有原表,但是没有__add元方法,tab5_nomet也没有__add元方法。所以,报错:attempt to perform arithmetic on local 'tab4' (a table value)
-- print("tab4 + tab5_nomet =",tab4 + tab5_nomet)
-- 不同元表:第一个值有元表(操作符前面),就以这个元表为准看是否有元方法,如果没有就看第二个元表是否有元方法。都没有元方法就会报错。
-- tab4有原表,但是没有__add元方法,tab5_nomet也没有__add元方法。所以,报错:attempt to perform arithmetic on local 'tab5_nomet' (a table value)
-- print("tab5_nomet + tab4 =", tab5_nomet + tab4)
------------------------------------------------------
-- 输出:tab5_nomet + tab6 = tab5_nomet.__add
print("tab5_nomet + tab6 =", tab5_nomet + tab6)
-- 输出:tab7 + tab8_nomet = tab4.__add
print("tab7 + tab8_nomet =", tab7 + tab8_nomet)
-- 输出:tab7 + tab4 = tab4.__add
print("tab7 + tab4 =", tab7 + tab8_nomet)
------------------------------------------------------
-- 输出:tab1 < tab2 = boolean结果
print("tab1 < tab2 =", tab1 < tab2)
-- 如果对两个具备不同"元表"的值进行这些比较操作,就会报错,一定要注意,这和加减法的规则不一样。
-- 所以,报错:attempt to compare two table values
-- print("tab1 < tab3 =", tab1 < tab3)
print("----------------")
测试用例输出结果
----------------
table: 00C5C9A8 table: 00C5C8E0 table: 00C5C958 table: 00C5C9F8 table: 00C5C9D0 table: 00C5C7F0
table: 00C5C9D0 table: 00C5C9D0 table: 00C5C7F0 table: 00C5C8B8 nil nil
tab1.Name= nil ,tab2.Name= nil ,tab3.Name= nil ,tab4.Name= mtab1 name ,mtab1.Name= mtab1 name ,mtab2.Name= mtab2 name
tab1 + tab2 = mtab1.__add
tab1 + tab3 = mtab1.__add
tab1 + tab4 = mtab1.__add
tab1 + tab5_nomet = mtab1.__add
tab5_nomet + tab1 = mtab1.__add
tab4 + tab1 = mtab1.__add
tab5_nomet + tab6 = tab5_nomet.__add
tab7 + tab8_nomet = tab4.__add
tab7 + tab4 = tab4.__add
call metamethod: mtab1.__lt
tab1 < tab2 = true
----------------