metatable和metamethod - abbshr/Lua-newbie GitHub Wiki

Metatables允许我们改变 table 的行为

每个表都可以有其Metatable.

任何一个表都可以是其他一个表的 metatable,

一组相关的表可以共享一个 metatable(描述他们共同的行为).

一个表也可以是自身的 metatable(描述其私有行为)

创建一个不带metatable的表:

t = {}
print(getmetatable(t)) -- nil

使用setmetatable函数设置/改变一个表的metatable:

mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt) -- true

定义table的算术运算

对于每个算数运算法,metatable都有对应的域:

__add加,__sub减,__mul乘,__div除,__unm负,__pow幂

把相应的域赋值自定义函数,即可在调用算数运算时生效:

mt.__add = func

当对两个不同metatable的操作数执行算数运算时,选择metatable的原则是:

如果第一个操作数的metatable有相应的域,则使用这个metatable,否则查看第二个操作数,否则报错

定义table的关系运算

__eq等于,__lt小于,__le小于等于

关系运算的metatable不支持混合类型运算.

如果两个操作数的metatable不同,则直接报错.但相等操作除外:如果类型不等直接返回false

其他metamethod

print函数通常会调用tostring来格式化输出,而tostring又会调用参数的metatable的__tostring域.

setmetatable/getmetatable会使用__metatable域: 可用于metatable的保护

设置table的__metatable后,setmetatable则会报错,而getmetatable会返回__metatable的值

与表相关的metamethod

__index,当在一个table中找不到指定索引时,将查询__index.

mt.__index = function(table, index) end

mt.__index = {...}

如果不想访问__index域,可以使用raw access方式: rawget(t, i)函数

__newindex,当对表的缺省域赋值,如果定义了__newindex域则调用这个函数而不是对表进行insert赋值,如果__newindex是一个table,则对这个table进行insert赋值

如果希望对原始表进行操作,可以调用rawset(t, k, v)函数

实现一个监控表

---./trackt.lua

local prindex = {};

local mt = {
  __index = function (t, i)
    print('access ' .. tostring(i))
    return t[prindex][i]
  end,

  __newindex = function (t, i, v)
    print('update ' .. tostring(i))
    t[prindex][i] = v
  end
}

function trackt(t)
  -- body
  local proxy = {}
  proxy[prindex] = t
  setmetatable(proxy, mt);
  return proxy;
end

--- 最后导出trackt函数
return trackt