元表与元方法 #

一、元表概述 #

1.1 什么是元表 #

元表是一个普通的表,用于定义其他表的行为。通过元表,可以:

  • 重定义运算符行为
  • 自定义表的访问方式
  • 实现面向对象编程
lua
-- 每个表都可以有一个元表
local t = {}
local mt = {}
setmetatable(t, mt)

-- 或简写
local t = setmetatable({}, {})

1.2 获取和设置元表 #

lua
local t = {}
local mt = {}

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

-- 获取元表
print(getmetatable(t) == mt)  -- true

-- 创建时设置
local t2 = setmetatable({}, {
    __index = {x = 1, y = 2}
})
print(t2.x)  -- 1

二、元方法 #

2.1 算术运算元方法 #

lua
local Vector = {}
Vector.__index = Vector

function Vector:new(x, y)
    return setmetatable({x = x, y = y}, self)
end

-- 加法
function Vector:__add(other)
    return Vector:new(self.x + other.x, self.y + other.y)
end

-- 减法
function Vector:__sub(other)
    return Vector:new(self.x - other.x, self.y - other.y)
end

-- 乘法(与标量)
function Vector:__mul(scalar)
    return Vector:new(self.x * scalar, self.y * scalar)
end

-- 除法
function Vector:__div(scalar)
    return Vector:new(self.x / scalar, self.y / scalar)
end

-- 取负
function Vector:__unm()
    return Vector:new(-self.x, -self.y)
end

-- 字符串表示
function Vector:__tostring()
    return string.format("(%g, %g)", self.x, self.y)
end

-- 使用
local v1 = Vector:new(1, 2)
local v2 = Vector:new(3, 4)

print(v1 + v2)  -- (4, 6)
print(v1 - v2)  -- (-2, -2)
print(v1 * 2)   -- (2, 4)
print(-v1)      -- (-1, -2)

2.2 比较运算元方法 #

lua
local Vector = {}
Vector.__index = Vector

function Vector:new(x, y)
    return setmetatable({x = x, y = y}, self)
end

-- 相等
function Vector:__eq(other)
    return self.x == other.x and self.y == other.y
end

-- 小于
function Vector:__lt(other)
    return self.x < other.x or (self.x == other.x and self.y < other.y)
end

-- 小于等于
function Vector:__le(other)
    return self < other or self == other
end

-- 使用
local v1 = Vector:new(1, 2)
local v2 = Vector:new(1, 3)

print(v1 == v2)  -- false
print(v1 < v2)   -- true
print(v1 <= v2)  -- true

2.3 __index 元方法 #

lua
-- __index 可以是表或函数

-- 作为表
local defaults = {x = 0, y = 0, z = 0}
local point = setmetatable({}, {__index = defaults})

print(point.x)  -- 0(从 defaults 获取)
point.x = 10
print(point.x)  -- 10(自己的值)

-- 作为函数
local t = setmetatable({}, {
    __index = function(tbl, key)
        print("访问不存在的键:" .. key)
        return nil
    end
})

print(t.name)  -- 访问不存在的键:name

-- 实现继承
local Animal = {name = "Animal"}

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

local Dog = setmetatable({}, {__index = Animal})
Dog.name = "Dog"

function Dog:speak()
    print(self.name .. " says: Woof!")
end

Dog:speak()  -- Dog says: Woof!

2.4 __newindex 元方法 #

lua
-- __newindex 在赋值时触发

-- 监控赋值
local t = setmetatable({}, {
    __newindex = function(tbl, key, value)
        print(string.format("设置 %s = %s", key, value))
        rawset(tbl, key, value)
    end
})

t.name = "Lua"  -- 设置 name = Lua

-- 只读表
local function readonly(t)
    local proxy = {}
    setmetatable(proxy, {
        __index = t,
        __newindex = function()
            error("表是只读的", 2)
        end
    })
    return proxy
end

local original = {x = 1, y = 2}
local ro = readonly(original)
print(ro.x)  -- 1
-- ro.x = 10  -- 错误:表是只读的

2.5 __call 元方法 #

lua
-- __call 使表可以像函数一样调用

local Counter = {}
Counter.__index = Counter

function Counter:new()
    return setmetatable({count = 0}, self)
end

function Counter:__call(n)
    self.count = self.count + (n or 1)
    return self.count
end

local counter = Counter:new()
print(counter())    -- 1
print(counter(5))   -- 6
print(counter.count)  -- 6

2.6 __len 元方法 #

lua
-- __len 定义 # 运算符的行为

local Set = {}
Set.__index = Set

function Set:new(items)
    local set = {}
    for _, v in ipairs(items or {}) do
        set[v] = true
    end
    return setmetatable(set, self)
end

function Set:__len()
    local count = 0
    for _ in pairs(self) do
        count = count + 1
    end
    return count
end

local s = Set:new({1, 2, 3, 4, 5})
print(#s)  -- 5

2.7 __tostring 元方法 #

lua
-- __tostring 定义 tostring() 的行为

local Person = {}
Person.__index = Person

function Person:new(name, age)
    return setmetatable({name = name, age = age}, self)
end

function Person:__tostring()
    return string.format("Person{name=%s, age=%d}", self.name, self.age)
end

local p = Person:new("Alice", 30)
print(p)  -- Person{name=Alice, age=30}
print(tostring(p))  -- Person{name=Alice, age=30}

2.8 __pairs 和 __ipairs(Lua 5.2+) #

lua
-- 自定义遍历行为

local Array = {}
Array.__index = Array

function Array:new(...)
    return setmetatable({...}, self)
end

function Array:__pairs()
    local i = 0
    local n = #self
    return function()
        i = i + 1
        if i <= n then
            return i, self[i]
        end
    end
end

local arr = Array:new(1, 2, 3, 4, 5)
for i, v in pairs(arr) do
    print(i, v)
end

三、运算符重载表 #

元方法 运算符 描述
__add + 加法
__sub - 减法
__mul * 乘法
__div / 除法
__mod % 取模
__pow ^ 幂运算
__unm - 取负
__idiv // 整除
__band & 按位与
__bor | 按位或
__bxor ~ 按位异或
__bnot ~ 按位非
__shl << 左移
__shr >> 右移
__concat 连接
__len # 长度
__eq == 相等
__lt < 小于
__le <= 小于等于

四、实用示例 #

4.1 实现类 #

lua
local Class = {}

function Class:new(parent)
    local class = {}
    class.__index = class
    
    if parent then
        setmetatable(class, {__index = parent})
    end
    
    function class:new(...)
        local instance = setmetatable({}, self)
        if instance.init then
            instance:init(...)
        end
        return instance
    end
    
    return class
end

-- 使用
local Animal = Class:new()

function Animal:init(name)
    self.name = name
end

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

local Dog = Class:new(Animal)

function Dog:init(name, breed)
    Animal.init(self, name)
    self.breed = breed
end

function Dog:speak()
    print(self.name .. " says: Woof!")
end

local dog = Dog:new("Buddy", "Golden Retriever")
dog:speak()  -- Buddy says: Woof!

4.2 实现代理表 #

lua
local function create_proxy(t)
    local proxy = {}
    local mt = {
        __index = function(_, key)
            print("获取:" .. key)
            return t[key]
        end,
        __newindex = function(_, key, value)
            print("设置:" .. key .. " = " .. tostring(value))
            t[key] = value
        end,
        __pairs = function()
            return pairs(t)
        end
    }
    setmetatable(proxy, mt)
    return proxy
end

local data = {x = 1, y = 2}
local proxy = create_proxy(data)
print(proxy.x)  -- 获取:x    1
proxy.z = 3     -- 设置:z = 3

4.3 实现默认值 #

lua
local function table_with_default(default)
    return setmetatable({}, {
        __index = function()
            return default
        end
    })
end

local t = table_with_default(0)
t.x = 10
print(t.x)  -- 10
print(t.y)  -- 0(默认值)

4.4 实现弱引用表 #

lua
-- 弱键表
local weak_keys = setmetatable({}, {__mode = "k"})

-- 弱值表
local weak_values = setmetatable({}, {__mode = "v"})

-- 弱键弱值
local weak_both = setmetatable({}, {__mode = "kv"})

-- 使用示例:缓存
local cache = setmetatable({}, {__mode = "v"})

local function get_cached(key, factory)
    if cache[key] then
        return cache[key]
    end
    local value = factory()
    cache[key] = value
    return value
end

五、总结 #

本章介绍了 Lua 的元表和元方法:

  1. 元表概念:定义表的行为
  2. 算术元方法:重定义运算符
  3. 比较元方法:自定义比较行为
  4. __index:访问不存在的键
  5. __newindex:赋值时触发
  6. __call:使表可调用
  7. 其他元方法:__len、__tostring、__pairs

下一章,我们将学习面向对象编程。

最后更新:2026-03-27