元表
lua中每个值都可以有元表,
- 默认情况下我们只可以为table类型的变量设置元表,其他类型需要通过c代码为某个类型设置元表。
- string类型的值默认情况下共用一个元表,
- 其他类型默认情况下没有元表(包括table类型)
print(getmetatable({})) --nil
print(getmetatable("44")) --table: 000002D826B11A20
print(getmetatable("454")) --table: 000002D826B11A20
print(getmetatable(print))--nil
print(getmetatable(4))--nil
__index
当一个表访问某个不存在的值时,一般情况下会返回nil。若该表有元表,就会访问其元表的__index元方法。
- 若此元方法为表是,lua会从这个新表中查找该值。
--Set.lua
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__index = newTable
return Set
-- 调用测试
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
print(tab[2],tab[4]) -- nil 4
setmetatable(tab,Set)
print(tab[2],tab[4]) --2 4
- 若此方法为函数时,lua会调用该函数,函数的参数为
原table
和不存在的键k
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__index = function(tab,k)
return "not contain " .. k
end
return Set
-- 调用测试
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
--print(tab[2],tab[4])
setmetatable(tab,Set)
print(tab[2]) -- not contain 2
print(tab[4]) --4
这个方法就常用来实现继承。可以使用rawget(t,i)来对表t进行原始的访问。此方法可以绕过元表,对表进行原始的访问。
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
setmetatable(tab,Set)
print(rawget(tab,2)) --nil
print(tab[2]) -- not contain 2
print(tab[4]) --4
__newindex
当为表中不存在的某个键赋值时,就会调用该方法,
- 此方法该函数时,其参数为
原table
,新k
,新v
。
-- Set.lua
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__newindex = function(tab,k,v)
print("not has this key")
end
Set.__index = newTable
return Set
-- 调用测试
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
tab[2] = 2
print(tab[2]) -- 2
setmetatable(tab,Set)
tab[3] = 5 -- not has this key
print(tab[3]) -- 3
可以看到设置了元表后,当tab[3] = 5时,调用的时元表的__newindex方法,而调用print(tab[3])时调用元表的__index方法,输出了3。
我们可以使用rawset(t,k,v)为某个表设置新值,而不调用元表的__newindex方法。
---Set.lua
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__newindex = function(tab,k,v)
rawset(tab,k,v)
end
Set.__index = newTable
return Set
---调用测试
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
tab[2] = 2
print(tab[2]) --2
setmetatable(tab,Set)
tab[3] = 5
print(tab[3]) --5
这里看到当再次调用print(tab[3]) 的时候,输出的时5。是因为上面调用了rawset方法,为原表增加了新的值。 为什么不能在__newindex用下面的书写方式呢?
Set.__newindex = function(tab,k,v)
tab[k] = v
end
是因为tab中原本不存在k,这样为表tab增加新值又会调用其元表的__newindex方法,从而导致无线循环。
- 当__newindex是一个表是,新值会直接存到__newindex所对应的表中
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__newindex = newTable
Set.__index = function(tab,k)
print("newTable start")
for k,v in pairs(newTable) do
print(k,v)
end
return "newTable end"
end
return Set
---
--- 调用测试
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
tab[2] = 2
print(tab[2]) -- 2
setmetatable(tab,Set)
tab[6] = 5
print(tab[6])
--输出结果
-- newTable start
-- 6 5
-- 1 1
-- 2 2
-- 3 3
-- newTable end
当访问原table中不存在的值tab[6]时,打印newTable的值时,发现多了键值为5的这一项。
__call方法
函数调用操作 func(args)。 当 Lua 尝试调用一个非函数的值的时候会触发这个事件 (即 func 不是一个函数)。 查找 func 的元方法, 如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。
--- Set.lua
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__newindex = newTable
Set.__index = function(tab,k)
print("newTable start")
for k,v in pairs(newTable) do
print(k,v)
end
return "newTable end"
end
Set.__call = function(tab, ...)
print(tab,...)
end
return Set
-- 调用测试
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
tab[2] = 2
setmetatable(tab,Set)
tab(2,45,4)
-- table: 000001D53F0CD5B0 2 45 4
__add
两个表直接使用+
运算符时错,会调用其元表的__add元方法,因为默认情况下表没有元表,所以直接将两个表相加时会报错。
- 当两个表相加时,若第一个表有元表,且有__add元方法,则调用第一个表的__add元方法,否则调用第二个的,若两个都没有,则会报错。
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__newindex = newTable
Set.__index = function(tab,k)
print("newTable start")
for k,v in pairs(newTable) do
print(k,v)
end
return "newTable end"
end
Set.__call = function(tab, ...)
print(tab,...)
end
Set.__add = function(tab1,tab2)
local sum = 0
for _,v in pairs(tab1) do
sum = sum + v
end
for _,v in pairs(tab2) do
sum = sum + v
end
return sum
end
return Set
调用
local Set = require("Set")
local tab =
{
[4] = 4,
[5] = 5,
}
setmetatable(tab,Set)
print(tab + tab)
--18
当然table也可以与number类型的值相加,只要保证table有元方法__add即可。
- 与之类似的有__sub(减法),__mod(取模),__div(除法)等等。
- 还有关系运算符,__eq(等于),__lt(小于),__le(小于等于),只有这三个,其他的例如~=可以转换为not(==),所以lua没有提供相应的机制。
__pairs
当时使用pairs方法遍历table时调用,若没有则,则默认放回next迭代函数,tab不可变量,nil
local Set = {}
local newTable =
{
[1] = 1,
[2] = 2,
[3] = 3,
}
Set.__newindex = newTable
Set.__index = function(tab,k)
print("newTable start")
for k,v in pairs(newTable) do
print(k,v)
end
return "newTable end"
end
Set.__call = function(tab, ...)
print(tab,...)
end
Set.__add = function(tab1,tab2)
local sum = 0
for _,v in pairs(tab1) do
sum = sum + v
end
for _,v in pairs(tab2) do
sum = sum + v
end
return sum
end
local function temp(tab,index)
local nk, nv = next(tab, index)
if nk then
nv = tab[2]
end
return nk, nv
end
Set.__pairs = function(tab)
return temp,tab,nil
end
return Set
调用
local Set = require("Set")
local tab =
{
[1] = 4,
[2] = 5,
}
setmetatable(tab,Set)
for k,v in pairs(tab) do
print(k,v)
end
--输出
-- 2 5
·· 1 5
因为我们重写了__pairs元方法,返回的迭代函数temp中,返回值永远都是tab[2]的值。
lua垃圾收集
lua使用的时自动管理内存,但以提供了一些辅助垃圾收集的机制
- 弱引用表允许lua释放还会被其他程序访问的表,类似于C#及其他语言中的弱引用机制。
- 析构器可以帮助lua释放lua收集器不能直接控制的外部资源
- collectgrabage允许我们设置垃圾收集其的步长,可以使垃圾收集机制更加高效
__mode
- 弱引用表就是元素均为弱引用的表(分为三类:值为弱引用的,键值为弱引用的表,值和键都为弱引用的,无论那种类型的弱引用表,只要有一个键或值被回收了,那么整个键值对都会从表中删除),若一个对象只被若引用表持有,则lua最终会回收这个对象。
- 一个表是否是弱引用,由其元表的__mode字段决定,__mode = "k",则表明键是弱引用的,__mode = "v"时表明值是弱引用的,__mode = "kv"变为值和键都是弱引用的。若不设置,说明表的键和值都是强引用,即使某个对象只最为了该表的键或值,也不会被回收。
local tab = {}
setmetatable(tab,{__mode = "k"})
local key1 = {}
local key2 = {}
tab[key1] = 1
tab[key2] = 2
key1 = nil
print("垃圾回收前")
for k,v in pairs(tab) do
print(k,v)
end
collectgarbage()
print("垃圾回收后")
for k,v in pairs(tab) do
print(k,v)
end
输出:
-- 垃圾回收前
-- table: 0000022E12E6E980 1
-- table: 0000022E12E6F240 2
-- 垃圾回收后
-- table: 0000022E12E6F240 2
可以看到,首先创建了两个table对象t1,t2,分别被key1,和key2持有,然后t1和t2有作为了键为弱引用的弱引用表tab的键值,此时t1分别被key1和tab持有,t2被key2和tab持有。当key1 = nil后,key1只被tab持有,当调用垃圾回收函数时,t1被回收了。验证了上面的说法。
- 此字段可以用缓存机制,比如记忆函数,如访问某种颜色对象,若该类型颜色不存在,则创建,并缓存下来。随着访问的颜色种类的增多,缓存数据会越来越大,导致程序运行缓慢,这时候可以使用该机制,这样在垃圾回收的时候,回收缓存种没有被使用的对象。
__gc
析构器,类似于C#种的析构函,在垃圾回收期间会调用该对象的析构器。
local tab = {}
tab = setmetatable(tab,{__gc = function() print("gc") end})
collectgarbage()
-- 输出结果
-- gc
垃圾收集步骤
- 标记,将可达的对象标记为活跃的
- 清理,查找所以需要进行析构,但没有标记为活跃的对象,将其标记为或与状态,并放在一个单独的列表中,等待析构。同时清理弱引用表中未被标记的对象(也就是该对象只被弱引用表持有)
- 清除,清除那些不可达的对象
- 析构,调用那些需要被析构对象的析构器,下一次这些对象会被回收
所以需要进行两次垃圾回收,才能删除需要析构的对象(第一次时调用析构器,第二次才是真正的回收)
常用的元表方法
__add(a, b) 对应表达式 a + b
__sub(a, b) 对应表达式 a - b
__mul(a, b) 对应表达式 a * b
__div(a, b) 对应表达式 a / b
__mod(a, b) 对应表达式 a % b
__pow(a, b) 对应表达式 a ^ b
__unm(a) 对应表达式 -a
__concat(a, b) 对应表达式 a .. b
__len(a) 对应表达式 #a
__eq(a, b) 对应表达式 a == b
__lt(a, b) 对应表达式 a < b
__le(a, b) 对应表达式 a <= b
__index(a, b) 对应表达式 a.b
__newindex(a, b, c) 对应表达式 a.b = c
__call(a, ...) 对应表达式 a(...)
__pairs(tab) 泛型for时使用
__mode 设置弱引用表
__gc(tab) 析构器