Intro
元表用于定义表与表之间的操作符的行为
元方法就是操作的定义
getmetatable(t) / setmetatable(t, mt) 算术运算相关元方法
定义元表的__add, __mul方法啥的
local mt = {} local Set = {} function Set.new (l) local set = {} setmetatable(set, mt) for _, v in ipairs(l) do set[v] = true end return set end function Set.union (a, b) local res = {} for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection (a, b) local res = {} for k in pairs(a) do res[k] = b[k] end return res end function Set.tostring (set) local l = {} for e in pairs(set) do l[#l + 1] = e end return "{".. table.concat(l, ",") .. "}" end mt.__add = Set.union mt.__mul = Set.intersection mt.__tostring = Set.tostring s1 = Set.new{10, 20, 30} s2 = Set.new({30, 1}) print(getmetatable(s1)) s3 = s1 + s2 print(s1) print(Set.tostring(s3)) --[[ table: 00000000006ea150 {20,10,30} {20,1,10,30} ]]--
表相关的元方法
__index 在读取表中不存在的元素时会用到__newindex 在更新表中不存在的元素时会用到prototype = {x = 0, y = 0, width = 100, height = 100} function new(o) setmetatable(o, mt) return o end mt.__index = function (_, key) return prototype[key] end mt.__newindex = function (t, key, value) print("Add new field: " .. key.tostring()) t[key] = value end --[[ 另一种写法是直接用表作为__index字段的值 mt.__index = prototype ]]-- local w = new {x = 10, y = 20} print(w.width) --> 100, 当w.width不存在时会调用prototype[width] w.height = 10 print(w.height) --[[ 100 Add new field: height 10 ]]--
Practice: 跟踪对表的访问(Proxy)
直接创建一个空代理对象proxy,将元表mt的元方法字段全部设置为作用于t的,由于proxy是空表所以所有对表t的读写一定经过
__index 和 __newindexfunction track (t) local mt = { __index = function (_, k) print("*access to element " .. tostring(k)) return t[k] end, __newindex = function (_, k, v) print("*update element " .. tostring(k) .. " with value " .. tostring(v)) rawset(t, k, v) end, __pairs = function () return function (_, k) local nextkey, nextvalue = next(t, k) if (nextkey ~= nil) then print("*traversing element " .. tostring(nextkey)) end return nextkey, nextvalue end end, __len = function () return #t end, } local proxy = {} setmetatable(proxy, mt) return proxy end local t = track({}) t.x = 1 t.y = 10 print(t["x"]) for k, v in pairs(t) do print("(" .. tostring(k) .. ", " .. tostring(v) .. ")") end --[[ *update element x with value 1 *update element y with value 10 *access to element x 1 *traversing element x (x, 1) *traversing element y (y, 10) ]]--
补充:元方法字段表
算术运算符相关的元方法
元方法字段 | 操作符 | 描述 |
__add | + | 定义表的加法操作,例如 t1 + t2。 |
__sub | - | 定义表的减法操作,例如 t1 - t2。 |
__mul | * | 定义表的乘法操作,例如 t1 * t2。 |
__div | / | 定义表的除法操作,例如 t1 / t2。 |
__mod | % | 定义表的取模操作,例如 t1 % t2。 |
__pow | ^ | 定义表的幂运算操作,例如 t1 ^ t2。 |
__unm | - | 定义表的取负操作,例如 -t1。 |
__idiv | // | 定义表的整数除法操作,例如 t1 // t2(Lua 5.3 及以上版本支持)。 |
比较运算符相关的元方法
元方法字段 | 操作符 | 描述 |
__eq | == | 定义表的相等比较操作,例如 t1 == t2。 |
__lt | < | 定义表的小于比较操作,例如 t1 < t2。 |
__le | <= | 定义表的小于等于比较操作,例如 t1 <= t2。 |
注意:
- Lua 不支持
__gt(大于)和__ge(大于等于)元方法。如果需要实现这些操作,可以通过__lt和__le间接实现。
- 如果定义了
__eq,则必须同时定义__le和__lt,否则会报错。
其他操作相关的元方法
元方法字段 | 操作符或用法 | 描述 |
__tostring | tostring(t) 或 print(t) | 定义表的字符串表示形式,例如 print(t) 时会调用此方法。 |
__concat | .. | 定义表的连接操作,例如 t1 .. t2。 |
__len | # | 定义表的长度操作,例如 #t。 |
__call | t(...) | 定义表的调用操作,例如 t() 或 t(1, 2, 3)。 |
__index | t[key] | 定义表的索引访问操作,例如 t[key]。 |
__newindex | t[key] = value | 定义表的索引赋值操作,例如 t[key] = value。 |
__pairs | pairs(t) | 定义表的遍历操作(Lua 5.2 及以上版本支持)。 |
__ipairs | ipairs(t) | 定义表的顺序遍历操作(Lua 5.2 及以上版本支持)。 |
__gc | 垃圾回收 | 定义表的垃圾回收行为(Lua 5.2 及以上版本支持)。 |
__mode | 弱引用 | 定义表的弱引用模式( "k" 表示键是弱引用,"v" 表示值是弱引用)。 |