浅拷贝与深拷贝
浅拷贝与深拷贝的区别
对象包含自身数据(可简单视为值类型)、外部引用类型数据
在 lua 中有 8 种基本类型,分别是:nil、boolean、number、string、userdata、function、thread、table。
浅拷贝
- 浅拷贝复制对象本身(如Lua中的表)、也会 复制 对象中外部引用类型数据(比如表、数组等)的 引用 。
- 如果原始对象中的某些数据是引用类型(比如表、字符串等),那么在浅拷贝后,新对象和原始对象中的这些引用类型值仍然指向同一块内存地址。
- 如果你修改了新对象中某个引用类型值的内部内容,原始对象中的相应值也会被修改,因为它们实际上是同一个对象。
举例子:
1、拷贝对象的类型是 string、number、boolean 等基本类型的时候,会进行复制,创建出另外一个新的对象,拷贝出来的对象和原来的对象互不影响,所以修改拷贝出来的对象的值不会影响到原来的对象的值!
2、拷贝对象的类型是 table 的时候,则是进行了引用指向(可以简单的认为是指针指向),拷贝出来的对象和原来的对象实际上是同一个对象,所以修改拷贝出来的对象中的元素的值也会使原来的对象中元素的值发生改变!
深拷贝
- 深拷贝是完全复制一个对象(自身数据 + 外部引用数据)中的所有数据,包括对象中所有的嵌套对象。
- 新创建的对象和原始对象在内存中完全独立。
- 原始对象中的部分值 是 引用类型,深拷贝也会递归地复制这些引用类型的值,确保新对象和原始对象之间没有任何内存共享。
- 修改新对象中的任何值都不会影响到原始对象。
如何实现浅拷贝与深拷贝
浅拷贝
赋值拷贝
- 过直接赋值实现浅拷贝的数据类型有:nil、boolean、number、string、function。
- nil、boolean、number 是简单值类型,拷贝后不会间接影响它们的副本。
- string,本身不是简单值类型,不能直接修改它的值,对 string 进行拷贝会自动创建新的副本(不会影响本体)。
- function,也不是简单值类型,但是即使多个不同的变量引用它,没有什么操作能修改或影响它。
在Lua中,实现浅拷贝(shallow copy)相对简单,因为表格(tables)的赋值操作本质上就是浅拷贝。
当你将一个表格赋值给另一个变量时,你得到的是原始表格的一个引用,而不是它的一个全新副本。
但是,如果你想要显式地创建一个新的表格,并将原始表格的内容(但不包括其内容中可能存在的表格的引用)复制到新表格中,你可以使用一个简单的循环来遍历原始表格的键值对,并将它们添加到新表格中。
function shallow_copy(obj)
if type(obj) ~= 'table' then
-- 如果不是表格,则直接返回原对象(对于基本类型,这等同于浅拷贝)
return obj
end
-- 创建一个新的空表格
local copy = {}
-- 遍历原表格的每个键值对,并复制到新表格中
for key, value in pairs(obj) do
copy[key] = value -- 这里没有进行递归,所以是浅拷贝
end
-- 返回新表格
return copy
end
-- 测试浅拷贝
local original = {
a = 1,
b = { c = 2 } -- b是一个嵌套表格
}
local copy = shallow_copy(original)
-- 修改原始表格的内容
original.a = 10
original.b.c = 20
-- 修改拷贝表格的内容(注意,这里修改了嵌套表格的内容)
copy.a = 100 -- 这将只影响拷贝表格
copy.b.c = 200 -- 这将同时影响原始表格和拷贝表格,因为b是浅拷贝的
-- 输出结果以验证
print(original.a) -- 输出 10
print(copy.a) -- 输出 100
print(original.b.c) -- 输出 200,因为copy.b.c的修改也影响了original.b.c
print(copy.b.c) -- 输出 200
分析:
shallow_copy 函数遍历了 original 表格的每个键值对,并将它们添加到了一个新的空表格 copy 中。
但是,由于 original.b 是一个表格,所以 copy.b 实际上是 original.b 的同一个引用。
因此,当你修改 copy.b.c 时,这个修改也会反映到 original.b.c 上。
深拷贝
非赋值拷贝
- userdata、thread、table 都是非简单值类型。
- table 它不能简单通过赋值进行拷贝,而是需要创建一个新的 table 并将原 table 的 key-value 递归拷贝。
- thread 和 userdata 比较复杂。在游戏项目中通常只对值对象(Value Object)进行深拷贝,通常值对象只存放一些简单值类型和 table,并不会包括这两种值类型。
在Lua中实现深拷贝(deep copy)不像在一些其他语言(如Python)中那样直接有一个内置函数,但在Lua中你可以通过递归地遍历数据结构(如表格和数组)并创建新的结构来模拟深拷贝。
function deep_copy(obj, seen)
-- 初始化seen表,用于处理循环引用
seen = seen or {}
-- 如果obj是基本类型,直接返回
if type(obj) ~= 'table' then
return obj
end
-- 检查是否已处理过该对象,防止循环引用
if seen[obj] then
return seen[obj]
end
-- 创建一个新的空表格来存放拷贝
local copy = {}
seen[obj] = copy -- 标记为已处理
-- 遍历obj的每一个键值对
for key, value in pairs(obj) do
-- 递归调用deep_copy来处理嵌套表格
copy[deep_copy(key, seen)] = deep_copy(value, seen)
end
-- 返回拷贝的表格
return copy
end
-- 测试代码
local original = {
a = 1,
b = {
c = 2,
d = {
e = 3
}
},
f = { g = original } -- 循环引用测试
}
local copy = deep_copy(original)
-- 修改拷贝后的数据,看原数据是否受影响
copy.b.d.e = 4
copy.f.g.a = 100 -- 由于f.g是循环引用,修改这里将不会影响原数据的a
print(original.b.d.e) -- 输出 3,原数据未受影响
print(copy.b.d.e) -- 输出 4
print(original.f.g.a) -- 输出 1,原数据未受影响
print(copy.f.g.a) -- 输出 100,但由于循环引用,这里的修改不影响原数据