元表是Lua中实现元编程的核心机制。每个表都可以关联一个元表,当对表执行某些操作时,Lua会检查元表中是否定义了对应的元方法。如果定义了,就调用该元方法;否则使用默认行为。元表查找遵循"元方法查找规则":先查找表本身的元表,如果找不到则递归查找元表的元表。这种机制允许我们改变表的行为,实现运算符重载、面向对象、代理模式等高级功能。
元表(Metatable)是Lua中的一个强大特性,允许改变表的行为。通过元表可以实现运算符重载、面向对象等高级功能。
-- 元表的基本概念
-- 元表是一个普通的表
-- 通过setmetatable设置元表
-- 通过getmetatable获取元表
-- 元方法是以双下划线开头的特殊字段
-- 元表的应用场景:
-- 1. 运算符重载
-- 2. 面向对象编程
-- 3. 只读表/只写表
-- 4. 代理模式
-- 5. 默认值
-- 6. 访问控制
-- 7. 类型检查
-- 创建表和元表
local t = {}
local mt = {}
-- 设置元表
setmetatable(t, mt)
-- 获取元表
local meta = getmetatable(t)
print(meta == mt) -- true
-- 链式设置
local t2 = setmetatable({}, {})
-- 保护元表
local t3 = setmetatable({}, {
__metatable = "受保护的元表"
})
print(getmetatable(t3)) -- 受保护的元表
-- setmetatable(t3, {}) -- 错误:无法修改受保护的元表
-- __index:访问不存在的键时调用
local defaults = {
name = "未知",
age = 0
}
local person = {}
setmetatable(person, {__index = defaults})
print(person.name) -- 未知 (从defaults获取)
print(person.age) -- 0
person.name = "张三"
print(person.name) -- 张三 (从person自身获取)
-- __index也可以是函数
local t = {}
setmetatable(t, {
__index = function(table, key)
return "键 " .. key .. " 不存在"
end
})
print(t.anything) -- 键 anything 不存在
-- __newindex:设置不存在的键时调用
local t = {}
local storage = {}
setmetatable(t, {
__newindex = function(table, key, value)
print("设置键: " .. key .. " = " .. tostring(value))
storage[key] = value
end,
__index = storage
})
t.name = "张三" -- 输出: 设置键: name = 张三
print(t.name) -- 张三
-- 只读表
function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(table, key, value)
error("表是只读的")
end
}
setmetatable(proxy, mt)
return proxy
end
local data = {x = 10, y = 20}
local ro = readOnly(data)
print(ro.x) -- 10
-- ro.x = 100 -- 错误:表是只读的
-- 向量类
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
local v = {x = x, y = y}
setmetatable(v, Vector)
return v
end
-- __add:加法运算符
function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
-- __sub:减法运算符
function Vector.__sub(a, b)
return Vector.new(a.x - b.x, a.y - b.y)
end
-- __mul:乘法运算符
function Vector.__mul(a, scalar)
if type(a) == "number" then
a, scalar = scalar, a
end
return Vector.new(a.x * scalar, a.y * scalar)
end
-- __tostring:转字符串
function Vector.__tostring(v)
return string.format("(%d, %d)", v.x, v.y)
end
local v1 = Vector.new(3, 4)
local v2 = Vector.new(1, 2)
print(v1 + v2) -- (4, 6)
print(v1 - v2) -- (2, 2)
print(v1 * 2) -- (6, 8)
print(2 * v1) -- (6, 8)
-- __eq:等于
-- __lt:小于
-- __le:小于等于
local Set = {}
Set.__index = Set
function Set.new(list)
local set = {}
for _, v in ipairs(list) do
set[v] = true
end
setmetatable(set, Set)
return set
end
function Set.__eq(a, b)
for k in pairs(a) do
if not b[k] then return false end
end
for k in pairs(b) do
if not a[k] then return false end
end
return true
end
local s1 = Set.new({1, 2, 3})
local s2 = Set.new({3, 2, 1})
local s3 = Set.new({1, 2})
print(s1 == s2) -- true
print(s1 == s3) -- false
-- __call:让表可以像函数一样调用
local Calculator = {}
function Calculator.new()
local calc = {result = 0}
setmetatable(calc, {
__call = function(self, operation, value)
if operation == "add" then
self.result = self.result + value
elseif operation == "sub" then
self.result = self.result - value
elseif operation == "mul" then
self.result = self.result * value
end
return self.result
end
})
return calc
end
local calc = Calculator.new()
print(calc("add", 10)) -- 10
print(calc("mul", 5)) -- 50
print(calc("sub", 20)) -- 30
-- __concat:连接运算符
local List = {}
List.__index = List
function List.new(...)
local list = {...}
setmetatable(list, List)
return list
end
function List.__concat(a, b)
local result = List.new()
for _, v in ipairs(a) do
table.insert(result, v)
end
for _, v in ipairs(b) do
table.insert(result, v)
end
return result
end
function List.__tostring(list)
return "[" .. table.concat(list, ", ") .. "]"
end
local l1 = List.new(1, 2, 3)
local l2 = List.new(4, 5, 6)
print(l1 .. l2) -- [1, 2, 3, 4, 5, 6]
-- __len:长度运算符
local CountedTable = {}
function CountedTable.new()
local t = {_count = 0}
setmetatable(t, {
__index = CountedTable,
__newindex = function(table, key, value)
if key ~= "_count" then
if rawget(table, key) == nil and value ~= nil then
rawset(table, "_count", table._count + 1)
elseif rawget(table, key) ~= nil and value == nil then
rawset(table, "_count", table._count - 1)
end
end
rawset(table, key, value)
end,
__len = function(table)
return table._count
end
})
return t
end
local t = CountedTable.new()
t.a = 1
t.b = 2
t.c = 3
print(#t) -- 3
t.b = nil
print(#t) -- 2
-- __pairs:pairs迭代器(Lua 5.4+)
-- __ipairs:ipairs迭代器(Lua 5.4+)
local OrderedTable = {}
function OrderedTable.new()
local t = {
_keys = {},
_values = {}
}
setmetatable(t, {
__index = OrderedTable,
__newindex = function(table, key, value)
if rawget(table, "_values")[key] == nil then
table.insert(table._keys, key)
end
table._values[key] = value
end,
__pairs = function(table)
local i = 0
return function()
i = i + 1
local key = table._keys[i]
if key then
return key, table._values[key]
end
end
end
})
return t
end
local ot = OrderedTable.new()
ot.c = 3
ot.a = 1
ot.b = 2
for k, v in pairs(ot) do
print(k, v) -- 按插入顺序输出
end
-- __mode:弱引用表
-- "k":弱键
-- "v":弱值
-- "kv":弱键和弱值
-- 弱键表
local weakKeys = setmetatable({}, {__mode = "k"})
local key = {}
weakKeys[key] = "value"
key = nil
collectgarbage()
print(next(weakKeys)) -- nil (键已被回收)
-- 弱值表
local weakValues = setmetatable({}, {__mode = "v"})
local value = {data = "something"}
weakValues["key"] = value
value = nil
collectgarbage()
print(weakValues["key"]) -- nil (值已被回收)
-- 对象缓存实现
local Cache = {}
Cache.__index = Cache
function Cache.new()
return setmetatable({
store = setmetatable({}, {__mode = "v"}),
hits = 0,
misses = 0
}, Cache)
end
function Cache:get(key)
local value = self.store[key]
if value then
self.hits = self.hits + 1
return value
else
self.misses = self.misses + 1
return nil
end
end
function Cache:set(key, value)
self.store[key] = value
end
function Cache:stats()
return string.format("Hits: %d, Misses: %d", self.hits, self.misses)
end
-- __gc:垃圾回收时调用(Lua 5.4+)
local FileHandle = {}
FileHandle.__index = FileHandle
function FileHandle.new(filename, mode)
local self = setmetatable({
file = io.open(filename, mode) or error("无法打开文件"),
filename = filename
}, FileHandle)
-- 设置__gc元方法
setmetatable(self, {
__index = FileHandle,
__gc = function(obj)
if obj.file then
obj.file:close()
print("文件已关闭:", obj.filename)
obj.file = nil
end
end
})
return self
end
function FileHandle:write(data)
self.file:write(data)
end
function FileHandle:read(...)
return self.file:read(...)
end
function FileHandle:close()
if self.file then
self.file:close()
self.file = nil
end
end
-- 使用
local fh = FileHandle.new("test.txt", "w")
fh:write("Hello, World!")
fh = nil -- 文件会自动关闭
collectgarbage() -- 触发垃圾回收
-- 1. 延迟计算表
local LazyTable = {}
function LazyTable.new()
local t = {}
setmetatable(t, {
__index = function(table, key)
local value = computeValue(key) -- 计算值
rawset(table, key, value) -- 缓存结果
return value
end
})
return t
end
-- 2. 只读表(深度保护)
function deepReadOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function()
error("表是只读的")
end,
__metatable = "受保护的元表"
}
for k, v in pairs(t) do
if type(v) == "table" then
proxy[k] = deepReadOnly(v)
else
proxy[k] = v
end
end
setmetatable(proxy, mt)
return proxy
end
-- 3. 访问日志表
function LoggedTable(t, logger)
local proxy = {}
local mt = {
__index = function(table, key)
local value = rawget(t, key)
logger("get", key, value)
return value
end,
__newindex = function(table, key, value)
logger("set", key, value)
rawset(t, key, value)
end,
__pairs = function()
return pairs(t)
end
}
setmetatable(proxy, mt)
return proxy
end
local logged = LoggedTable({a = 1, b = 2}, function(op, key, value)
print(string.format("%s %s = %s", op, key, tostring(value)))
end)
print(logged.a) -- get a = 1
logged.b = 3 -- set b = 3
-- 4. 类型检查表
function TypedTable(schema)
local t = {}
local mt = {
__newindex = function(table, key, value)
local expectedType = schema[key]
if expectedType and type(value) ~= expectedType then
error(string.format("键 '%s' 应该是 %s 类型", key, expectedType))
end
rawset(table, key, value)
end
}
setmetatable(t, mt)
return t
end
local user = TypedTable({
name = "string",
age = "number",
active = "boolean"
})
user.name = "张三"
user.age = 25
-- user.age = "25" -- 错误:键 'age' 应该是 number 类型
-- 使用元表实现类和继承
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:speak()
print(self.name .. " 发出声音")
end
-- 继承
local Dog = setmetatable({}, {__index = Animal})
Dog.__index = Dog
function Dog.new(name, breed)
local self = setmetatable(Animal.new(name), Dog)
self.breed = breed
return self
end
function Dog:speak()
print(self.name .. " 汪汪叫")
end
function Dog:fetch()
print(self.name .. " 去捡球")
end
local dog = Dog.new("旺财", "金毛")
dog:speak() -- 旺财 汪汪叫
dog:fetch() -- 旺财 去捡球