Lua元表使用

lua中每个值都可以拥有一个元表,元表是一个普通的lua table,定义了原始值在特定操作下的行为。

  • setmetatable、getmetatable就可以设置元表和获取元表:
local t = {}
local mt = {}
setmetatable(t, mt)
getmetatable(t)
  • 改变元表中特定的key,来改变原始值的对应行为,这个key对应的value(table或function)叫”元方法“。

通过例子可以更清楚的理解元表\元方法的含义:

1. __call 元方法

-- 尝试调用一个非函数类型值

local A = {}
local metaA = {
    __call = function(...)
        print("call A with args : ", ...)
    end
}

setmetatable(A, metaA)

A("string", 1)  -- output: call A with args: table:0x2814cd640 string 1

当调用A(A是一个table)时,会找A的元表中的__call元方法,A作为第一个参数,调用A的参数列表随后。
也可以用”:“定义__call元方法,传入的第一个参数A 则就是self :

-- 尝试调用一个非函数类型值

local A = {}
local metaA = {}

function metaA:__call(...)
    print("call A with self : ", self)
    print("with args : ", ...)
end

setmetatable(A, metaA)

A("string", 1)  
-- output: call A with self: table:0x2814cd640
--         with args: string 1

tips:
(1) __call元方法可以用来实现类似构造方法的调用形式。
(2) 在调用某个值a前不确定它是否是function时,除了判断 type(a) == "function" 外,还有可能a不是function 但a的元表__call是function,也是可以成功调用的。


2. __index 元方法

-- 尝试访问一个不存在的key
local A = {}
print( A.a )    -- output: nil

-- 1.__index是一个table
local metaA = {
    __index = {
    a = "123"
    }
}
setmetatable(A, metaA)
print( A.a )    
-- output: 123

-- 2.__index是一个function
local metaA2 = {
    __index = function(t, k)
        print("try to get key : "..k.." from : ",t)
    end
}
setmetatable(A, metaA2)
print( "A.a is : ", A.a )   
-- output: try to get key : a from table: 0x281495640
--         A.a is : nil  (这里由于__index并没有返回任何值,所以打印 nil )

lua中查找表元素的过程:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值(传入查找的表和key)。


3. __newindex 元方法
__newindex 元方法用来对表更新,当你给表的一个不存在的key赋值,解释器就会查找__newindex 元方法:

-- 尝试给一个不存在的 key 赋值
local A = {}
A.a = "123"
print("A.a is ", A.a)
-- output: A.a is 123

local metaA = {
    __newindex = function(t, k, v)
        print("try to set value:", v, " for key:", k, " for ", t)
    end
}
setmetatable(A, metaA)
A.a = "456"
print("A.a is ", A.a)
-- output: A.a is "456"
-- 此时 A 已经包含 key "a", 所以直接改变 A.a 而没有走元表的__newindex

A.a = nil
A.a = "789"
print("A.a is ", A.a)
-- output: try to set value: 789 for key: a for table: 0x2814aec00
--         A.a is nil
-- 这里由于已经将 A.a 置空,再次赋值时没有这个 key 所以走了元表的__newindex,元方法只进行了打印并没有任何操作,所以打印 A.a 仍是nil

给table的某个key赋值时,只有当表中不存在这个key时才会找元表的__newindex方法,传入表t、要赋值的key和value,当表中已经存在这个key时则会直接赋值。

利用__index和__newindex可以实现一些有意思的功能,比如:

  • 实现类似switch/case中的default语句:
local Score = setmetatable({
    ["A"] = "91~100",
    ["B"] = "81~90",
    ["C"] = "61~80",
    ["D"] = "0~60"
}, {
    __index = function()
        return "error";
    end
})
print(Score.A)      -- "91~100"
print(Score.E)      -- "error"  
  • 将一个表作为另一个表的元表的__index,从而实现表继承关系:Lua实现继承;
  • 将__index 和 __newindex作为get、set方法:Lua实现KVO

4. 运算符方法
运算符方法用来定义table的运算操作,类似C++中的运算符重载

local A = {
    a = "a"
}
local B = {
    a = "b"
}
local m = {
    __add = function(t1, t2)
        return {
            a = t1.a .. t2.a
        }
    end
}
setmetatable(A, m)
setmetatable(B, m)
print((A + B).a)    -- output: ab
print((B + A + B).a)    -- output: bab

Lua中所有的运算符元方法:https://www.lua.org/manual/5.3/manual.html#2.4

5. 垃圾回收方法

local A = {}
local metaA = {
    __gc = function(...)
        print("receive gc with args : ", ...)
    end
}
setmetatable(A, metaA)
collectgarbage()

ps. lua5.2以上版本


6. 弱引用表
弱引用表允许它的key和value被gc回收,通过设置元表的__mode实现,__mode是一个字符串,如果包含"k"则key是弱引用,如果包含"v"则value是弱引用。

local k = {}
local v = {}
a = {}
a[k] = v
k = nil
v = nil
collectgarbage()
for i, v in pairs(a) do
    print(i, v)
end
-- output: table: 0x282314a40  table: 0x282314840

setmetatable(a, {__mode = "k"})
collectgarbage()
print("weak table:")
for i, v in pairs(a) do
    print(i, v)
end
-- output: weak table:

你可能感兴趣的:(Lua元表使用)