On this page

Lua 元表(Metatable)

Lua 元表(Metatable)深度解析

元表是 Lua 中最强大的特性之一,它允许我们改变表的基础行为,实现面向对象编程、操作符重载等高级功能。

1. 元表基础概念

什么是元表?

  • 元表是一个普通的 Lua 表
  • 可以关联到其他表上(称为目标表)
  • 包含特殊键(称为元方法)定义特定操作行为

基本操作

local target = {}  -- 目标表
local mt = {}      -- 元表

-- 设置元表
setmetatable(target, mt)

-- 获取元表
local mt_get = getmetatable(target)

2. 常用元方法详解

算术运算元方法

mt.__add = function(a, b)  -- 重载 + 运算
    return a.value + b.value
end

local t1 = {value = 10}
local t2 = {value = 20}
setmetatable(t1, mt)

print(t1 + t2)  -- 输出: 30

支持的算术元方法:

  • __add: + (加法)
  • __sub: - (减法)
  • __mul: * (乘法)
  • __div: / (除法)
  • __mod: % (取模)
  • __pow: ^ (指数)
  • __unm: - (负号)
  • __idiv: // (整除,Lua 5.3+)

关系运算元方法

mt.__eq = function(a, b)  -- 重载 == 运算
    return a.id == b.id
end

local t1 = {id = 100}
local t2 = {id = 100}
setmetatable(t1, mt)
setmetatable(t2, mt)

print(t1 == t2)  -- 输出: true

支持的关系元方法:

  • __eq: ==
  • __lt: <
  • __le: <=

访问控制元方法

mt.__index = function(table, key)  -- 访问不存在的键
    return "default_" .. key
end

local t = {}
setmetatable(t, mt)

print(t.name)  -- 输出: default_name
mt.__newindex = function(table, key, value)  -- 设置不存在的键
    rawset(table, "log_" .. key, value)  -- 使用rawset绕过元方法
end

local t = {}
setmetatable(t, mt)
t.name = "Lua"  -- 实际设置的是log_name

print(t.log_name)  -- 输出: Lua

其他重要元方法

-- 调用表时执行
mt.__call = function(table, arg)
    print("Called with:", arg)
end

local t = {}
setmetatable(t, mt)
t("test")  -- 输出: Called with: test

-- 转换为字符串
mt.__tostring = function(table)
    return "Table:" .. table.name
end

local t = {name = "demo"}
setmetatable(t, mt)
print(t)  -- 输出: Table:demo

3. 元表实现面向对象

类实现

local Animal = {}
Animal.__index = Animal  -- 设置__index指向自己

function Animal.new(name)
    local self = setmetatable({}, Animal)
    self.name = name
    return self
end

function Animal:speak()
    print(self.name .. " makes a sound")
end

local dog = Animal.new("Dog")
dog:speak()  -- 输出: Dog makes a sound

继承实现

local Cat = setmetatable({}, {__index = Animal})

function Cat.new(name)
    local self = Animal.new(name)
    setmetatable(self, {__index = Cat})
    return self
end

function Cat:speak()
    print(self.name .. " says meow")
end

local felix = Cat.new("Felix")
felix:speak()  -- 输出: Felix says meow

4. 高级元表技巧

默认值表

local mt = {
    __index = function(t, k)
        return "DEFAULT"
    end
}

local t = setmetatable({}, mt)
print(t.nonexistent)  -- 输出: DEFAULT

只读表

local function readOnly(t)
    local proxy = {}
    local mt = {
        __index = t,
        __newindex = function(_, k, v)
            error("Attempt to modify read-only table", 2)
        end
    }
    return setmetatable(proxy, mt)
end

local config = readOnly{width=100, height=200}
print(config.width)  -- 输出: 100
config.width = 300   -- 报错: Attempt to modify read-only table

跟踪表访问

local function trackedTable(t)
    local proxy = {}
    local mt = {
        __index = function(_, k)
            print("ACCESS:", k)
            return t[k]
        end,
        __newindex = function(_, k, v)
            print("UPDATE:", k, "=", v)
            t[k] = v
        end
    }
    return setmetatable(proxy, mt)
end

local t = trackedTable{}
t.name = "Lua"  -- 输出: UPDATE: name = Lua
print(t.name)   -- 输出: ACCESS: name \n Lua

5. 元表性能考虑

  1. 元方法调用有开销:比直接操作略慢
  2. 避免过度使用:只在真正需要时使用
  3. rawget/rawset:需要性能时绕过元方法
    local value = rawget(t, key)  -- 不触发__index
    rawset(t, key, value)         -- 不触发__newindex
    

元表是 Lua 实现高级特性的核心机制,合理使用可以让你的代码更简洁强大,但也要注意不要滥用影响性能。