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: