Continue from above
Article directory
meta table
Q: Why use metatables?
A: In Lua, operations between tables are often required. The metatable provides some metamethods. You can achieve the desired functions by customizing metamethods, which is equivalent to giving you a series of methods in object-oriented for you to overload.
Use the following code to set up the metatable:
meta={
}
table={
}
setmetatable(table,meta) -- 第一个元素为子表,第二个元素为元表
Usually operations in the metatable are divided into three steps:
- Operation subtable
- Check if there is a metatable
- If there is a meta table, check whether there is a corresponding meta method. If there is no meta method, return to the original processing of the corresponding operation. If there is a corresponding meta-method, execute the meta-method.
Metamethod indexes usually start with "__" (two underscores, I think this is because naming private static variables often starts with an underscore), such as the following example:
__tostring
meta = {
}
table={
}
setmetatable(table, meta)
print(table)
输出:
table: 00ABA2A8
meta = {
__tostring = function () <--注意两个下划线
return "123"
end
}
table={
}
setmetatable(table, meta)
print(table)
输出:
123
The above example is equivalent to using a meta method to overload the print function
meta = {
__tostring = function (t)
return t.name
end
}
table={
name = "233"}
setmetatable(table, meta)
print(table)
输出:
233
In the above example, even if we do not specify the input parameters of the metamethod, because of the association between the subtable and the metatable, the metamethod will automatically pass the subtable into the metamethod as a parameter.
__call
Let's define another __call
meta method
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
After defining __call
the meta method, we used it table(index)
and found that __call
the meta method printed 123, 456 and call. 123 is the element of the subtable, and 456 is the __tostring
return result. This means that __call
the method calls the subtable as parameter a. And like our printing example above, print(a)
the method also calls __tostring
the metamethod. Parameter 2 in table(2)
is obviously ignored.
Now we can determine a basic rule: when using __tostring
the sum __call
metamethod, the first parameter we define must be the subtable table itself. Only by defining more parameters can we receive other input parameters:
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
We want to find the name index in the table, but of course it does not exist. The output result is nil.
Can the metatable have this index? The answer is no, because we are still looking for table1:
meta = {
name =1}
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil
Now we define a metamethod __index
that will point to another query table
meta = {
name = 2 }
meta.__index = meta
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
2
The entire search process is actually:
- Query subtable
- Subtable query failed, query metatable to see if there is a
__index
metamethod - If so, query
__index
the table pointed to by the meta-method
However, there is another situation where it is recommended not to __index
define the metamethod inside the metatable:
meta = {
name = 2,
__index = meta,
}
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil --不明觉厉
You can also use matryoshka dolls
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
Look at 4 examples:
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
Is it weird? In fact, it is easy to understand. This is because of __newindex
the meta method. Its function is actually to add the newly added elements of the sub-table to __newindex
the pointed table without modifying the sub-table (of course __newindex
it can also be used as a nesting doll):
meta = {
}
table1 ={
}
meta.__newindex = {
}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
print(meta.__newindex.age)
输出:
nil
1
operator metamethod
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
We found that when using the operator metamethod, the first parameter is no longer the bound subtable by default. Instead, the metamethod is assigned values in sequence according to the left and right variables of the operator. And regardless of whether the metatable is set to the subtrahend or the minuend, the final result remains unchanged. There are many operator methods, so I won’t list them in detail here. I copied a table directly from the novice tutorial:
metamethod | describe |
---|---|
__add | Corresponding operator '+' |
__sub | Corresponding operator '-' |
__mul | Corresponding operator '*' |
__div | Corresponding operator '/' |
__mod | Corresponding operator '%' |
__unm | Corresponding operator '-' |
__concat | The corresponding operator '..' |
__pow | Corresponding operator '^' |
__eq | Corresponding operator '==' |
__lt | Corresponding operator '<' |
__the | Corresponding operator '<=' |
In addition to normal mathematical operations, we also need to talk about the more special meta-methods of logical judgment (the columns in bold in the above table). These meta-methods require both sides of the operator to be bound to the same Yuan table. The reason is that the meta-method does not provide a greater-than symbol. The meta-method of computing a>b is actually equivalent to the original method of computing b<a. This requires b to also be bound to the metatable. Therefore, logical operations require both sides of the operation to be bound to the same element. Table, let's look at the following example:
meta = {
__eq= function (t1,t2)
return true
end,
}
table1 = {
age = 1 }
table2 = {
name = nil }
setmetatable(table1, meta)
print(table1 == table2)
setmetatable(table2, meta)
print(table1 == table2)
输出:
false
true
We can find that there is a problem with the above logical judgment of equality. The first print is false, and the second print is true.
In the first print, only table1
the metatable is bound, but table2
not the metatable. The reason return false
is because during logical operations, both the left and right sides need to be bound to the same metatable.
And in the second print, it was printed true
, even if it was different table1
from table2
the content. Because this is our custom meta-method, it is what we return by default true
.
A few examples. Please consider the output results of the following examples:
meta = {
__eq= function (t1,t2)
if t1.age == t2.age then
return true
end
end,
}
table1 = {
age = 1 }
table2 = {
age = 2 }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
输出:
false
In the above example, we use __eq
the meta method to determine age
whether the index values are the same, which is obviously different, so it isfalse
meta = {
__eq= function (t1,t2)
if t1.name == t2.name then
return true
end
end,
}
table1 = {
age = 1 }
table2 = {
name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
输出:
true
In the above example, we use the meta method to determine name
whether the index values are the same. Table1 does not have name
an index. The default is nil
, table2 does name = nil
, so it istrue
meta = {
__eq= function (t1,t2)
if t1 == t2 then
return true
end
end,
}
table1 = {
age = 1 }
table2 = {
name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
输出:
栈溢出
In the above example, we make a judgment inside the meta-method t1==t2
, and when making the judgment, it is equivalent to calling __eq
the meta-method again, so it will loop infinitely and eventually lead to stack overflow.
Other metatable operations
print(getmetatable(table1)) --使用getmetatable方法获得table1的元表地址
输出:
table: 00BBA320
meta = {
name = 2 }
meta.__index = meta
table1 = {
age = 1 }
setmetatable(table1, meta)
print(rawget(table1,"nane")) --rawget方法只查询子表,无视元表index
输出:
nil
meta = {
}
table1 ={
}
meta.__newindex = {
}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
rawset(table1, "age", 1) --rawset方法可以无视元表的newindex,直接修改子表
print(table1.age)
输出:
nil
1