第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)) -- true2.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 的作用是什么?
如何实现一个带类型检查的表?