表与元表进阶
00:00
Lua表操作与元表机制
概述
元表(Metatable)是 Lua 实现面向对象、运算符重载和自定义行为的核心机制。通过元方法(Metamethod),可以改变表在特定操作下的行为,如算术运算、索引访问、比较和函数调用等。
基础概念
元方法列表
| 元方法 | 触发操作 | 说明 |
|---|---|---|
| __add | a + b | 加法 |
| __sub | a - b | 减法 |
| __mul | a * b | 乘法 |
| __div | a / b | 除法 |
| __mod | a % b | 取模 |
| __pow | a ^ b | 幂运算 |
| __unm | -a | 取负 |
| __concat | a .. b | 字符串连接 |
| __eq | a == b | 相等比较 |
| __lt | a < b | 小于比较 |
| __le | a <= b | 小于等于比较 |
| __index | a.key | 索引访问(键不存在时) |
| __newindex | a.key = value | 索引赋值(键不存在时) |
| __call | a(args) | 函数调用 |
| __tostring | tostring(a) | 转为字符串 |
| __len | #a | 取长度 |
| __pairs | pairs(a) | 自定义遍历 |
| __ipairs | ipairs(a) | 自定义数组遍历 |
快速上手
运算符重载
-- 向量类型:重载算术运算符
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({ x = x or 0, y = y or 0 }, Vector)
end
-- 加法
Vector.__add = function(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
-- 减法
Vector.__sub = function(a, b)
return Vector.new(a.x - b.x, a.y - b.y)
end
-- 标量乘法
Vector.__mul = function(a, b)
if type(a) == "number" then
return Vector.new(a * b.x, a * b.y)
elseif type(b) == "number" then
return Vector.new(a.x * b, a.y * b)
end
end
-- 字符串表示
Vector.__tostring = function(v)
return string.format("(%g, %g)", v.x, v.y)
end
-- 使用
local v1 = Vector.new(1, 2)
local v2 = Vector.new(3, 4)
print(v1 + v2) -- (4, 6)
print(v1 * 2) -- (2, 4)
print(3 * v2) -- (9, 12)
**index 和 **newindex
-- __index:访问不存在的键时触发
local defaults = { color = "red", size = 10 }
local obj = setmetatable({}, { __index = defaults })
print(obj.color) -- "red"(从 defaults 中查找)
-- __index 可以是函数
local readonly = setmetatable({}, {
__index = function(t, k)
error("只读表,访问: " .. k)
end
})
-- __newindex:给不存在的键赋值时触发
local tracked = setmetatable({}, {
__newindex = function(t, k, v)
print(string.format("设置 %s = %s", k, tostring(v)))
rawset(t, k, v) -- 使用 rawset 实际存储值
end
})
tracked.name = "Alice" -- 输出: 设置 name = Alice
详细用法
__call 实现可调用对象
-- 使用 __call 创建可调用的对象
local Counter = {}
Counter.__index = Counter
function Counter.new(initial)
local self = setmetatable({ count = initial or 0 }, Counter)
return self
end
-- 使对象可以像函数一样调用
Counter.__call = function(self, delta)
self.count = self.count + (delta or 1)
return self.count
end
function Counter:reset()
self.count = 0
end
-- 使用
local c = Counter.new(0)
print(c()) -- 1(默认加1)
print(c(5)) -- 6(加5)
print(c()) -- 7
c:reset()
print(c()) -- 1
__pairs 自定义遍历
-- 使用 __pairs 自定义表的遍历行为
local OrderedTable = {}
OrderedTable.__index = OrderedTable
function OrderedTable.new()
local obj = setmetatable({
_data = {}, -- 实际数据
_order = {}, -- 键的顺序
}, OrderedTable)
return obj
end
function OrderedTable:set(key, value)
if not self._data[key] then
table.insert(self._order, key)
end
self._data[key] = value
end
-- 自定义遍历
OrderedTable.__pairs = function(t)
local i = 0
return function()
i = i + 1
local key = t._order[i]
if key then return key, t._data[key] end
end
end
-- 使用
local ot = OrderedTable.new()
ot:set("banana", 2)
ot:set("apple", 1)
ot:set("cherry", 3)
for k, v in pairs(ot) do
print(k, v) -- 按插入顺序输出
end
代理表(Proxy Table)
-- 使用代理表实现属性观察
function observable(initial)
local data = initial or {}
local handlers = {}
local proxy = setmetatable({}, {
__index = function(t, k)
return data[k]
end,
__newindex = function(t, k, v)
local old = data[k]
data[k] = v
for _, handler in ipairs(handlers) do
handler(k, old, v)
end
end
})
function proxy:onChange(handler)
table.insert(handlers, handler)
end
return proxy
end
-- 使用
local state = observable({ count = 0 })
state:onChange(function(key, old, new)
print(string.format("属性 %s: %s -> %s", key, tostring(old), tostring(new)))
end)
state.count = 1 -- 输出: 属性 count: 0 -> 1
state.count = 2 -- 输出: 属性 count: 1 -> 2
常见场景
只读表
-- 创建只读表
function readonly(t)
return setmetatable({}, {
__index = t,
__newindex = function(t, k, v)
error("尝试修改只读表: " .. k, 2)
end
})
end
local config = readonly({
host = "localhost",
port = 8080,
debug = false
})
print(config.host) -- "localhost"
-- config.host = "x" -- 报错
默认值表
-- 带默认值的表
function defaultTable(defaults)
return setmetatable({}, {
__index = function(t, k)
if defaults[k] ~= nil then
return defaults[k]
end
return nil
end
})
end
local settings = defaultTable({
theme = "dark",
fontSize = 14,
language = "zh-CN"
})
print(settings.theme) -- "dark"
print(settings.unknown) -- nil
注意事项
- **index 和 **newindex 只在键不存在时触发,已存在的键直接访问
- rawget 和 rawset 绕过元方法,直接操作表
- __eq 只在两个操作数的元表相同时才会触发
- 元表设置后不会自动复制,修改元表会影响所有使用该元表的对象
- __call 只在对象本身被调用时触发,不会影响对象的普通方法
- setmetatable 返回第一个参数(表本身),可以链式调用
进阶用法
多重继承
-- 使用 __index 实现多重继承
function createClass(...)
local bases = { ... }
local cls = {}
cls.__index = cls
setmetatable(cls, {
__index = function(t, k)
for _, base in ipairs(bases) do
local v = base[k]
if v ~= nil then return v end
end
end
})
function cls.new(...)
local obj = setmetatable({}, cls)
if cls.init then cls.init(obj, ...) end
return obj
end
return cls
end
-- 使用
local A = { greet = function(self) print("A: hello") end }
local B = { farewell = function(self) print("B: bye") end }
local C = createClass(A, B)
local obj = C.new()
obj:greet() -- A: hello
obj:farewell() -- B: bye