UH2S1L11——元表
元表
任何表变量都可以作为另一个表变量的元表,任何表变量都可以有自己的元表
当我们对有元表的表中进行一些特定操作时,会执行其元表中的内容, 即元表内存储的特定方法将改变其对应表的特定方法
在Lua的 table
中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table
进行操作(比如相加)。
因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。
当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add
的字段,
若找到,则调用对应的值。 __add
等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法"。
———— Lua 元表(Metatable) | 菜鸟教程 (runoob.com)
本章代码关键字
1 2 3 4 5 6 7 8 9 setmetatable () getmetatable () __tostring __call __index __newindex rawget () rawset ()
设置元表
使用setmetatable(table, metatable)
给指定表设置元表。第一个参数是要设置元表的表,第二个参数是要作为元表的表
1 2 3 local meta = {}local myTable = {}setmetatable (myTable, meta)
不过用空表设置为一个表的元表是没有作用的
特定操作 __tostring
当把非字符串或者数字的变量传入print
函数时,会自动将其转为字符串再输出,而没有元表的表直接转字符串会是一些列表号
1 2 3 4 local meta = {}local myTable = {}setmetatable (myTable, meta)print (myTable)
而我们为表设置元表时,可以在表的元表里实现转字符串的方法,这个方法名就是__tostring
,使用它可以修改转字符串的逻辑
1 2 3 4 5 6 7 8 9 print ("**********特定操作-__tostring************" )local meta2 = { __tostring = function () return "唐老狮" end } local myTable2 = {}setmetatable (myTable2, meta2)print (myTable2)
1 2 **********特定操作-__tostring ************ 唐老狮
但是如果想让表转字符串的方法能够调用表内的变量的话,需要我们为__tostring
函数添加一个参数,
并默认该参数传入了调用方法的表自己,再调用传入的表中的变量
1 2 3 4 5 6 7 8 9 10 11 print ("**********特定操作-__tostring************" )local meta2 = { __tostring = function (t) return t.name end } local myTable2 = { name = "唐老狮2" } setmetatable (myTable2, meta2)print (myTable2)
1 2 **********特定操作-__tostring ************ 唐老狮2
特定操作 __call
在表对应的元表实现__call
函数,可以让表像函数那样调用(不设置元表不实现该方法强行让表像函数那样调用会报错)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 print ("**********特定操作-__call************" )local meta3 = { __tostring = function (t) return t.name end , __call = function () print ("call!" ) end } local myTable3 = { name = "唐老狮" } setmetatable (myTable3, meta3)myTable3()
1 2 **********特定操作-__call ************ call!
如果我们想为函数添加参数,直接在元表的__call
参数列表里添加一个参数是不可行的,这个参数默认会传入元表对应的表自己
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 print ("**********特定操作-__call************" )local meta3 = { __tostring = function (t) return t.name end , __call = function (a) print (a) print ("call!" ) end } local myTable3 = { name = "唐老狮" } setmetatable (myTable3, meta3)myTable3(1 )
1 2 3 **********特定操作-__call ************ 唐老狮 call!
因此,我们需要在元表的__call
参数列表里添加两个参数及以上,第一个参数默认会传入元表对应的表自己,第二个参数起才是外部可用的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 print ("**********特定操作-__call************" )local meta3 = { __tostring = function (t) return t.name end , __call = function (self, a) print (a) print ("call!" ) end } local myTable3 = { name = "唐老狮" } setmetatable (myTable3, meta3)myTable3(1 )
1 2 3 **********特定操作-__call ************ 1 call!
特定操作-运算符重载
一般情况下,我们无法让两个表进行相加等运算,但是在其元表里我们可以实现运算符重载,实现相加逻辑,让两个表可以相加
1 2 3 4 5 6 7 8 9 10 print ("**********特定操作-运算符重载************" )local meta4 = { __add = function (t1, t2) return 5 end } local myTable4 = {}setmetatable (myTable4, meta4)local myTable5 = {}print (myTable4 + myTable5)
1 2 **********特定操作-运算符重载************ 5
__add
方法可以需要两个参数,默认传入加号两边的内容
1 2 3 4 5 6 7 8 9 10 print ("**********特定操作-运算符重载************" )local meta4 = { __add = function (t1, t2) return t1.age + t2.age end } local myTable4 = {age = 1 }setmetatable (myTable4, meta4)local myTable5 = {age = 2 }print (myTable4 + myTable5)
1 2 **********特定操作-运算符重载************ 3
其他运算符的重载与上述例子差不多
算数运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 local meta4 = { __add = function (t1, t2) return t1.age + t2.age end , __sub = function (t1, t2) return t1.age - t2.age end , __mul = function (t1, t2) return t1.age * t2.age end , __div = function (t1, t2) return t1.age / t2.age end , __mod = function (t1, t2) return t1.age % t2.age end , __pow = function (t1, t2) return t1.age ^ t2.age end } local myTable4 = {age = 1 , num = 2 }setmetatable (myTable4, meta4)local myTable5 = {age = 2 , num = 5 }print (myTable4 + myTable5)print (myTable4 - myTable5)print (myTable4 * myTable5)print (myTable4 / myTable5)print (myTable4 % myTable5)print (myTable4 ^ myTable5)
当两个表参与算数运算符计算 时,只需要有一个表的元表存在运算符重载即可进行计算(只要传入的两个表能够满足运算符重载的逻辑)
运算符左边的表作为第一个参数传入,运算符左边的表作为第二个参数传入(条件运算符不行)
1 2 3 4 5 6 7 8 9 10 11 12 local meta4 = { __pow = function (t1, t2) return t1.age ^ t2.age end } local myTable4 = {age = 1 , num = 2 }setmetatable (myTable4, meta4)local myTable5 = {age = 2 , num = 5 }print (myTable4 ^ myTable5)print (myTable5 ^ myTable4)
如果运算时两个表的元表都存在相同算数运算符的重载且逻辑不同,则以运算符左边表的元表的运算符重载为准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 local meta4 = { __pow = function (t1, t2) return t1.age ^ t2.age end } local meta5 = { __pow = function (t1, t2) return t1.num ^ t2.num end } local myTable4 = {age = 1 , num = 2 }setmetatable (myTable4, meta4)local myTable5 = {age = 2 , num = 5 }setmetatable (myTable5, meta5)print (myTable4 ^ myTable5)print (myTable5 ^ myTable4)
条件运算符
官方没有提供~=
、<
、<=
的重载,需要自己取反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 local meta4 = { __eq = function (t1, t2) return t1.age == t2.age end , __lt = function (t1, t2) return t1.age < t2.age end , __le = function (t1, t2) return t1.age <= t2.age end , }
与算数运算符不同,如果要用条件运算符来比较两个表,则两个表的元表必须要一致!才能准确的调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 local meta4 = { __eq = function (t1, t2) return t1.age == t2.age end } local myTable4 = {age = 1 , num = 2 }setmetatable (myTable4, meta4)local myTable5 = {age = 1 , num = 5 }print (myTable4 == myTable5)setmetatable (myTable5, meta4)print (myTable4 == myTable5)
从这里我们也可以知道为什么不需要设置>
、>=
的重载,因为这两个就是调换了传入<
、<=
的重载的参数顺序而已
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 local meta4 = { __eq = function (t1, t2) return t1.age == t2.age end , __lt = function (t1, t2) return t1.age < t2.age end , __le = function (t1, t2) return t1.age <= t2.age end , } local myTable4 = {age = 1 , num = 2 }setmetatable (myTable4, meta4)local myTable5 = {age = 2 , num = 5 }setmetatable (myTable5, meta4)print (myTable4 > myTable5)print (myTable4 < myTable5)print (myTable5 > myTable4)print (myTable5 < myTable4)print (myTable4 >= myTable5)print (myTable4 <= myTable5)print (myTable5 >= myTable4)print (myTable5 <= myTable4)
1 2 3 4 5 6 7 8 false true true false false true true false
拼接运算符
1 2 3 4 5 6 7 8 9 10 local meta4 = { __concat = function (t1, t2) return t1.age .. t2.age end } local myTable4 = {age = 1 , num = 2 }setmetatable (myTable4, meta4)local myTable5 = {age = 2 , num = 5 }print (myTable4 .. myTable5)
特定操作 __index
和 __newindex
__index
__index
是当元表对应的表找不到某一个索引时,会回到元表里,__index
指定的表去找索引 ( __index
****最好在外部赋值!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local meta6 = { age = 6 } local myTable6 = {}setmetatable (myTable6, meta6)print (myTable6.age)local meta6 = { age = 6 } meta6.__index = meta6 local myTable6 = {}setmetatable (myTable6, meta6)print (myTable6.age)local meta6 = {}meta6.__index = {age = 6 } local myTable6 = {}setmetatable (myTable6, meta6)print (myTable6.age)
注意!不建议将 __index
写在元表内部,尤其是将 __index
赋值为元表自己时, __index
应当在外面进行赋值,否则会出现值错误的情况,
可能的原因是在表内为__index
赋值时,元表自己还没有赋值完毕导致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 local meta6 = { age = 1 , __index = meta6 } local myTable6 = {}setmetatable (myTable6, meta6)print (myTable6.age)local meta6 = { age = 6 } local meta6 = { age = 1 , __index = meta6 } local myTable6 = {}setmetatable (myTable6, meta6)print (myTable6.age)
__index
是可以赋值元表自己的元表 ,因此当外部获取表的某个索引的值而表不存在那个索引时,外部可以找到表的元表的元表获取值
1 2 3 4 5 6 7 8 9 10 11 12 13 local meta6Father = { age = 1 } meta6Father.__index = meta6Father local meta6 = {}meta6.__index = meta6Father setmetatable (meta6, meta6Father)local myTable6 = {}setmetatable (myTable6, meta6)print (myTable6.age)
__newindex
__newindex
当为表的某个索引赋值时,如果表不存在某个索引,则会将值复制到表的元表的__newindex
指向的表中
1 2 3 4 5 6 local meta7 = {}meta7.__newindex = {} local myTable7 = {}setmetatable (myTable7, meta7)myTable7.age = 1 print (myTable7.age)
获取元表
1 2 3 4 5 6 local meta = { age = 1 , } local myTable = {}setmetatable (myTable, meta)print (getmetatable (myTable).age)
无视元表__index查找
1 2 3 4 5 6 7 8 9 10 local meta = { age = 1 , } meta.__index = meta local myTable = {}local myTable1 = { age = 1 }setmetatable (myTable, meta)print (myTable.age)print (rawget (myTable1, "age" ))print (rawget (myTable, "age" ))
无视元表__newIndex赋值
1 2 3 4 5 6 7 8 local meta7 = {}meta7.__newindex = {} local myTable7 = {}setmetatable (myTable7, meta7)myTable7.age = 1 print (myTable7.age)rawset (myTable7, "age" , 2 )print (myTable7.age)