Q:为什么要使用元表?
A:在Lua中,常常会需要表与表之间的操作。元表中提供了一些元方法,通过自定义元方法可以实现想要的功能,相当于面向对象中给你一系列方法让你重载。
使用下列代码设置元表:
meta={}
table={}
setmetatable(table,meta) -- 第一个元素为子表,第二个元素为元表
通常在元表中操作分为三步:
元方法索引一般以"__"(两个下划线,我想这是因为命名私有静态变量常常以一个下划线作为开头)作为开头,例如下面的例子:
__tostring
meta = {}
table={}
setmetatable(table, meta)
print(table)
输出:
table: 00ABA2A8
meta = {
__tostring = function () <--注意两个下划线
return "123"
end
}
table={}
setmetatable(table, meta)
print(table)
输出:
123
上例相当于使用元方法重载print函数
meta = {
__tostring = function (t)
return t.name
end
}
table={name = "233"}
setmetatable(table, meta)
print(table)
输出:
233
在上例中,即使我们未指定元方法的入参,但是因为子表和元表的关联,元方法会自动地将子表作为参数传入元方法。
__call
让我们再定义一个__call
元方法
meta = {
__tostring = function ()
return "456"
end,
__call = function(a)
print(a.name)
print(a)
print("call")
end
}
table={name = "123"}
setmetatable(table, meta)
table(2) --无论table(x)中给出的x为几,结果都是一样的
输出:
123
456
call
定义了__call
元方法后,我们使用table(index)
,发现__call
元方法打印了123,456和call,123是子表的元素,而456是__tostring
的返回结果。这意味着__call
方法调用了子表作为入参a。并且像我们上面的打印样例一样,print(a)
方法又同时会调用__tostring
的元方法。而table(2)
中的参数2很明显被无视了。
现在我们可以确定一个基本规则:当使用__tostring
和__call
元方法时,我们定义的第一个参数一定是子表table本身,只有定义更多参数才能接收其它入参:
meta = {
__tostring = function ()
return "456"
end,
__call = function(a,b)
print(a)
print(b)
print("call")
end
}
table={name = "123"}
setmetatable(table, meta)
table(2)
输出:
456
2
call
__index
meta = {
}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil
我们想要找到表中的name索引,当然是没有的,输出结果是nil
那能不能让元表拥有这个索引呢?答案是不行,因为找的还是table1:
meta = {name =1}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil
现在我们定义一个元方法 __index
,它会指向一个其他的查询表
meta = { name = 2 }
meta.__index = meta
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
2
整个查找的流程其实是:
__index
元方法__index
元方法指向的表但是还有种情况,建议不要把__index
元方法在元表内部定义:
meta = {
name = 2,
__index = meta,
}
table1 = { age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil --不明觉厉
还可以套娃
meta = {
}
metaFather = {
name =4,
}
table1 ={age =1}
meta.__index = meta --让元表的index指向meta,子表找不到就去meta里找
metaFather.__index =metaFather --让元表的index指向metaFather,子表找不到就去metaFather里找
setmetatable(meta,metaFather)
setmetatable(table1, meta)
print(table1.name)
输出:i
4 --允许套娃
__newindex
看4个例子:
meta = {}
table1 ={}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)
输出:
1
meta = {}
table1 ={age = 1}
meta.__newindex = {}
setmetatable(table1, meta)
print(table1.age)
输出:
1
meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age = 1
print(table1.age)
输出:
nil
meta = {}
table1 ={}
meta.__newindex = {}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)
输出:
1
有没有很诡异?其实很好理解,这是由于__newindex
元方法,它的作用其实是将子表新加入的元素加入到__newindex
所指向的表而不修改子表(当然__newindex
也可以套娃):
meta = {}
table1 ={}
meta.__newindex = {}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
print(meta.__newindex.age)
输出:
nil
1
meta = {
__sub = function (t1,t2)
return t1.age - t2.age
end
}
table1={age=1}
table2={age=2}
setmetatable(table1, meta) --无论把元表设置给table1还是table2,结果都一样
print(table1 - table2)
输出:
-1
我们发现使用运算符元方法的时候,第一个参数也不默认是绑定的子表了。而是根据运算符的左右变量依次给元方法赋值。而且无论元表设置给减数还是被减数,最终结果都是不变的。运算符元方法较多,这里就不详细列举了,从菜鸟教程上直接抄了个表格:
元方法 | 描述 |
---|---|
__add | 对应的运算符 ‘+’ |
__sub | 对应的运算符 ‘-’ |
__mul | 对应的运算符 ‘*’ |
__div | 对应的运算符 ‘/’ |
__mod | 对应的运算符 ‘%’ |
__unm | 对应的运算符 ‘-’ |
__concat | 对应的运算符 ‘..’ |
__pow | 对应的运算符 ‘^’ |
__eq | 对应的运算符 ‘==’ |
__lt | 对应的运算符 ‘<’ |
__le | 对应的运算符 ‘<=’ |