前置知识: Lua

表与元表进阶

00:00
3 min Intermediate 2026/6/14

Lua表操作与元表机制

概述

元表(Metatable)是 Lua 实现面向对象、运算符重载和自定义行为的核心机制。通过元方法(Metamethod),可以改变表在特定操作下的行为,如算术运算、索引访问、比较和函数调用等。

基础概念

元方法列表

元方法触发操作说明
__adda + b加法
__suba - b减法
__mula * b乘法
__diva / b除法
__moda % b取模
__powa ^ b幂运算
__unm-a取负
__concata .. b字符串连接
__eqa == b相等比较
__lta < b小于比较
__lea <= b小于等于比较
__indexa.key索引访问(键不存在时)
__newindexa.key = value索引(键不存在时)
__calla(args)函数调用
__tostringtostring(a)转为字符串
__len#a取长
__pairspairs(a)自定义遍历
__ipairsipairs(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 只在键不存在触发,已存在的键直接访问
  • rawgetrawset 绕过元方法直接操作
  • __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

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式