6Programming in Lua(第4版)

元表与元方法

元编程

metatable__index__newindex__call

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第6章:元表与元方法

一、导读

1.1 本章学习目标

第6章讲解 Lua 的元表(metatable)和元方法(metamethod):如何改变 table 的默认行为。

本章核心学习目标:

  • 掌握元表的概念:改变 table 的行为
  • 理解核心元方法:__index、__newindex、__add、__tostring
  • 掌握元表的高级应用

1.2 为什么本章如此重要

元表是 Lua 最强大的特性之一。它让 Lua 支持运算符重载、默认值、只读表等高级功能。

1.3 核心问题引导

__index 元方法在什么时候被调用?

如何实现运算符重载?

元表如何支持面向对象?


二、核心概念详解

2.1 元表基础

lua
-- 设置元表
local t = {}
local mt = {}
setmetatable(t, mt)

-- __index: 当访问不存在的键时
mt.__index = function(table, key)
    return "default value for " .. key
end

print(t.anything)  -- default value for anything

-- 常用模式: 用 table 作为 __index
local defaults = {color = "white", size = 10}
local item = setmetatable({}, {__index = defaults})
print(item.color)  -- white
print(item.size)   -- 10
item.color = "red"
print(item.color)  -- red (自己的值优先)

2.2 算术元方法

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:__tostring()
    return string.format("(%g, %g)", self.x, self.y)
end

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

-- 使用
local v1 = Vector:new(1, 2)
local v2 = Vector:new(3, 4)
print(v1 + v2)       -- (4, 6)
print(v1 * 3)        -- (3, 6)
print(v1 == Vector:new(1, 2))  -- true

2.3 __newindex 和 __call

lua
-- __newindex: 当设置不存在的键时
local t = setmetatable({}, {
    __newindex = function(table, key, value)
        rawset(table, key, string.upper(value))
    end
})
t.name = "alice"
print(t.name)  -- ALICE

-- __call: 让 table 可以像函数一样调用
local Callable = {}
setmetatable(Callable, {
    __call = function(self, ...)
        print("Called with:", ...)
    end
})
Callable("hello", "world")  -- Called with: hello world

-- 只读表
function readOnly(t)
    local proxy = {}
    setmetatable(proxy, {
        __index = t,
        __newindex = function()
            error("attempt to update a read-only table", 2)
        end
    })
    return proxy
end

local config = readOnly{debug = true, port = 8080}
print(config.port)    -- 8080
-- config.port = 9090  -- error!

三、实战案例

3.1 案例一:带默认值的表

lua
function tableWithDefault(default)
    return setmetatable({}, {
        __index = function(t, k)
            return default
        end
    })
end

-- 计数器
local counts = tableWithDefault(0)
local words = {"apple", "banana", "apple", "cherry", "banana", "apple"}

for _, word in ipairs(words) do
    counts[word] = counts[word] + 1
end

for word, count in pairs(counts) do
    print(word, count)
end
-- apple   3
-- banana  2
-- cherry  1

四、本章小结

元表改变 table 的默认行为

`__index` 在键不存在时被调用

算术元方法支持运算符重载


五、思考与练习

rawget 和 rawset 的作用是什么?

如何实现一个带类型检查的表?


6/8元表与元
0%