Lua 元表:metatable 与 __index
Lua 的表本质上是键值对的集合。通过元表,可以改变表的默认行为,实现类似于面向对象编程中的操作符重载、继承等特性。核心在于理解 metatable 的设置与 __index 的查找逻辑。
1. 设置与读取元表
元表本质上也是一个普通的表。通过特定的函数将一个表设置为另一个表的元表,从而改变后者的行为。
-
使用
setmetatable(table, metatable)函数 将第二个参数设置的表作为第一个参数的元表。此操作会修改原始表,并返回该表。local myTable = {} local myMetatable = {} setmetatable(myTable, myMetatable) -
使用
getmetatable(table)函数 获取指定表的元表。如果表没有元表,返回nil。print(getmetatable(myTable)) -- 输出: table: 0x... (地址)
2. __index 元方法的基础逻辑
__index 是元表中最常用的键。当访问表中不存在的字段时,Lua 会查找元表中的 __index 键。
查找流程遵循以下逻辑:
场景一:__index 为表
如果 __index 的值是一个表,Lua 会在该表中查找对应的键。
-
定义 一个父表
parent,包含一个money字段。 -
定义 一个子表
child,设置为空表。 -
设置
child的元表为parent,并将元表的__index指向parent自身。local parent = { money = 100 } local child = {} -- 设置元表 setmetatable(child, { __index = parent }) -- 访问 child 中不存在的 money 字段 print(child.money) -- 输出: 100 -
执行 读取操作。Lua 在
child中找不到money,转而查找元表的__index(即parent表),最终返回100。
场景二:__index 为函数
如果 __index 的值是一个函数,Lua 会调用该函数,并将表和键作为参数传递进去。这允许动态计算结果。
-
定义 一个表
smartTable。 -
设置 其元表,其中
__index为一个函数。 -
在 函数内部 实现 自定义逻辑。
local smartTable = {} local mt = { __index = function(table, key) print("尝试访问不存在的键: " .. key) return "默认值" end } setmetatable(smartTable, mt) print(smartTable.name) -- 输出: -- 尝试访问不存在的键: name -- 默认值
3. 实现面向对象编程 (OOP)
利用 __index 可以轻松实现类和继承。Lua 没有内置的类概念,但通过元表机制可以模拟。
模拟类与继承
-
声明 一个表作为类
Animal。 -
定义 构造函数
new。 -
在 构造函数内部 设置 元表,将
__index指向Animal自身。这使得新对象可以访问类中定义的方法。local Animal = {} Animal.__index = Animal function Animal:new(name) local obj = { name = name } setmetatable(obj, Animal) return obj end function Animal:speak() print(self.name .. " makes a sound.") end -- 创建实例 local dog = Animal:new("Buddy") dog:speak() -- 输出: Buddy makes a sound. -
分析 执行过程:
dog:speak()被调用。- Lua 在
dog表中查找speak,未找到。 - Lua 查找
dog的元表(即Animal)。 - 元表有
__index,且指向Animal自身。 - Lua 在
Animal中找到speak并执行。
实现继承
-
创建 子类
Dog。 -
设置
Dog的元表为Animal,并将__index也指向Animal。 -
重写 父类方法。
local Dog = {} -- Dog 继承自 Animal setmetatable(Dog, { __index = Animal }) function Dog:new(name, breed) local obj = Animal:new(name) -- 调用父类构造函数 obj.breed = breed setmetatable(obj, Dog) -- 设置实例的元表为 Dog return obj end function Dog:speak() print(self.name .. " barks!") end local myDog = Dog:new("Max", "Labrador") myDog:speak() -- 输出: Max barks!
4. __newindex 与数据保护
__newindex 是另一个重要的元方法。当尝试向表中 添加 新键值对时,如果元表有 __newindex,Lua 将不执行赋值,而是调用该方法。
-
利用
__newindex实现 只读表。 -
创建 一个代理表,代理对原始表的访问。
-
拦截 所有写入操作。
function readOnly(t) local proxy = {} local mt = { __index = t, -- 读取时查源表 __newindex = function(table, key, value) error("错误: 该表是只读的,不允许修改。") end } setmetatable(proxy, mt) return proxy end local days = readOnly{"Sunday", "Monday", "Tuesday"} -- 尝试修改 days[1] = "Holiday" -- 报错: stdin:X: 错误: 该表是只读的,不允许修改。
5. 原始访问与设置
在某些情况下(例如在 __index 或 __newindex 函数内部),需要绕过元表机制,直接操作表数据。
-
使用
rawget(table, key)函数 直接获取表中键对应的值,无视__index元方法。 -
使用
rawset(table, key, value)函数 直接在表中设置键值对,无视__newindex元方法。local t = {} local mt = { __index = function() return "来自元表" end } setmetatable(t, mt) print(t.name) -- 输出: 来自元表 print(rawget(t, "name")) -- 输出: nil

暂无评论,快来抢沙发吧!