Lua_ Detailed explanation of meta table_ Deep understanding of index and newindex_ Self realization monitoring table_ Read only table_ Operator overloading_ tostring(14)


Code cloud code link
https://gitee.com/wenwenc9/lua_pro.git

Meta table concept

In Lua table, we can access the corresponding key to get the value value, but we cannot operate on two tables.
Therefore, Lua provides a meta table, which allows us to change the behavior of the table, and each behavior is associated with the corresponding meta method.
For example, using a meta table, we can define how Lua calculates the addition operation a+b of two tables.

When Lua tries to add two tables, first check whether one of them has a meta table, and then check whether there is a field called "_add". If it is found, call the corresponding value. For real-time fields such as "_add", the corresponding value (often a function or table) is "meta method".
There are two important functions to handle metatables:

  • Set metatable (table, metatable): set a metatable for the specified table if it exists in the metatable__ Metatable key value, setmetatable will fail. [cannot inherit more]
  • getmetatable(table): returns the metatable of the object.

The following example demonstrates how to set a meta table on a specified table:

t = {}
print(getmetatable(t)) -- nil

You can use the setmetatable function to set or change the metatable of a table

t1 = {}
setmetatable(t,t1)
print(getmetatable(t)) -- table:table: 00A89B30
print(assert(getmetatable(t) == t1)) -- true

Any table can be a metatable of another table, and a group of related tables can share a metatable
(describe their common behavior)
A table can also be its own metatable (describing its private behavior). It's a bit like the inheritance class in python

In short, the meta table concept:

  • Any table variable can be used as a meta table of another table variable
  • Any table variable can have its own meta table (DAD)
  • When we do some specific operations in the sub table
  • The contents of the meta table are executed

1, Common fields of meta table

The following fields are commonly used in meta tables:

Arithmetic class meta method:
__add(+), __mul(*), __ sub(-), __div(/), __unm, __mod(%), __pow, (__concat)

Relationship class meta method:
__ eq, __ lt(<), __ Le (< =), other Lua s automatically convert a ~ = B -- > not (a = = b) a > B -- > b < a > = B -- > b < = a (pay attention to NaN)

Metamethods accessed by table:
__index, __newindex

__ index: query: access fields not saved in the table & rawget (T, I)
__ newindex: update: assign a rawset(t, k, v) to an index that does not exist in the table

2, Table lookup element rule

A meta table is essentially a table used to store meta methods. We can get the value value through the corresponding key. The function is to modify the behavior of a value (more specifically, this is the ability of meta methods). It should be noted that this modification will overwrite the corresponding predefined behavior that may exist for the value.

Each value in lua can have a meta table, but table and userdata can have separate meta tables, while other types of values share a single meta table to which their types belong.

Only the meta table of table can be set in lua code, and the meta table of other types of values can only be set through C code.

Multiple tables can share a common meta table, but each table can only have one meta table.

We call the keys in the meta table events and the values meta methods. The event in the preceding example is "add", and the meta method is a function that performs addition.

You can query the meta table of any value through the function getmetatable.

The metatable of a table can be replaced by the function setmetatable

The rules for lua to look up elements in a table are as follows (very important!! you need to understand this):

1. Search in the table. If found, return the element. If not found, continue
2. Judge whether the table has a meta table. If there is no meta table, return nil. If there is a meta table, continue
3. Judge whether there is a meta table__ Index method, if__ If the index method is nil, nil is returned; If__ If the index method is a table, repeat 1, 2, and 3; If__ If the index method is a function, it returns the return value of the function

The simplest case

father = {
    house = 1
}
son = {
    car = 1
}

setmetatable(son,father)
print(son.house)  -- nil
-- father.__index=father
setmetatable(son,{__index=father})
print(son.house) -- 1

Try to use the above search element rules to understand!!!!

III__ index metamethod (lookup method)

1 Application__ index create meta table

Only by using this method can we realize the real meta table. The meta table elements you want to inherit will not be found in the single save setmetatable

other = { foo = 3}
t = setmetatable({},{__index = other})
print(t.foo) -- 3
print(t.bar) -- nil

2 __index corresponding function

If__ If index contains a function, Lua will call that function, and table and key will be passed to the function as parameters.
__ The index meta method checks whether the elements in the table exist. If they do not exist, the returned result is nil;
If present, by__ index returns the result.

Corresponding function example

mytable = setmetatable({ key1 = "value1" }, {
    __index = function(mytable, key)
        if key == "key2" then
            return "metatablevalue"
        else
            return nil
        end
    end
})
print(mytable.key1, mytable.key2,mytable.key3)
-- value1	metatablevalue	nil

Instance resolution:

  • The mytable table is assigned {key1 = "value1"}.
  • mytable sets the meta table. The meta method is__ index.
  • Find key1 in the mytable table. If it is found, return the element. If it is not found, continue.
  • Look up key2 in the mytable table. If it is found, return the element. If it is not found, continue.
  • Judge whether the meta table has__ Index method, if__ If the index method is a function, it is called.
    Check whether the parameter of "key2" key is passed in the meta method (mytable.key2 has been set). If the parameter of "key2" is passed in, it returns "metatablevalue". Otherwise, it returns the key value corresponding to mytable.

The above code can be abbreviated

mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)

When we visit a nonexistent field of a table, the return result is nil, which is correct, but not necessarily correct.
In fact, this access triggers the lua interpreter to find it__ Index metametethod: if it does not exist,
The returned result is nil; If present, by__ Index metametethod returns the result

3 window cases

The prototype of this example is an inheritance. Suppose we want to create some tables to describe the window. Every watch must be traced
Describe some parameters of the window, such as position, size, color, style, etc. All these parameters have default values,
When we want to create a window, we only need to give parameters with non default values to create the window we need. first
One way is to implement the constructor of a table and fill in the default value for each missing field in the table. The second method is to create a new window to inherit the missing domain of a prototype window. First, we implement a prototype and a
Constructor, they share a metatable:

- create a namespace
Window = {}
-- create the prototype with default values
Window.prototype = { x = 0, y = 0, width = 100, height = 100, }
-- create a metatable
Window.mt = {}
-- declare the constructor function
function Window.new (o)
    setmetatable(o, Window.mt)
    return o
end

Now let's define__ index metamethod

Window.mt.__index = function(table, key)
    return Window.prototype[key]
end

In this way, we create a new window and access the missing domain. The results are as follows:

w = Window.new{x=10, y=20}
print(w.width) --> 100

When Lua finds that w does not have a domain width, but there is a metatable with__ index field, used by Lua
w (the table) and width (missing value)__ index metamethod, which is accessed through
The prototype table gets the result of the missing domain.
__ Index metathod is very common in inheritance, so Lua provides a more concise use
Way__ Index metathod does not need to be a function, it can also be a table. But it's a function
When, Lua calls this function with table and missing fields as parameters; When he is a watch, Lua will
See if there are missing fields in this table. Therefore, the above example can be simply rewritten in the second way
Is:

Window.mt.__index = Window.prototype

Now, when Lua looks for metatable__ index domain, he found window The value of prototype, which is
A table, so Lua will access this table to get the missing value, that is, it is equivalent to executing:

Window.prototype["width"]

Take a table as__ index metamethod is used to provide a cheap and simple way to implement single inheritance
Law. Although the cost of a function is slightly higher, it provides more flexibility: we can implement multiple inheritance and implicit
Tibet, and some other mechanisms of variation. We will discuss the way of inheritance in detail in Chapter 16.
When we don't want to call__ Index metathod to access a table. We can use the rawget function.
The call of Rawget(t,i) accesses the table in raw access mode. This access will not make your code faster
overhead of a function call kills any gain you could have)
We'll see.

Streamline the above methods

Window = {}
Window.prototype = {
    x = 0,
    y = 0,
    width = 100,
    height = 100
}
Window.mt = {}
w = setmetatable({x=10,y=10},{__index=Window})
print(w.width) -- nil Window Not in the table width key
print(w.prototype.width) -- 100
--- Drill down the inherited meta table
w = setmetatable({x=10,y=10},{__index=Window.prototype})
print(w.width) -- 100
print(w.x) -- 10

IV__ newindex meta method (update and modification)

__ The newindex meta method is used to update the table__ index is used to access the table.

1 basic example

mymetatable = {}
mytable = setmetatable({key1 = 'value1'},
        {__newindex=mymetatable})

print(mytable.key1) -- value1

-- Part I 
mytable.newkey = "New value 2"
print(mytable.newkey,mymetatable.newkey) -- nil    New value 2

-- Part II
mytable.key1 = "New value 1"
print(mytable.key1,mymetatable.newkey1) -- New value 1    nil

Part I understanding:
When performing key value operations on a table, first search in the original table mytable. If not, judge whether there is a meta table. If there is a meta table, perform meta method search and find that there is a meta table__ newindex, corresponding to mymetatable, searches for mymetatable. If it is not found, check whether there is an inherited meta table. If it is not found, add a newky = 'add 2' in mymetatable

Part II understanding:
When performing key value operations on a table, first search the original table mytable and find the corresponding key, then update the value of this table

Use rawset to bypass metamethod to update tables instead of meta tables

mytable = setmetatable({key1 = "value1"}, {
  __newindex = function(mytable, key, value)
       rawset(mytable, key, "\""..value.."\"")

  end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2) -- new value "4"

5, Build default table

Option 1

function setDefault(t,d)
    local mt = {__index = function () return d end}
    setmetatable(t,mt)
end

tab = {x=10,y=10}
print(tab.x,tab.y,tab.z)
setDefault(tab,0)
print(tab.x,tab.y,tab.z)
print(tab.c)
10	10	nil
10	10	0
0

In this creation, whenever we access the missing field of the table, its meta method will return the default value of 0

Option 2

Case 1 method will consume a lot of metatable. If only one metatable is used, it will be greatly optimized. However, different tables have different default values. This method stores the default values in their own fields

Create your own domain for each table and store your own default values.

To solve this problem, a unique field is stored in the table itself. If you don't worry about the confusion of naming, you can use "_" (three underscores) as the unique field representation

local mt = {__index = function (t) return t.___ end}
function setDefault (t, d)
     t.___ = d
     setmetatable(t, mt)
end
tab1 = {x=10}
setDefault(tab1,1)
print(tab1.x,tab1.z,tab1.y)

tab2 = {x=25}
setDefault(tab2,2)
print(tab2.___,tab2.__)

Option 3

If we are worried about naming confusion, it is also easy to ensure the uniqueness of this special key value. All we have to do is create
Create a new table as the key value: (because it involves the uniqueness of the field in the table, of course "_" The domain is also unique in actual use. You can use another key to ensure the uniqueness of the domain.)

local key = {} -- unique key
local mt = { __index = function(t)
    return t[key] 
end }
function setDefault (t, d)
    t[key] = d -- Ensure the uniqueness of the domain
    setmetatable(t, mt)
end
tab1 = {x=10}
setDefault(tab1,3)
print(tab1.x,tab1.s)

Option 4

To ensure uniformity, save each table and their default values in a public table, but the table needs to be a weak table
– if the public table is an ordinary table, the table as a key will be assumed to exist forever and will not be recycled by Lua
– the key of our weak table needs to be weak, so if the table as the key is not referenced, it will be recycled by Lua

defaults = {}
setmetatable(defaults, { __mode = "k" })  --key yes weak of weak table

mt = { __index = function(t)
    return defaults[t]
end }

function setDefault (t, d)
    defaults[t] = d
    setmetatable(t, mt)
end

Option 5

The above method is very good, but suppose there are thousands of tables, but there are several default values of tables, then the public defaults table
– the information stored in is redundant
– this method is to save the meta table through the default value. The same default value uses the same meta table, and different default values use different meta tables?
– naturally, this method uses memory technology
– use a public table to save the meta table. key is the default value and value is the meta table corresponding to the default value
– of course, no one should allow lua's GC to recycle metatable when using it again, so the public table should be a weak table with value as weak

metas = {}
setmetatable(metas, {__mode = "v"})

function setDefault (t,d)
	local res = metas[d]
	if res == nil then
		res = {__index = function () return d end}
		metas[d] = res   -- memoize  This is memory technology~~
	end
	setmetatable(t, res)
end

6, Monitoring table

__ index and__ New indexes work only when the domain accessed in the table does not exist.

  • The only way to capture all access to a table is to keep the table empty.
  • To monitor all accesses to a table, you should create a proxy for the real table.
  • The agent is an empty table with__ index and__ newindex meta method, which is responsible for tracking all access of the table and pointing it to the original table.
  • Assuming that t is the original table we want to track, we can:
--Original table
t = {}

-- Maintain private access to the original table
local _t = t

-- Create proxy
t = {}

-- Create meta table
local mt = {
    __index = function(t, k)
        print("Access element " .. tostring(k))
        return _t[k] -- Access original table
    end,
    __newindex = function(t, k, v)
        print("Update element " .. tostring(k) ..
                " to " .. tostring(v))
        _t[k] = v -- Update original table
    end
}
setmetatable(t, mt)

t[2] = 'hello'
print(t[2])
Update element 2 to hello
 Access element 2
hello

(Note: Unfortunately, this design does not allow us to traverse the table. The Pairs function will operate on the proxy instead of the original table)

  • If we want to monitor multiple tables, we don't need to create a different metatable for each table.
  • We only need to associate each proxy with its original table, and all proxies share a common metatable.
  • A simple way to associate a table with the corresponding proxy is to use the original table as the proxy domain, as long as we ensure that this domain is not used for other purposes.
  • A simple way to ensure that it is not used for other purposes is to create a private key that no one else can access.

Summarize the above ideas, and the final results are as follows:

local index = {}  -- Create private index

local mt = {
    __index = function(t, k)
        print("*access to element " .. tostring(k))
        return t[index][k] -- Query original table
    end,
    __newindex = function(t, k, v)
        print("*update of element " .. tostring(k) .. " to "
                .. tostring(v))
        t[index][k] = v -- Update original table
    end
}
function track (t)
    local proxy = {}
    proxy[index] = t
    setmetatable(proxy, mt)
    return proxy
end
tab1 = {x=10}
tab1 = track(tab1)
tab1[2] = 'hello'

Create a general proxy, assign a private domain index = {}, store different tables, and inherit the meta table

*update of element 2 to hello

7, Read only table

  • Using the idea of agent, it is easy to implement a read-only table.
  • All we need to do is throw an error when we monitor an attempt to modify the table.
  • Pass__ Index metamed, we can use the original table itself instead of using the function, because we don't need to monitor the lookup.
  • This is a simple and efficient way to redirect all queries to the original table.
  • However, this usage requires each read-only agent to have a separate new metatable, using__ index points to the original table:
function readOnly (t)
    local proxy = {}
    local mt = {
        __index = t,
        __newindex = function(t, k, v)
            error("attempt to update a read-only table", 2)
        end
    }
    setmetatable(proxy, mt)
    return proxy
end

days = readOnly { "Sunday", "Monday", "Tuesday", "Wednesday",
                  "Thursday", "Friday", "Saturday" }
print(days[1]) --> Sunday 
days[2] = "Noday"

8, Adding operators to tables

1 __tostring

meta2 = {
    -- When the sub table is to be used as a string, the in the meta table will be called by default tostring method
    __tostring = function()
        return 'Lao Wang'
    end
}
myTable2 = {}
setmetatable(myTable2,meta2)
print(myTable2) -- Lao Wang

tostring adds a parameter. By default, the sub table will be passed in and the sub table attribute will be called

meta2 = {
    -- When the sub table is to be used as a string, the in the meta table will be called by default tostring method
    __tostring = function(t)
        return t.name
    end
}
myTable2 = {
    name = 'Lao Wang'
}
setmetatable(myTable2,meta2)
print(myTable2) -- Lao Wang

2 __call

This is called by default when the sub table is used as a function__ Contents in call

meta3 = {
    -- When the sub table is to be used as a string, the in the meta table will be called by default tostring method
    __tostring = function(t)
        return t.name
    end,
    -- This is called by default when the sub table is used as a function__call Content in
    __call = function(a)
        print(a)
        print('Lao Wang is riding a bike')
    end
}
myTable3 = {
    name = 'Lao Wang'
}
setmetatable(myTable3,meta3)
--print(myTable3)
myTable3(1)

I passed in the parameter of sub table 1, but I didn't print 1, but myself, which is equivalent to__ call method, if the parameter is passed in, its own sub table is passed in as a parameter. Affected by tostring, it is passed in. If tostring is annotated, the table is printed


First, if tostring is in, the child table has been passed in, but tostring has been annotated,
Then, when call ing, the first parameter is the sub table and the second parameter is 1

3 __add

meta4 = {}
myTable4 = {}
setmetatable(myTable4,meta4)
myTable5 = {}
print(myTable4 + myTable5) -- report errors

So transform meta4

meta4 = {
    -- Equivalent to operator overloading, when the child table is used+Operator, the method is called
    __add = function(t1,t2)
        return t1.age + t2.age
    end
}
myTable4 = {age=1}
setmetatable(myTable4,meta4)
myTable5 = {age=2}
print(myTable4 + myTable5) -- 3

Other Operators

meta4 = {
    -- Equivalent to operator overloading, when the child table is used+Operator, the method is called
    __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,
    __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,
    __concat = function(t1,t2)
        return t1.age .. t2.age
    end,
}
myTable4 = {age=1}
setmetatable(myTable4,meta4)
myTable5 = {age=2}
print(myTable4 + myTable5) -- 3

9, Supplement__ index and__ newindex

__ The index should not be written in the meta table, but outside

1. Search in the table. If found, return the element. If not found, continue
2. Judge whether the table has a meta table. If there is no meta table, return nil. If there is a meta table, continue
3. Judge whether there is a meta table__ Index method, if__ If the index method is nil, nil is returned; If__ If the index method is a table, repeat 1, 2, and 3; If__ If the index method is a function, it returns the return value of the function

meta6 = {
    age =1
}
meTable6 ={}
setmetatable(meTable6 ,{__index = meta6})
print(meTable6.age)

__newindex
When copying, if you copy an index that does not exist
This value will be assigned to the table indicated by newinex and will not be modified

meta7 = {}
meta7.__newindex = {}
myTable7 = {}
setmetatable(myTable7,meta7)
meta7.age =1
print(myTable7.age)
print(meta7.age)

Keywords: lua

Added by Tjorriemorrie on Tue, 25 Jan 2022 15:31:04 +0200