概述:
浅拷贝: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 有一些特性需要注意:
- 拷贝后的 table 应与原 table 具有相同的元表;
- 元表不需要递归拷贝;
深拷贝代码
代码语言:javascript复制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为值
代码语言:javascript复制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
执行理解
- tabC = DeepCopy(tabA)
- tabA不是table,接着执行
- tabA不在记忆表Look中,接着执行
- Look[tabA] = newTab,这里newTab是新建的地址
- 遍历tabA,newTab.x = 1,因为是值,直接返回
- newTab的元表为tabA的元表
- 返回newTab
单元测试2:key为非table,value为table
代码语言:javascript复制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
代码执行顺序:
- tabC = DeepCopy(tabA)
- tabA不是table,接着执行
- tabA不在记忆表Look中,接着执行
- Look[tabA] = newTab,这里newTab是新建的地址
- 遍历tabA,newTab.x = 1,因为是值,DeepCopy会直接返回,得到 newTab.x = 1
- 接着遍历tabA,得到newTab.inside
- 然后DeepCopy(inside的value)时,触发递归
- tabInside不在记忆表Look中,接着执行新建一个newTab1
- 遍历tabA.inside,newTab1.y = 1
- 递归到这,相当于返回了第6步_copy(v),返回了个新表newTab1,替代原来tabA.inside
- 到这里,返回newTab,里面有.x ,.inside
代码解析
- tabB = tabA ,相当于是对象起别名,或者说赋值指针,tabA的任何改动也会实装在tabB上
- lookup_table相当于一个记忆表,里面的key为table的地址,这样可以保证每一个key都是唯一的,里面只包含,一个是要深拷贝的tabA,另外是tabA的里面的域为table 的。自己里面的属性是自己,要防止死循环
- _copy里面执行逻辑,如果复制的是值,直接返回,如果复制的是表,在记忆表里找,没找到,接着创建一个新表。 4.表里有表,就是执行_copy(v)时,进行递归,直到返回一个塞好的新newTab1
lua中强引用
代码语言:javascript复制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自动清掉。
代码语言:javascript复制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],所以被回收
代码语言:javascript复制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