Lua Language Programming Learning Road 02----Chapter 13 Metatable and Metamethod

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()


















Guess you like

Origin blog.csdn.net/qq_41286356/article/details/118057795