✏️

Metatable & MetaMethod

Created
Feb 13, 2025 09:10 AM

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 __newindex
function 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" 表示值是弱引用)。