在Lua table中,我们可以对table中的key,value进行操作处理,但无法对两个table进行加减操作,比如:
local tabA = { 1,2,3}local tabB = { 4,5}local tabC = tabA + tabB-- Error: attempt to perform arithmetic on local 'tabA' (a table value)
因此,Lua提供了元表(metatables),允许我们改变table的行为。假如两个table进行相加操作时,lua会检查两个表是否存在Metatable,并且检查Metatable是否有__add域,即元表方法,如果存在,则通过_add执行相加结果。比如:
local meta = {}meta.__add = function(t1,t2) local tmp = {} for _, v in pairs(t1) do table.insert(tmp,v) end for _, v in pairs(t2) do table.insert(tmp,v) end return tmpendlocal tab1 = { 1,2,3}local tab2 = { 4,5}-- 设定元表setmetatable(tab1,meta)setmetatable(tab2,meta)-- 执行相加操作local tab3 = tab1 + tab2print(table.concat(tab3,",")) -- 1,2,3,4,5
lua默认情况下是不存在元表的,我们可以通过getmetatable(table),setmetatable(table,metatable) 来获取或者设定元表的组成,比如:
-- 默认不存在元表,查询方法: getmetatablelocal tabA = {}print(getmetatable(tabA))-- 设定元表,设置方法: setmetatable()local tabB = {}local meta = {} -- 任何表都可以成为元表setmetatable(tabB, meta)assert(getmetatable(tabB) == meta)
一组相关的表,可以共享一个元表,比如:
-- 元表相关local meta = {}meta.__add = function(t1,t2) local tmp = {} for _, v in pairs(t1) do table.insert(tmp,v) end for _, v in pairs(t2) do table.insert(tmp,v) end return tmpendlocal tab1 = { 1,2,3}local tab2 = { 4,5}-- 设定元表,也可以设定任意一个--[[原因:如果第一个参数存在__add域的元表,lua将使用第一个作为元表方法。 同样,如果第二个参数存在__add域的元表,lua将使用第二个作为元表方法 这是lua选择元表方法的原则]]setmetatable(tab1,meta)setmetatable(tab2,meta)-- 执行相加操作local tab3 = tab1 + tab2print(table.concat(tab3,",")) -- 1,2,3,4,5
在lua中,元表都有着对应的域名或者元方法(metaMethod)与运算符相对应,以及其他元方法,如下(注意: __是两个下划线):
算术运算符 | 域名 |
加 + | __add |
减 - | __sub |
乘 * | __mul |
除 / | __div |
模 % | __mod |
取反 | __unm |
连接符 .. | __concat |
幂 | __pow |
等于 | __eq |
小于 | __lt |
小于等于 | __le |
不等于 | 没有域名,会由 a ~= b 转换为 not(a==b) |
大于 | 没有域名,会由 a > b 转换为 b < a |
大于等于 | 没有域名, 会由 a >= b 转换为 b <= a |
函数调用 | __call |
转为字符串 | __tostring |
调用索引 | __index |
给索引赋值 | __newindex |
可参考C库中的ltm.c文件,如下:
void luaT_init (lua_State *L) { static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__gc", "__mode", "__len", "__eq", "__add", "__sub", "__mul", "__mod", "__pow", "__div", "__idiv", "__band", "__bor", "__bxor", "__shl", "__shr", "__unm", "__bnot", "__lt", "__le", "__concat", "__call" }; int i; for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); luaC_fix(L, obj2gco(G(L)->tmname[i])); /* never collect these names */ }}
接下来,我们来模拟下几个元表方法:
__index:
用于对表的访问,当调用table的一个不存在的key时,如果这个key没有,lua会查找元表的__index。__index可以是table也可以是方法
作为table:查找__index元方法表,若有,则返回该索引对应的值,否则返回nil
local meta = {}meta.__index = {key = "meta"}local tab = {one = 1,two = 2}print(tab.key) -- nilsetmetatable(tab, meta)print(tab.key) -- meta
代码也可编写为:
local tab = setmetatable({one = 1,two = 2},{__index = {key = "meta"}})print(tab.key) -- meta
作为方法:将表和索引作为参数传入__index,return返回值
local meta = {}-- 参数分别为:表自己,索引meta.__index = function(tab,key) if key == "key2" then return "meta value2" end return nilendlocal tab = {key1 = "value1"}print(tab.key1, tab.key2) -- value1 nilsetmetatable(tab, meta)print(tab.key1, tab.key2) -- value1 meta value2
注意,查找key时,如果表中存在,则使用表自己的,如果不存在,则查找元表中的。比如:
local meta = {}-- 参数分别为:表自己,索引meta.__index = function(tab,key) if key == "key2" then return "meta value2" end return nilendlocal tab = {key1 = "value1",key2 = "value2"}print(tab.key1, tab.key2) -- value1 value2setmetatable(tab, meta)print(tab.key1, tab.key2) -- value1 value2
__newindex
主要用于对表数据的更新,当为table中一个不存在的索引赋值时,会去调用元表中的__newindex元方法,如果存在则调用该表而不进行赋值操作
local metaTab = {} -- 元方法中的表local tab = {key = "key1"} -- 自己的表setmetatable(tab,{__newindex = metaTab})print(tab.key) -- key1-- 给自己的表中不存在的索引赋值tab.newkey = "newkey"-- 自己的还是为空,实质上其赋值操作是调用了__newindex的表,而非自己print(tab.newkey, metaTab.newkey) -- nil newkey1-- 给自己表中原有索引重新赋值tab.key = "new key1"-- 已存在的索引赋值,不会再调用__indexprint(tab.key, metaTab.key) -- new key1 nil
后续的再做补充...
参考: