元表的概念

  • 任何表变量都可以作为另一个表变量的元表
  • 任何表变量都可以有自己的元表
  • 当我们在子表中进行一些特定的操作时,会执行元表中的内容

设置和获取元表

--设置元表
--setmetatable(子表,元表)
meta = {}
t1 = {}
setmetatable(t1, meta)
--获取元表
print(getmetatable(t1))  -- table: 00E09968

元表中的元方法

__tostring

当子表要被当作字符串使用时,会调用它的元表的__tostring方法

--__tostring 当子表要被当作字符串使用时,会调用它的元表的__tostring方法
meta2 = {
    __tostring = function()
        return "小明"
    end
}
t2 = {}
setmetatable(t2, meta2)
print(t2)  -- 小明

meta3 = {
    __tostring = function(t)  
        return t.name
    end
}
t3 ={
    name = "小芳"
}
setmetatable(t3, meta3)
print(t3)  -- 小芳

__call

当子表被当作一个函数来使用时,会默认调用元表的__call方法

meta4 = {
    __tostring = function(t)
        return t.name
    end,
    -- __call被调用时,第一个参数默认是子表,从第二个参数开始才是调用时传入的参数
    __call = function(t, a)  
        print(t) -- 之类输出小红的原因是因为将字表作为字符串输出了,且上面定义了tostring的元方法
        print(a)
    end
}
t4 ={
    name = "小红"
}
setmetatable(t4, meta4)
t4(666)  -- 小红 666

运算符重载

  • __add 加法的运算符重载,当对两个表使用+连接时,会调用__add元方法
  • __sub 减法
  • __mul 乘法
  • __div 除法
  • __mod 取余
  • ___pow 幂运算
  • __eq 等于等于
  • __lt 小于
  • __le 小于等于
  • __concat 相连

注意:在两个表的元表不同时,调用==、<=这些比较运算符时会出问题。需要让两个表的元表设置一致才能正确调用。

--__add 加法的运算符重载。当对两个表使用+连接时,会调用__add元方法
--__sub 减法-
--__mul 乘法*
--__div 除法/
--__mod 取余%
--__pow 幂运算^
--__eq 等于等于==
--__lt 小于<
--__le 小于等于<=
--__concat 相连..
meta5 = {
    __add = function(t1, t2)
        return t1.index + t2.index
    end,
    __sub = function(t1, t2)
        return t1.index - t2.index
    end,
    __mul = function(t1, t2)
        return t1.index * t2.index
    end,
    __div = function(t1, t2)
        return t1.index / t2.index
    end,
    __mod = function(t1, t2)
        return t1.index % t2.index
    end,
    __pow = function(t1, t2)
        return t1.index ^ t2.index
    end,
    __eq = function(t1, t2)
        return t1.index == t2.index
    end,
    __lt = function(t1, t2)
        return t1.index < t2.index
    end,
    __le = function(t1, t2)
        return t1.index <= t2.index
    end,
    __concat = function(t1, t2)
        return t1.index .. " " .. t2.index
    end
}
t5 = {index = 5}
t6 = {index = 6}
setmetatable(t5, meta5)  -- 注意,大部分运算符只需要其中一个表设置了元表即可

print(t5 + t6)  -- 11
print(t5 - t6)  -- -1
print(t5 * t6)  -- 30
print(t5 / t6)  -- 0.8333333
print(t5 % t6)  -- 5
print(t5 ^ t6)  -- 15625
print(t5 .. t6)  -- 5 6
--在两个表的元表不同时,调用==、<=这些比较运算符时会出问题
--print(t5 < t6)  -- error

--想要正确使用运算符重载的元方法,需要让两个表的元表设置一致
setmetatable(t6, meta5)
print(t5 == t6)  -- false
print(t5 < t6)  -- true
print(t5 <= t6)  -- true
--元方法中并没有>和>=,但是Lua会自动调用<和<=取非的结果返回
print(t5 > t6)  -- false
print(t5 >= t6)  -- false

__index和__newindex

__index

  • 当子表中找不到某一个属性时,会到元表中__index指向的表中去找索引
  • __index可以设置为表自身,这样就可以直接访问元表中的元素而不用再定义一个__index了
  • __index还可以层层嵌套。当在元表中的__index找不到目标元素时,会继续向上访问元表的元表的__index
--__index 当子表中找不到某一个属性时,会到元表中__index指向的表中去找索引
meta7 = {}
meta7.__index = {age = 2}  -- __index尽量在表外进行赋值。在表内赋值有时候会有问题
t7 = {}
setmetatable(t7, meta7)
print(t7.age)  -- 2

--__index可以设置为表自身,这样就可以直接访问元表中的元素而不用再定义一个__index了
meta8 = {age = 3}
meta8.__index = meta8
t8 = {}
setmetatable(t8, meta8)
print(t8.age)  -- 3

--__index还可以层层嵌套。当在元表中的__index找不到目标元素时,会继续向上访问元表的元表的__index
meta9Father = {age = 4}
meta9Father.__index = meta9Father
meta9 = {}
meta9.__index = meta9
t9 = {}
setmetatable(t9, meta9)
setmetatable(meta9, meta9Father)
print(t9.age)  -- 4

__newindex

--__newindex 当赋值时,如果赋值一个不存在的索引,那么会把这个值赋值到newindex所指的表中,不会修改自己的表
meta10 = {}
meta10.__newindex = {}
t10 = {}
setmetatable(t10, meta10)
t10.age = 5
print(t10.age)  -- nil
print(meta10.__newindex.age)  -- 5

rawget和rawset

rawget

避过__index,只在自己本表中查找目标元素

--rawget 避过__index,只在自己本表中查找目标元素
meta11 = {}
meta11.__index = {age = 6}
t11 = {}
setmetatable(t11, meta11)
print(t11.age)  -- 6
print(rawget(t11, age))  -- nil

rawset

避过__newindex,只在自己本表中进行赋值

--rawset 避过__newindex,只在自己本表中进行赋值
meta12 = {}
meta12.__newindex = {}
t12 = {}
setmetatable(t12, meta12)
t12.age = 7
rawset(t12, "height", 180)
print(t12.age)  -- nil
print(t12.height)  -- 180

参考资料

https://www.bilibili.com/video/BV1iA411n7oQ?p=17

https://www.lengyueling.cn/archives/67