foreword
In Lua we cannot directly add two tables, compare functions, or call a function.
So Lua can modify the behavior of a value so that it can perform a self-implemented operation in the face of a non-predefined operation. For example, add two tables, a+b. Let a and b be both tables. When Lua tries to calculate a+b, it will check whether one of the two has a metatable, and then check whether there is a function field of __add in the metatable. If it is found, it will call the corresponding field The value of , this value is the "meta method", that is, the function.
Every value in Lua has a metatable. table and userdata can have their own separate metatables, while other types share a single metatable to which their types belong. However, Lua only supports modifying the metatable of the table, and other metatables need to go to the underlying C code to complete
Get the metatable of table:
t = {} print(getmetatable(t)) --设置b为a的元表 setmerarable(a,b)
13.1 Metamethods of arithmetic classes
Implement union and intersection of sets with metamethods:
Set = {} local mt = {} function Set.new(l) local set = {} setmetatable(set, mt) for _,v in pairs(l) do set[v] = true end return set end function Set.union(a, b) local res = Set.new({}) for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection(a, b) local res = Set.new({}) for k in pairs(a) do res[k] = b[k] end return res end function Set.tostring(set) local l = {} for e in pairs(set) do l[#l+1] = e end return "{"..table.concat(l,", ").."}" end function Set.print(s) print(Set.tostring(s)) end mt.__add=Set.union s1=Set.new({10,20,30,50}) s2=Set.new({1,30}) s3 = s1 + s2 mt.__mul=Set.intersection Set.print(s3) Set.print(s1*s2)
In addition to __add and __mul in the code, there are also __sub (subtraction), __div (division), __unm (opposite number), __mod (modulus), __pow (exponentiation), __concat, etc.
Lua search metatable rule: first find whether the first value has a metatable, and there is an __add field in the metatable, then this is calculated as a metamethod and has nothing to do with the second one. Conversely, if the second one has a metatable and has an __add field, then this is the calculation function. None, it will cause an error.
13.2 Metamethods of relationship classes
__eq (equal to)
__lt (less than)
__le (less than or equal to)
13.3 Library-Defined Methods
tostring
setmetatable
getmetatable
13.4 Metamethods for table access
13.4.1 The __index metamethod
In fact, when accessing a field that does not exist in a table, Lua's search path is to find a metamethod called __index. If not, then the result of the access is nil. Otherwise the final result is provided from __index.
It's a bit like the concept in inheritance. If the subclass does not have an attribute, it will go to the parent class to find it. In Lua, it is common to use the __index metamethod to implement inheritance. __index is not necessarily a function, it can also be a table. When it is a function, Lua calls this function with a table and a key that does not exist as parameters. When it is a table, revisit this table in the same way.
13.4.2 The __newindex metamethod
When assigning a value to a field that does not exist in a table, the interpreter looks for the __newindex metamethod. If __newindex is a table, assign a value in this table.
But rawset(table,k,v) can bypass all the meta-methods of table and force the table to be assigned
13.4.3 Tables with default values
Implement the default value code through the __index metatable:
function setDefault(t, d)
local mt = {__index = function() return d end}
setmetatable(t, mt)
end
tap = {x=10,y=20}
print(tap.x, tap.z)
setDefault(tap, 0)
print(tap.x, tap.z)
The setDefault function creates a new metatable for all tables that require default values, but this can be prohibitively expensive when there are multiple tables. It would be great if multiple tables could use the same default metatable. In fact, it is very simple. Setting the mt of the above code as a global variable will not be ok. The code is as follows
local mt = {__index = function(t) return t.___ end}
function setDefault(t, d)
t.___ = d
setmetatable(t, mt)
end
tap = {x=10,y=20}
print(tap.x, tap.z)
setDefault(tap, 0)
print(tap.x, tap.z)
13.4.4 Tracking table access
Implement a function that can monitor table changes,
t = {}
local _t = t
t = {}
local mt = {
__index = function(t, k)
print("access to element"..tostring(k))
return _t[k]
end,
__newindex = function(t, k , v)
print("update of ellement"..tostring(k))
_t[k] = v
end
}
setmetatable(t, mt)
t[2] = "hello"
print(t[2])
Disadvantages: 1. It is impossible to traverse the original table, that is, _t. 2. There are multiple metatables for multiple monitors, which wastes overhead
improve:
local index = {}
t = {}
local mt = {
__index = function(t, k)
print("access to element"..tostring(k))
return t[index][k]
end,
__newindex = function(t, k , v)
print("update of ellement"..tostring(k))
t[index][k] = v
end
}
function track(t)
local answer = {}
answer[index] = t
setmetatable(answer, mt)
return answer
end
tmp = {}
tmp = track(tmp)
tmp[2] = "hello"
print(tmp[2])
operation result:
13.4.5 Read-only tables
Just disable the update operation (__newindex)
function readonly(t)
local answer = {}
mt = {
__index = t,
__newindex = function(t, k ,v)
error("update error")
end
}
setmetatable(answer, mt)
return answer
end
days = {1,2,3}
days = readonly(days)
print(days[1])
days[1]=2
By the way, let me talk about the knowledge points of Chapter 14
setfenv(f, table): set the environment of a function
(1) When the first parameter is a function, it means setting the environment of the function (2) When the first parameter is a number, 1 represents the current function, and 2 represents calling its own function (that is, the function of the previous layer ), 3 represents the function that calls its own function (the function of the previous layer of the previous layer), and so on
* Nature: The environment of the function, in fact, an environment is a table, and the function is limited to only access the fields in the table, or the variables defined by itself in the function body.
setfenv example
--[[
Example 1: setfenv(1, env1)
sets the current function operating environment
The following example is equivalent to setting the operating environment of the test1 function to env1, and env1 is empty, so print("test1 func") will report an error]
]local env1 = {
}
print("main func")
local test1 = function()
setfenv(1, env1)
print("test1 func")
end
test1()--[[
Correct writing
output:
main func
test1 func
]]
local env1 = { print = print } print("main func") local test1 = function() setfenv(1, env1) print("test1 func") end test1 ()
--[[
Example 2: setfenv(2, env1)
sets the function environment of the upper layer of the setfenv function
The following example is equivalent to setting the environment env1 of the test2 function of the upper layer of the test1 function, so when running print("test2 func") An error will be reported
]]
local env1 = { } print("main func") local test1 = function() setfenv(2, env1) print("test1 func") end local test2 = function() test1() print("test2 func") end test2() --[[ No error will be reported if print and test1 are swapped in the test2 function ]] local env1 = { } print("main func") local test1 = function() setfenv(2,env1) print("test1 func") end local test2 = function()
print("test2 func")
test1()
end
test2()
getfenv example
--[[
Example: getfenv(2)
test1 function environment env1 set its own operating environment, so it will report an error but can print the main func test2 func
normally ]] local env1 = { } print("main func") local test1 = function() local env2 = getfenv(2) setfenv(1, env2) print("test1 func") end local test2 = function() print("test2 func") setfenv(1, env1) test1() end test2()