找回密码
 立即注册
查看: 146|回复: 1

Lua 元表(Metatable)

[复制链接]
发表于 2023-2-25 08:52 | 显示全部楼层 |阅读模式
元表

元表可以修改一个值在面对一个未知操作时的行为。
a = {}
b = {}
print(a + b)    --error上面例子中,a和b都是一个table,当Lua计算表达式a+b时,会抛出一个错误,说尝试对table执行算术运算


这没有问题,因为lua中没有定义table如何相加。既然元表可以修改一个值在面对一个未知操作时的行为,我们看一下怎么实现两个table相加的功能。
mt = {}
mt.__add = function (a,b)
    return "try to add table"
end

setmetatable(a,mt)  --设置a的元表为mt先定义一个元表mt,并且实现 __add 元方法,然后将mt设置为a的元表,这时执行print函数后调用了 mt.__add方法
print(a + b)    --try to add table当Lua试图将a、b两个表相加时,它会先检查两者之一是否有元表(metatable)且该元表中是否有__add字段,如果找到了该字段,就调用这个元方法(metamethod)。如果a和b都有元表,会执行a的元表,因为a在b之前。
总结一下Lua查找元方法的步骤:

  • 如果第一个值有元表且元表中存在所需的元方法,那么Lua语言就使用这个元方法,不考虑第二个值
  • 如果第二个值有元表且元表中存在所需的元方法,Lua语言就使用这个元方法
  • 否则,Lua语言就抛出异常
♂ 在元表中,我们称它的键为事件(Event),它的值为元方法(metamethod)
Lua语言中的每一个值都可以有元表,只有 table和userdata可以有各自独立的元表,其他类型则共享其类型所属的同一个元表。
获取元表

获取元表使用getmetatable()方法
t = {}
print(getmetatable(t))      --> nil设置元表

可以使用函数setmetatable来设置或修改任意表的元表
t1 = {}
setmetatable(t,t1)
print(getmetatable(t) == t1)        --> true我们只能为表设置元表,如果要为其他类型的值设置元表,则必须通过C代码或调试库完成。字符串标准库为所有的字符串都设罝了同一个元表,而其他类型在默认情况中都没有元表:
print(getmetatable("hello world"))   --> table: 0000022E8BA91B40
print(getmetatable(123))             --> nil
print(getmetatable(print))           --> nil运算符相关的预定义元方法

每种算术运算符都有一个对应的元方法,除了 __add 外,还有下面这些键值定义:


Table相关的元方法

__index元方法

当我们访问表中一个不存在的字段时,得到的结果会是nil,这看似正确。但实际上,访问的时候解释器会查找一个名为 __index 的元方法,如果没有这个元方法,返回nil,否则,由这个元方法来提供最终结果。
mt = {x = 5}            --定义一个元表,里面拥有一个字段x

w = {}

w = setmetatable(w,mt)   --将mt设置为w的元表

mt.__index = mt          --设置元表的__index值

print(w.x)              -- 5
print(w.y)              -- nil

--将mt的__index元方法设置为函数
mt.__index  = function(_,key)
    return mt[key]
end

print(w.x) --w中没有x字段,所以调用函数 __index,传入的参数为function(w,x),所以得到的值为mt["x"] = 5
           --也就是以表和键为参数调用该函数,并返回该函数的返回值
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:

  • 在表中查找,如果找到,返回该元素,找不到则继续
  • 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
  • 判断元表有没有 index 方法,如果 index 方法为 nil,则返回 nil;如果 index 方法是一个表,则重复 1、2、3;如果 index 方法是一个函数,Lua会以表和键为参数调用该函数,并返回该函数的返回值。
如果我们希望在访问一个表时不调用__index元方法,那么可以使用函数rawget,它在不考虑元表的情况下对表进行简单的访问,定义为:
rawget (table, index)
在不触发任何元方法的情况下 获取 table[index] 的值。 table 必须是一张表; index 可以是任何值。
接着上面的代码,添加一些内容
print(rawget(w,"x"))    -- 忽略元表中的值,x在w表中不存在,所以输出为 nil
w.x = "hhh"
print(rawget(w,"x"))    -- 输出 hhh__newindex元方法

当查询表中的一个元素时,如果不存在会从它的__index元方法中继续查找;当对表中不存在的元素进行赋值时,如果这个表有__newindex元方法,则分两种情况:
__newindex指向table
给这个table创建一个新元素
mt = {x = 5,

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-2-25 09:01 | 显示全部楼层
怎么遍历这个只读表
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-6-20 23:53 , Processed in 0.090545 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表