[Lua Study Notes] Lua Advanced - Table (3) Metatable

Insert image description here
Continue from above


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:

  1. Operation subtable
  2. Check if there is a metatable
  3. 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 __callmeta 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 __callthe meta method, we used it table(index)and found that __callthe meta method printed 123, 456 and call. 123 is the element of the subtable, and 456 is the __tostringreturn result. This means that __callthe method calls the subtable as parameter a. And like our printing example above, print(a)the method also calls __tostringthe metamethod. Parameter 2 in table(2)is obviously ignored.

Now we can determine a basic rule: when using __tostringthe sum __callmetamethod, 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 __indexthat 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:

  1. Query subtable
  2. Subtable query failed, query metatable to see if there is a __indexmetamethod
  3. If so, query __indexthe table pointed to by the meta-method

However, there is another situation where it is recommended not to __indexdefine 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 __newindexthe meta method. Its function is actually to add the newly added elements of the sub-table to __newindexthe pointed table without modifying the sub-table (of course __newindexit 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 table1the metatable is bound, but table2not the metatable. The reason return falseis 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 table1from table2the 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 __eqthe meta method to determine agewhether 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 namewhether the index values ​​​​are the same. Table1 does not have namean 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 __eqthe 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

Guess you like

Origin blog.csdn.net/milu_ELK/article/details/131965788