为什么将一个表赋值给另一个表会引起问题?

为什么在 Lua 中我们不能直观地复制表,像这样:

a = {
  a = {},
  b = {},
}

b = {}
b = a.b

我试图这样做时遇到了一些奇怪的错误。如果我使用以下的表克隆函数,它就可以正常工作,但我不明白为什么需要使用克隆函数或者这种方式为什么是最佳实践。

很难描述我尝试使用第一种方法遇到的错误,但基本上,如果我尝试在 a.b 部分的 b = a.b 中添加其他键值,那么其他键值就不总是变成我设定的值。

function deepCopy(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for index, value in pairs(object) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

然后使用以下方法就可以消除任何错误。

b = deepCopy(a.b)
点赞
用户3574628
用户3574628
  • 变量持有引用,而不是整个表格。
  • 复制引用比复制整个表格要更高效。
  • 函数调用有效地将参数分配给该函数的参数,因此如果赋值进行完整的复制,将无法编写修改表格的函数。
  • 通常,当我们将表格分配给某些内容时,我们要么(a)不打算修改表格,要么(b)明确打算使用至少一个变量来修改基础表格。请参见上一点的函数。这意味着默认情况下进行完整复制将浪费资源。

我的建议是,只有在确实需要时才复制表格,并首选浅复制,除非确实需要深复制。事实上,当我需要复制表格时,我通常编写一个专门的复制函数,以便我不会复制更多的内容。

2019-08-30 15:23:49
用户734069
用户734069

在 Lua 中,表是一个值,每个不同的表都有一个不同的值。表的值用于确定其内容,但是表的内容概念上不是表的值。也就是说,要访问表的内容,需要使用表的值,但表的值与其内容不是同一回事。

表的值可以存储在任何变量中。同样,该值用于标识该表并访问该表的内容,但逻辑上它并不是表的内容。

考虑下面的例子:

tbl1 = { 1, 2, 3 }
tbl2 = tbl1
tbl3 = { 1, 2, 3 }

tbl1tbl2 的值相同;这意味着它们引用相同的表,因此可以通过任何一个变量访问该表的内容。因此,tbl1 [2] tbl2 [2] 不仅返回2;它们都访问相同的表。

tbl3tbl1 不是同一个表。它们的内容可能具有逻辑上相同,但就 Lua 而言,它们是不同的表。操作存储在 tbl3 中的表的内容不会影响查看存储在 tbl1 tbl2 中的表的任何人。

那么,为什么将表存储到变量中不会复制表的内容?有几个原因。

  1. 深层复制是昂贵的。如果所有副本都是深层的,甚至不能执行简单的 return {1, 2, 3} 而不执行复制。毫无意义的复制,因为没有其他变量可以访问该表(因为它是在现场创建的)。为什么浪费性能?将同样适用于将表作为参数传递给函数或任何其他数量的事情。

  2. 仅深层复制会阻止有用的事情,例如在不同位置访问相同的表。如果每个表副本都是深的,那么您如何获得简单的模块表的本地副本?您无法让表“成员函数”返回对象内部的表,因此您可以使用该返回来操作该对象中的数据,因为该返回将不得不复制表。因此,只能通过直接成员函数修改表对象。

深度复制是一种有用的工具。但它不是默认值,因为它不应该是。大多数复制表的情况不需要它,而用户需要一种可以从多个位置访问表的方法。

也没有深复制的标准函数或机制。原因很简单:可以使用许多方法进行深度复制,从简单到复杂不等。例如,您的简单 deepCopy 函数会对存储它自己(递归地)的表发生错误:

me = { a = 4, other = {} }
me.other.me = me

这是 100% 的有效性,但是您的 deepCopy 函数将在其上发生故障。有一些方法可以实现 deepCopy,使其处理这种情况,但是它们是复杂且昂贵的。大多数用户不需要能够处理递归对象的 deepCopy

如果 Lua 的标准库中有深层复制函数,那么它将处理每个这样的情况(因此是昂贵的),或者它将是一个较简单的函数,可以在任何角落情况(在表格中具有多个对同一表格的引用等)下出错。

因此,最好让使用深度复制的潜在用户坐下来,并确切地决定他们想处理哪些情况,哪些情况不需要。

2019-08-30 15:48:15