Lua:深拷贝与浅拷贝,强引用与弱引用

本文详细介绍了Lua中的深拷贝和浅拷贝概念,着重分析了不同基本类型在拷贝过程中的行为。通过一个DeepCopy函数展示了如何实现深拷贝,特别关注了table类型的处理,包括元表的处理。同时,提供了单元测试以验证拷贝的正确性,并探讨了Lua中的引用类型特性,包括强引用和弱引用的概念及其影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述:

浅拷贝:tabB = tabA ,相当于是对象起别名,或者说赋值指针,tabA的任何改动也会实装在tabB上;
深拷贝:tabC = DeepCopy(tabA)。,遍历k,v在tabA创建。遍历只需要对 table 类型进行递归拷贝即可。其它所有类型直接用赋值拷贝或浅拷贝。
https://blog.mutoo.im/2015/10/deepclone-in-lua/
在 lua 中有 8 种基本类型,分别是:nil、boolean、number、string、userdata、function、thread、table。

赋值拷贝

其中通过直接赋值能满足我们对拷贝的定义的类型有:nil、boolean、number、string、function。对于前三者没有什么好说的,它们就是简单值类型,不会有什么操作能间接影响它们的副本。
对于 string,虽然它本身不是简单值类型,但在 lua 中有特殊的内存管理方式,不能直接去修改它的值,而且任何影响它的操作都会创建新的副本——不会影响本体,所以它符合我们对拷贝的定义。
还有 function,它也不是简单值类型,但是即使多个不同的变量引用它也没有关系,因为没有什么操作能修改或影响它。同一段代码,在程序中只需要有一个实例即可。所以我认为它也符合我们对拷贝的定义。

非赋值拷贝

另外三种 lua 基本类型 userdata、thread、table 都是非简单值类型。其中 table 可以说是 lua 数据结构的根基,要实现其它的数据结构都要依赖它,在 lua 程序中模拟面向对象类也离不开它,然而它不能简单通过赋值进行拷贝,而是需要创建一个新的 table 并将原 table 的 key-value 递归拷贝,此外还要考虑元表等相关处理
通过对 lua 基本类型的分析,可知只需要对 table 类型进行递归拷贝即可。其它所有类型直接用赋值拷贝或浅拷贝。
但是 table 有一些特性需要注意:

  1. 拷贝后的 table 应与原 table 具有相同的元表;
  2. 元表不需要递归拷贝;

深拷贝代码

function DeepCopy(object)
    -- 已经复制过的table,key为复制源table,value为复制后的table
    -- 为了防止table中的某个属性为自身时出现死循环
    -- 避免本该是同一个table的属性,在复制时变成2个不同的table(内容同,但是地址关系和原来的不一样了)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= 'table' then -- 非table类型都直接返回
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end 
        local new_table = {}
        lookup_table[object] = new_table
        for k,v in pairs(object) do
            new_table[_copy(k)] = _copy(v) 
        end 
        -- 这里直接拿mt来用是因为一般对table操作不会很粗暴的修改mt的相关内容
        return setmetatable(new_table, getmetatable(object))
    end 
    return _copy(object)                    
end

单元测试1:表中key为非table,value为值

tabA = { x= 1}
tabB = tabA

print(tabB.x) --1

tabA.x = 2

print(tabB.x) --2


tabC = DeepCopy(tabA)


print(tabC.x) --2

tabA.x = 3

print(tabC.x) --2

执行理解

  1. tabC = DeepCopy(tabA)
  2. tabA不是table,接着执行
  3. tabA不在记忆表Look中,接着执行
  4. Look[tabA] = newTab,这里newTab是新建的地址
  5. 遍历tabA,newTab.x = 1,因为是值,直接返回
  6. newTab的元表为tabA的元表
  7. 返回newTab

单元测试2:key为非table,value为table

tabA = { x= 1,inside = { y =1}}
tabB = tabA

print(tabB.inside.y) --1

tabA.inside.y = 2

print(tabB.inside.y) --2

tabC = DeepCopy(tabA)

print(tabC.inside.y)--2

tabA.inside.y = 3

print(tabC.inside.y) --2

代码执行顺序:

  1. tabC = DeepCopy(tabA)
  2. tabA不是table,接着执行
  3. tabA不在记忆表Look中,接着执行
  4. Look[tabA] = newTab,这里newTab是新建的地址
  5. 遍历tabA,newTab.x = 1,因为是值,DeepCopy会直接返回,得到 newTab.x = 1
  6. 接着遍历tabA,得到newTab.inside
  7. 然后DeepCopy(inside的value)时,触发递归
  8. tabInside不在记忆表Look中,接着执行新建一个newTab1
  9. 遍历tabA.inside,newTab1.y = 1
  10. 递归到这,相当于返回了第6步_copy(v),返回了个新表newTab1,替代原来tabA.inside
  11. 到这里,返回newTab,里面有.x ,.inside

代码解析

  1. tabB = tabA ,相当于是对象起别名,或者说赋值指针,tabA的任何改动也会实装在tabB上
  2. lookup_table相当于一个记忆表,里面的key为table的地址,这样可以保证每一个key都是唯一的,里面只包含,一个是要深拷贝的tabA,另外是tabA的里面的域为table 的。自己里面的属性是自己,要防止死循环
  3. _copy里面执行逻辑,如果复制的是值,直接返回,如果复制的是表,在记忆表里找,没找到,接着创建一个新表。
    4.表里有表,就是执行_copy(v)时,进行递归,直到返回一个塞好的新newTab1

lua中强引用

a = {c = 1}
b = a
a = nil
print(b.c)

输出1
a与b指向同一个内存
a = nil只是a不指向了
b还存在

lua中的table是引用类型,更准确地说,是强引用类型。如下第二段代码,在内存中有一个{name = “123”}的table,并用a和b[1]指向它,然后置空a,此时就只剩下b[1]指向它了。这种引用方式和我们所认知的引用是一样的。值得一提的是,这里的a = nil为什么不等同于{name = “123”} = nil呢,意思是将指向的这个表删掉呢?因为lua是具备自动内存管理的,我们只管创建,删除操作是lua自动进行的,因此这里的a = nil并不是删除表,而是指将a对这张表的引用去掉,当没有地方引用这张表时,这张表就会被lua自动清掉。

a = {name = "123"}
b = {}
b[1] = a
a = nil
print(b[1].name)--123

lua中弱引用

弱引用不能防止对象被回收。如果一个对象只存在弱引用,那么它就会被回收。lua中通过弱引用table来实现弱引用。弱引用table有三种形式:

1.key值弱引用。设置方法为setmetatable(b, {__mode = “k”})

2.value值弱引用。设置方法为setmetatable(b, {__mode = “v”})

3.key和value值弱引用。设置方法为setmetatable(b, {__mode = “kv”})

b的value对其指向的对象的引用是弱引用,而b的key对其指向的对象的引用仍然是强引用(对于table来说,其key和value可以指向任何类型的对象,除了key不能指向nil)。{name = “123”}这个table只存在弱应用b[1],所以被回收

a = {name = "123"}
b = {}
setmetatable(b, {__mode = "v"}) --add
b[1] = a
a = nil
collectgarbage()
for k,v in pairs(b) do
    print(k) --无
    print(v) --无
end
<think>好的,用户想了解Lua强引用弱引用的区别及使用场景。首先,我需要回顾一下Lua中的引用机制。强引用是最常见的引用类型,只要存在强引用,对象就不会被垃圾回收器回收。而弱引用则不同,即使存在弱引用,如果对象没有其他强引用,垃圾回收时会被回收。 接下来,我得确认用户的问题重点。他们可能是在处理内存管理问题,或者遇到了对象无法释放的情况,想通过弱引用来优化内存使用。需要详细解释两者的区别,并举例说明使用场景,比如缓存、对象管理、监听器模式等。 还要注意Lua弱引用表的设置方式,通过元表的__mode字段,可以设置为"k"、"v"或"kv"。这部分需要具体说明,确保用户知道如何正确声明弱引用表。 另外,用户提供的引用资料中提到了弱引用表的使用和垃圾回收机制,需要将这些信息整合到回答中,确保引用正确。例如,引用[3]提到弱引用表不会增加引用计数,这需要强调。 最后,生成相关问题时要围绕Lua内存管理、弱引用的具体实现和其他相关机制,比如元表的使用。确保问题对用户进一步学习有帮助。</think>### Lua强引用弱引用的区别及使用场景 #### 一、核心区别 1. **强引用** - 对象被强引用时,其生命周期由引用计数决定 - 即使内存不足,只要有强引用存在,对象就不会被垃圾回收器(GC)回收 - 示例:普通表的键值对默认都是强引用 ```lua local strongTable = {} strongTable.key = {} -- 创建强引用关联 ``` 2. **弱引用** - 通过弱引用表(weak table)实现,需显式声明 - 不会阻止GC回收对象,当对象仅被弱引用时会被自动回收 - 声明方式:通过元表设置`__mode`字段 ```lua local weakTable = {} setmetatable(weakTable, {__mode = "k"}) -- 声明键为弱引用 ``` #### 二、技术特性对比 | 特性 | 强引用表 | 弱引用表 | |--------------|--------------------------|-----------------------------| | GC行为 | 阻止对象回收 | 允许对象回收[^3] | | 内存管理 | 需要手动解除引用 | 自动清理失效条目 | | 典型应用 | 常规数据存储 | 缓存/临时数据管理 | | 引用计数影响 | 增加引用计数 | 不影响引用计数[^3] | | 声明方式 | 默认 | 需设置元表`__mode`字段 | #### 三、使用场景 1. **缓存系统** 适合存储临时计算结果,当内存不足时自动释放旧缓存: ```lua local cache = {} setmetatable(cache, {__mode = "v"}) -- 值弱引用 function getBigData(id) if not cache[id] then cache[id] = computeExpensiveData(id) end return cache[id] end ``` 2. **对象管理** 管理临时对象时避免内存泄漏: ```lua local tempObjects = setmetatable({}, {__mode = "k"}) function createTempObject() local obj = {} tempObjects[obj] = true return obj end ``` 3. **监听器模式** 自动移除失效的监听器引用: ```lua local listeners = setmetatable({}, {__mode = "k"}) function addListener(target, callback) listeners[callback] = target end ``` #### 四、注意事项 1. `__mode`字段可选值: - `"k"`:键为弱引用 - `"v"`:值为弱引用 - `"kv"`:键值均为弱引用 2. 弱引用表条目会在GC时自动清除 3. 不应将系统关键对象存入弱引用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四夕立羽

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值