深度学习Lua--元表

元表的元方法

函数描述
__add运算符 +
__sub运算符 -
__mul运算符 *
__ div运算符 /
__mod运算符 %
__unm运算符 -(取反)
__concat运算符 ..
__eq运算符 ==
__lt运算符 <
__le运算符 <=
__call当函数调用
__tostring转化为字符串
__index调用一个索引
__newindex给一个索引赋值

由于那几个运算符使用类似,所以就不单独说明了,接下来说 __call, __tostring, __index, __newindex四个元方法。

__call

__call可以让table当做一个函数来使用。

local mt = {}
--__call的第一参数是表自己
mt.__call = function(mytable,...)
    --输出所有参数
    for _,v in ipairs{...} do
        print(v)
    end
end

t = {}
setmetatable(t,mt)
--将t当作一个函数调用
t(1,2,3)

结果:

1
2
3

__tostring

__tostring可以修改table转化为字符串的行为

local mt = {}
--参数是表自己
mt.__tostring = function(t)
    local s = "{"
    for i,v in ipairs(t) do
        if i > 1 then
            s = s..", "
        end
        s = s..v
    end
    s = s .."}"
    return s
end

t = {1,2,3}
--直接输出t
print(t)
--将t的元表设为mt
setmetatable(t,mt)
--输出t
print(t)

结果:

table: 0x14e2050
{1, 2, 3}

__index

调用table的一个不存在的索引时,会使用到元表的__index元方法,和前几个元方法不同,__index可以是一个函数也可是一个table。
作为函数:
将表和索引作为参数传入__index元方法,return一个返回值

local mt = {}
--第一个参数是表自己,第二个参数是调用的索引
mt.__index = function(t,key)
    return "it is "..key
end

t = {1,2,3}
--输出未定义的key索引,输出为nil
print(t.key)
setmetatable(t,mt)
--设置元表后输出未定义的key索引,调用元表的__index函数,返回"it is key"输出
print(t.key)

结果:

nil
it is key

作为table:
查找__index元方法表,若有该索引,则返回该索引对应的值,否则返回nil

local mt = {}
mt.__index = {key = "it is key"}

t = {1,2,3}
--输出未定义的key索引,输出为nil
print(t.key)
setmetatable(t,mt)
--输出表中未定义,但元表的__index中定义的key索引时,输出__index中的key索引值"it is key"
print(t.key)
--输出表中未定义,但元表的__index中也未定义的值时,输出为nil
print(t.key2)

结果:

nil
it is key
nil

__newindex

当为table中一个不存在的索引赋值时,会去调用元表中的__newindex元方法
作为函数
__newindex是一个函数时会将赋值语句中的表、索引、赋的值当作参数去调用。不对表进行改变

local mt = {}
--第一个参数时表自己,第二个参数是索引,第三个参数是赋的值
mt.__newindex = function(t,index,value)
    print("index is "..index)
    print("value is "..value)
end

t = {key = "it is key"}
setmetatable(t,mt)
--输出表中已有索引key的值
print(t.key)
--为表中不存在的newKey索引赋值,调用了元表的__newIndex元方法,输出了参数信息
t.newKey = 10
--表中的newKey索引值还是空,上面看着是一个赋值操作,其实只是调用了__newIndex元方法,并没有对t中的元素进行改动
print(t.newKey)

结果:

it is key
index is newKey
value is 10
nil

作为table
__newindex是一个table时,为t中不存在的索引赋值会将该索引和值赋到__newindex所指向的表中,不对原来的表进行改变。

local mt = {}
--将__newindex元方法设置为一个空表newTable
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--对t中不存在的索引进行负值时,由于t的元表中的__newindex元方法指向了一个表,所以并没有对t中的索引进行赋值操作将,而是将__newindex所指向的newTable的newKey索引赋值为了"it is newKey"
t.newKey = "it is newKey"
print(t.newKey,newTable.newKey)

结果:

nil nil
nil it is newKey

rawget 和 rawset

有时候我们希望直接改动或获取表中的值时,就需要rawget和rawset方法了。
rawget可以让你直接获取到表中索引的实际值,而不通过元表的__index元方法。

local mt = {}
mt.__index = {key = "it is key"}
t = {}
setmetatable(t,mt)
print(t.key)
--通过rawget直接获取t中的key索引
print(rawget(t,"key"))

结果:

it is key
nil

rawset可以让你直接为表中索引的赋值,而不通过元表的__newindex元方法。

local mt = {}
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--通过rawset直接向t的newKey索引赋值
rawset(t,"newKey","it is newKey")
print(t.newKey,newTable.newKey)

结果:

nil nil
it is newKey    nil

元表的使用场景

作为table的元表

通过为table设置元表可以在lua中实现面向对象编程。

作为userdata的元表

通过对userdata和元表可以实现在lua中对c中的结构进行面向对象式的访问。

 

弱引用table

 

与python等脚本语言类似地,Lua也采用了自动内存管理(Garbage Collection),一个程序只需创建对象,而无需删除对象。通过使用垃圾收集机制,Lua会自动删除过期对象。垃圾回收机制可以将程序员从C语言中常出现的内存泄漏、引用无效指针等底层bug中解放出来。

我们知道Python的垃圾回收机制使用了引用计数算法,当指向一个对象的所有名字都失效(超出生存期或程序员显式del了)了,会将该对象占用的内存回收。但对于循环引用是一个特例,垃圾收集器通常无法识别,这样会导致存在循环引用的对象上的引用计数器永远不会变为零,也就没有机会被回收。

一个在python中使用循环引用的例子:

class main1:
    def __init__(self):
        print('The main1 constructor is calling...')
    def __del__(self):
        print('The main1 destructor is calling....')

class main2:
    def __init__(self, m3, m1):
        self.m1 = m1
        self.m3 = m3
        print('The main2 constructor is calling...')
    def __del__(self):
        print('The main2 destructor is calling....')

class main3:
    def __init__(self):
        self.m1  = main1()
        self.m2 = main2(self, self.m1)
        print('The main3 constructor is calling...')
    def __del__(self):
        print('The main3 destructor is calling....')

# test
main3()
       

输出内容为:

The main1 constructor is calling...
The main2 constructor is calling...
The main3 constructor is calling...

可以看出,析构函数(__del__函数)没有被调用,循环引用导致了内存泄漏。

 

垃圾收集器只能回收那些它认为是垃圾的东西,不会回收那些用户认为是垃圾的东西。比如那些存储在全局变量中的对象,即使程序不会再用到它们,但对于Lua来说它们也不是垃圾,除非用户将这些对象赋值为nil,这样它们才能被释放。但有时候,简单地清除引用还不够,比如将一个对象放在一个数组中时,它就无法被回收,这是因为即使当前没有其他地方在使用它,但数组仍引用着它,除非用户告诉Lua这项引用不应该阻碍此对象的回收,否则Lua是无从得知的。

table中有key和value,这两者都可以包含任意类型的对象。通常,垃圾收集器不会回收一个可访问table中作为key或value的对象。也就是说,这些key和value都是强引用,它们会阻止对其所引用对象的回收。在一个弱引用table中,key和value是可以回收的。

弱引用table(weak table)是用户用来告诉Lua一个引用不应该阻碍对该对象的回收。所谓弱引用,就是一种会被垃圾收集器忽视的对象引用。如果一个对象的引用都是弱引用,该对象也会被回收,并且还可以以某种形式来删除这些弱引用本身。

弱引用table有3种类型:

1、具有弱引用key的table;
2、具有弱引用value的table;
3、同时具有弱引用key和value的table;

table的弱引用类型是通过其元表中的__mode字段来决定的。这个字段的值应为一个字符串:
如果包含'k',那么这个table的key是弱引用的;
如果包含'v',那么这个table的value是弱引用的;

 

弱引用table的一个例子,这里使用了collectgarbage函数强制进行一次垃圾收集:

a = {1,4, name='cq'}

setmetatable(a, {__mode='k'})

key = {}
a[key] = 'key1'

key = {}
a[key] = 'key2'

print("before GC")
for k, v in pairs(a) do
    print(k, '\t', v)
end

collectgarbage()

print("\nafter GC")
for k, v in pairs(a) do
    print(k, '\t', v)
end

输出:

before GC
1                       1
2                       4
table: 0x167ba70                        key1
name                    cq
table: 0x167bac0                        key2

after GC
1                       1
2                       4
name                    cq
table: 0x167bac0                        key2

在本例中,第二句赋值key={}会覆盖第一个key,当收集器运行时,由于没有地方在引用第一个key,因此第一个key就被回收了,并且table中的相应条目也被删除了。至于第二个key,变量key仍引用着它,因此它没有被回收。

注意,弱引用table中只有对象可以被回收,而像数字、字符串和布尔这样的“值”是不可回收的。

 

 

备忘录(memoize)函数是一种用空间换时间的做法,比如有一个普通的服务器,每当它收到一个请求,就要对代码字符串调用loadstring,然后再调用编译好的函数。不过,loadstring是一个昂贵的函数,有些发给服务器的命令有很高的频率,例如"close()",如果每次收到一个这样的命令都要调用loadstring,那还不如让服务器用一个辅助的table记录下所有调用loadstring的结果。

备忘录函数的例子:

local results = {}

setmetatable(results, {__mode='v'})

function mem_loadstring(s)

    local res = results[s]

    if res == nil then
        res=assert(loadstring(s))
        results[s]=res
    end

    return res
end 

local a = mem_loadstring("print 'hello'")
local b = mem_loadstring("print 'world'")

a = nil

collectgarbage()

for k,v in pairs(results) do
    print(k, '\t', v)
end

例子中,table results会逐渐地积累服务器收到的所有命令及其编译结果。经过一定时间后,会耗费大量的内存。弱引用table正好可以解决这个问题,如果results table具有弱引用的value,那么每次垃圾收集都会删除所有在执行时未使用的编译结果。

 

lua元表一文中,提到过如何实现具有默认值的table。如果要为每一个table都设置一个默认值,又不想让这些默认值持续存在下去,也可以使用弱引用table,如下面的例子:

local defaults = {}

setmetatable(defaults, {__mode='k'})

local mt = {__index=function(t) return defaults[t] end}

function setDefault(t, d)
    defaults[t] = d
    setmetatable(t, mt)
end 


local a = {}
local b = {}

setDefault(a, "hello")
setDefault(b, "world")

print(a.key1)
print(b.key2) 

b = nil
collectgarbage()

for k,v in pairs(defaults) do
    print(k,'\t',v)
end

弱引用的应用:lua中检测内存泄露

在lua中检测内存泄露,也分两个层次,

层次1:同时检测lua层中的lua对象的泄露和因为lua层而导致的c对象的泄露。

层次2:仅检测lua层中的lua对象的泄露。

做到满足层次1的内存泄露检测相对略微复杂,不过原理还是比较简单的,即扫描两个时间点时的lua state并获得快照,之后比较两张快照,后一张中多出来的内存引用就是后一张那个时间点相对前一张那个时间点多分配的内存。那如果这两个时间点在功能逻辑上是一致的(比如前一个时间点是进入某副本前,后一个时间点是退出某副本后),那么多出来的内存引用就是泄露出来的内存。

这里来重点讨论层次2的实现,仅检测lua层中lua对象的泄露这一点也是很重要的。代码规模一大,想通过敏锐的视力和严谨的头脑分析能力来检测出内存泄露是很困难的,因此需要一定的工具来为自己提供帮助。这里可以利用lua提供的“弱引用”。“弱引用”是这样的一种引用,其对某个对象的引用并不会对lua gc机制对该对象的垃圾回收造成影响,也就是说某对象若其只存在弱引用,那么该对象会被gc回收——简单来说,gc会无视弱引用。

因此我们可以建立一个全局的内存泄露监控弱引用表,其对键值和内容值的引用为“弱引用”(即其metatable中的__mode元属性值为"kv"),把我们关心的对象放置到该表中。然后等到某一个我们认为此对象应该已经被回收的时刻查看一下该表中是否还存在此对象即可:若不存在,说明该对象被正确的回收了;若存在说明该对象未被正确的回收,简言之,该对象泄露了。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
 本套课程分为基础与中级两部分,分别就lua语言的各方面知识点进行探讨,学习完本套课程,对于后续Xlua(Tolua等框架)技术的学习提供强大的语言技术保证。       本套lua课程采用入门与商业级两种开发IDE进行教学:入门级的SciTE内置IDE与商业级的IDEA lua插件。本套课程学习完毕,对于除了传统手游外,在VR、AR、商业级大型应用程序、嵌入式设备开发等领域都有较强的指导作用。           《lua中级篇》分为:“函数的进阶”、“字符串进阶”、“Table进阶”、“元表”、“OOP面向对象”、“协同程序”、“IO操作”、“调试与运行”等八个大的章节,详细深入讲解lua开发的方方面面。        内容包含lua可变参数、闭包、模块、函数尾调用、字符串模式匹配、字符串不变性原理、矩阵、链表、元表详解与应用、协同的生命周期与生产消费者问题、lua文件各种读写操作、lua执行外部代码与错误异常处理垃圾收集机制等。       最后,lua中级篇的学习,对于广大学员开发商业级lua热更新技术,具有不可替代的重要作用! 热更新系列(技术含量:中高级):B:《热更新框架设计之Xlua基础视频课程》https://edu.csdn.net/course/detail/27110C:《热更新框架设计之热更流程与热补丁技术》https://edu.csdn.net/course/detail/27118D:《热更新框架设计之客户端热更框架(上)》https://edu.csdn.net/course/detail/27132E:《热更新框架设计之客户端热更框架(中)》https://edu.csdn.net/course/detail/27135F:《热更新框架设计之客户端热更框架(下)》https://edu.csdn.net/course/detail/27136 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值