有关深拷贝、浅拷贝在Lua中的一系列事情。

为什么要写深拷贝:

在《Lua程序设计(第四版)》第五章开头,作者描述了表的性质:

可以认为,表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此之外,Lua语言不会进行隐藏的拷贝(hidden copies)或创建新的表。

已经描述的很清楚了,对于表的操作,Lua语言不会进行深拷贝(即Lua语言拷贝的是对象的引用而非整个对象本身。)

先看一下Lua的源码:

1
2
3
4
5
6
7
8
9
10
Table *luaH_new (lua_State *L) {
GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
Table *t = gco2t(o);
t->metatable = NULL;
t->flags = cast_byte(~0);
t->array = NULL;
t->sizearray = 0;
setnodevector(L, t, 0);
return t;
}

当生成一个Table时,会返回一个Table类型的指针(t),可以看出,如果在Lua中进行表的赋值的话,实际上是将一个表的指针赋给了另一个变量,而这两个量指向的是同一个地址,也就是说,这次赋值并没有创造出一个新的副本出来,而只是给原来的变量起了一个别名。

1
2
3
4
5
6
7
8
9
10
11
12
table1 = {1,2,3}
table2 = table1
table.insert(table1,4)

for i,v in ipairs(table2) do
print(v)
end

>> 1
2
3
4

如果要复制出一份独立的表,该怎么写呢?

深拷贝与浅拷贝:

深拷贝(DeepCopy):

拷贝整个对象本身,在上例的Lua代码中,如果table2对table1进行深拷贝,则改变table1不会同时改变table2,即table2是table1完全独立的一份复制。

深拷贝的实现方式:

具体讲解可以参考wiki上的这一篇:CopyTable

一种快速但不怎么好的实现:

这个版本的clone使用到了标准库中的unpack函数,这个函数会返回列表中的元素。

1
2
3
4
5
6
7
8
function  table.cloneorg
return { table.unpackorg)}
end

local abc = {5,12,1}
local def = table.cloneabc
table.sortdef
printabc [2],def [2])- 12 5
Shallow Copy:

这个实现很简单,但是有一些缺陷:它只复制了顶层的值,没有对更加深层的元素、元表和特殊类型(如userdata或coroutines)进行处理,它也容易受到__pairs元方法的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
function shallowcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end
Deep Copy:

这个版本的实现可以复制所有层级的元素,是一个简单的递归实现,并且会将原表的元表复制一份到新表的元表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end

将表的拷贝存储在copies中,由原始的表索引,这是通过创建已复制的表的哈希表并将其作为第二个参数提供给deepcopy函数来完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- Save copied tables in `copies`, indexed by original table.
function deepcopy(orig, copies)
copies = copies or {}
local orig_type = type(orig)
local copy
if orig_type == 'table' then
if copies[orig] then
copy = copies[orig]
else
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key, copies)] = deepcopy(orig_value, copies)
end
copies[orig] = copy
setmetatable(copy, deepcopy(getmetatable(orig), copies))
end
else -- number, string, boolean, etc
copy = orig
end
return copy
end