在 Lua 中,元表(Metatable) 是一种强大的机制,用于改变表的默认行为。通过元表,你可以实现自定义的操作,例如运算符重载、访问控制、元方法等。
1. 元表的基本概念
- 每个 Lua 表可以附加一个元表。
- 元表是一个普通的 Lua 表,其中包含特定的元方法(以键值对的形式存储)。
- 元方法是 Lua 定义的一些特殊字段,用于修改表的行为,比如加法、减法等操作符。
- 可以通过
setmetatable
和getmetatable
函数来设置和获取元表。
1.1 设置元表
setmetatable(table, metatable)
:为表设置元表。getmetatable(table)
:获取表的元表。
示例:设置元表
local tbl = {} -- 定义一个表
local mt = {} -- 定义一个元表
setmetatable(tbl, mt) -- 为表 tbl 设置元表 mt
print(getmetatable(tbl) == mt) -- 输出:true
1.2 元表的作用
元表允许你:
- 重定义表的行为:
- 运算符重载(
+
,-
,*
,/
等)。 - 自定义索引(
__index
)和新值赋值(__newindex
)。
- 运算符重载(
- 实现继承:通过共享行为实现类似面向对象的设计。
- 控制表的访问:如只读表或动态字段。
2. 元方法
元方法是元表中定义的一些特殊字段,用于修改表的特定行为。以下是常见的元方法及其作用。
2.1 __index
元方法
- 当访问表中不存在的键时,会调用
__index
。 __index
可以是表或函数:- 如果是表,会在该表中查找键。
- 如果是函数,会调用该函数,并将表和键作为参数传入。
示例:__index
为表
local mt = {__index = {name = "Lua", version = 5.4}}
local tbl = {}
setmetatable(tbl, mt)
print(tbl.name) -- 输出:Lua (元表中查找到的值)
print(tbl.version) -- 输出:5.4
示例:__index
为函数
local mt = {
__index = function(table, key)
return key .. " is not found"
end
}
local tbl = {}
setmetatable(tbl, mt)
print(tbl.name) -- 输出:name is not found
2.2 __newindex
元方法
- 当给表中不存在的键赋值时,会调用
__newindex
。 __newindex
可以是表或函数:- 如果是表,会将新值存储到该表中。
- 如果是函数,会调用该函数,并将表、键和值作为参数传入。
示例:__newindex
为表
local mt = {__newindex = {}}
local tbl = {}
setmetatable(tbl, mt)
tbl.name = "Lua"
print(mt.__newindex.name) -- 输出:Lua (值存储到元表中)
示例:__newindex
为函数
local mt = {
__newindex = function(table, key, value)
print("Attempting to set " .. key .. " to " .. value)
end
}
local tbl = {}
setmetatable(tbl, mt)
tbl.name = "Lua" -- 输出:Attempting to set name to Lua
2.3 运算符相关元方法
元表允许重载运算符。以下是常见的运算符元方法:
元方法 | 描述 | 示例运算符 |
---|---|---|
__add |
加法重载 | a + b |
__sub |
减法重载 | a - b |
__mul |
乘法重载 | a * b |
__div |
除法重载 | a / b |
__mod |
取模重载 | a % b |
__pow |
幂运算重载 | a ^ b |
__eq |
等于比较 | a == b |
__lt |
小于比较 | a < b |
__le |
小于等于比较 | a <= b |
__concat |
连接操作符重载 | a .. b |
__unm |
一元负号运算符重载 | -a |
示例:加法重载
local mt = {
__add = function(a, b)
return {value = a.value + b.value}
end
}
local a = {value = 10}
local b = {value = 20}
setmetatable(a, mt)
setmetatable(b, mt)
local c = a + b
print(c.value) -- 输出:30
示例:比较操作符
local mt = {
__eq = function(a, b)
return a.value == b.value
end,
__lt = function(a, b)
return a.value < b.value
end,
__le = function(a, b)
return a.value <= b.value
end
}
local x = {value = 10}
local y = {value = 20}
setmetatable(x, mt)
setmetatable(y, mt)
print(x == y) -- 输出:false
print(x < y) -- 输出:true
print(x <= y) -- 输出:true
2.4 表的元方法
元方法 | 描述 |
---|---|
__tostring |
自定义 tostring 函数的行为 |
__call |
使表可以像函数一样调用 |
__len |
自定义 # 操作符(表的长度) |
__pairs |
自定义 pairs 函数的行为 |
示例:__tostring
local mt = {
__tostring = function(table)
return "This is a custom table"
end
}
local tbl = {}
setmetatable(tbl, mt)
print(tbl) -- 输出:This is a custom table
示例:__call
local mt = {
__call = function(table, name)
print("Hello, " .. name)
end
}
local tbl = {}
setmetatable(tbl, mt)
tbl("Lua") -- 输出:Hello, Lua
示例:__len
local mt = {
__len = function(table)
return 42
end
}
local tbl = {}
setmetatable(tbl, mt)
print(#tbl) -- 输出:42
示例:__pairs
local mt = {
__pairs = function(table)
return function(_, key)
return next({x = 1, y = 2, z = 3}, key)
end
end
}
local tbl = {}
setmetatable(tbl, mt)
for k, v in pairs(tbl) do
print(k, v) -- 输出:x 1, y 2, z 3
end
3. 实现面向对象编程
元表在 Lua 中常用于实现面向对象编程。
3.1 简单类的实现
示例:创建类
-- 定义类
local Person = {}
Person.__index = Person
function Person:new(name, age)
local obj = {name = name, age = age}
setmetatable(obj, self)
return obj
end
function Person:greet()
print("Hello, my name is " .. self.name)
end
-- 创建实例
local p = Person:new("Alice", 25)
p:greet() -- 输出:Hello, my name is Alice
3.2 类的继承
示例:继承实现
-- 定义父类
local Animal = {}
Animal.__index = Animal
function Animal:new(name)
local obj = {name = name}
setmetatable(obj, self)
return obj
end
function Animal:speak()
print(self.name .. " makes a sound")
end
-- 定义子类
local Dog = setmetatable({}, {__index = Animal})
function Dog:speak()
print(self.name .. " barks")
end
-- 创建实例
local dog = Dog:new("Buddy")
dog:speak() -- 输出:Buddy barks
4. 小结
元方法 | 描述 | 示例 |
---|---|---|
__index |
自定义表中不存在的键的访问行为 | 动态字段、继承 |
__newindex |
自定义表中不存在的键的赋值行为 | 只读表、代理字段 |
运算符元方法 | 重载运算符 | __add , __eq ,
|