9.Lua如何实现深拷贝
9.1 题目
Lua如何实现深拷贝?
9.2 深入解析
什么是深拷贝?
在Lua中,使用赋值运算符”=”进行拷贝的时候,分两种情况:
string、number、boolean这些基本类型,会进行复制,会创建一个新对象,拷贝出来的对象和原来的互不影响
local num1 = 5 local num2 = num1 num2 = 10 print(num1) -- 5 print(num2) -- 10 local str1 = "test123" local str2 = str1 str2 = "韬老狮" print(str1) -- test123 print(str2) -- 韬老狮
table类型,是直接进行的引用,拷贝出来的对象和原来是同一个对象,改一处另一处也会变化
local tbl = { x = 1, y = 2, z = 3 } local tb2 = tbl tb2.x = 4 print(tbl.x) -- 4 print(tb2.x) -- 4
因此,一般我们提到Lua中的深拷贝,一般都是希望对table类型的变量实现深拷贝。即拷贝后的内容变化,不会影响原来的内容。而Lua中并没有提供这样的api,因此我们一般会自己封装一个函数。
如何进行深拷贝?
进行table深拷贝整体的封装思路就是递归地遍历表的每一个元素,并且在遇到子表时,对子表也进行深拷贝。这样可以确保拷贝后的新表与原表完全独立,任何对新表的修改都不会影响到原表。
function clone(object)
-- 记录已经拷贝过的表,防止循环引用
-- 有可能表自己引用自己,如果不记录有可能无限递归
local lookup_table = {}
-- 递归拷贝函数
local function _copy(object)
-- 如果遇到的不是表则直接返回这个值(比如nil或者基本类型)
if type(object) ~= "table" then
return object
-- 如果在拷贝过的表中找到该对象,说明已经拷贝过,直接返回拷贝过的表
elseif lookup_table[object] then
return lookup_table[object]
end
-- 创建新表
local new_table = {}
-- 记录这个新表 表明object这个表已经拷贝过
lookup_table[object] = new_table
-- 递归拷贝每个键值对 对每个键值对都调用_copy拷贝
-- 之所以对键也调用_copy函数,是因为键也可能是一个表
for key, value in pairs(object) do
new_table[_copy(key)] = _copy(value)
end
-- 为拷贝表设置与原来的表相同的元表
return setmetatable(new_table, getmetatable(object))
end
return _copy(object)
end
local tb3 = { x = 1, y = 2, z = 3 }
local tb4 = clone(tb3)
tb4.x = 4
print(tb3.x) -- 1
print(tb4.x) -- 4
其中不太好理解的代码为:
-- 递归拷贝每个键值对 对每个键值对都调用_copy拷贝
-- 之所以对键也调用_copy函数,是因为键也可能是一个表
for key, value in pairs(object) do
new_table[_copy(key)] = _copy(value)
end
我们举个例子来理解这里的代码:
local original = {
a = { 1, 2, 3 },
b = "string",
c = 42,
d = { x = 1, y = 2 }
}
local copy = clone(original)
对于键a,值{1,2,3},调用_copy(a)返回a本身,调用_copy({1,2,3})会递归地创建一个新表{1,2,3}
对于键b,值”string”,调用_copy(b)返回b本身,调用_copy(“string”)会返回”string”
对于键c,值42,调用_copy(c)返回c本身,调用_copy(42)会返回42
对于键d,值{x=1,y=2},调用_copy(d)返回d本身,调用_copy({x=1,y=2})会递归地创建一个新表{x=1,y=2}
每次递归调用_copy函数时,都会对原表中的键和值进行深拷贝,并将结果插入到新表”new_table”中。这样就确保了新表和原表之间完全独立。
9.3 答题示例
在Lua中,对基本类型(number、string、boolean)的赋值是值拷贝,但对table的赋值只是引用拷贝。要实现深拷贝,我们一般自己写一个递归函数:
- 维护一个“已拷贝表”映射,防止循环引用无限递归;
- 对每个键和值都调用同一个拷贝函数,遇到非table直接返回原值,遇到table则新建表并递归拷贝;
- 最后用
setmetatable
保留原表的元表。示例:
function clone(obj) local lookup = {} local function _copy(o) if type(o) ~= "table" then return o end if lookup[o] then return lookup[o] end local t = {} lookup[o] = t for k,v in pairs(o) do t[_copy(k)] = _copy(v) end return setmetatable(t, getmetatable(o)) end return _copy(obj) end
这样
clone
出来的新table 就与原table 完全独立,修改新表不会影响原表。
9.4 关键词联想
- 值拷贝 vs 引用拷贝
- 递归遍历 table
- 防止循环引用
- lookup 映射表
- setmetatable / getmetatable
- 深拷贝与浅拷贝
- Lua 类型判断(type 函数)
- pairs 遍历 table
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com