改变一个表似乎改变了另一个表

时间:2009-09-09 06:15:36

标签: data-structures lua

我正在编写一个lua脚本,它所做的一件事就是将一个表复制到一个表的表中,并对它应用几个转换。奇怪的是,当我稍后使用其中一个表(并修改其中一些属性)时,更改似乎也会显示在其他表中!代码:

-- thanks to http://stackoverflow.com/questions/1283388/lua-merge-tables/1283608#1283608
-- tableMerge:
-- merges two tables, with the data in table 2 overwriting the data in table 1
function tableMerge(t1, t2)
    for k,v in pairs(t2) do
        if type(v) == "table" then
            if type(t1[k] or false) == "table" then
                tableMerge(t1[k] or {}, t2[k] or {})
            else
                t1[k] = v
            end
        else
            t1[k] = v
        end
    end
    return t1
end

--tableCopy:
--takes a table and returns a complete copy including subtables.
function tableCopy(t)
    return tableMerge({}, t)
end

local t1 = { a = 1, b = true, c = "d", e = { f = 2 } }
local t2 = tableCopy(t1)
t2.b = false
t2.e.f = 1
print(t1.b) -- prints true as it should
print(t1.e.f) -- prints 1!

[由于其中包含的信息而删除了其他代码,这是对错误的良好复制]

这是我的代码中的错误还是什么?我无法理解......

2 个答案:

答案 0 :(得分:4)

这是Lua表的工作方式 - 它们不会被复制,只有对表的引用才会传递给函数或存储在表中。如果您熟悉.NET术语,则可以说表是“引用类型”。观察:

function modtable(t)
    t.hello = "world"
end

local t = { hello = "no!"; }
modtable(t)
print(t.hello)

这会打印“世界”,因为modtable函数获取对表的引用而不是副本。当您尝试将表存储在另一个表中时,会发生同样的事情

local t = { hello = "no!"; }
local bigT = { innerTable = t; }
bigT.innerTable.hello = "world"
print(t.hello)

t.hello = "double world";
print(bigT.innerTable.hello);

这将打印

world 
double world

因为t和bigT.innerTable基本上是同一个表。

如果您想要彼此独立修改的表的副本,您可以编写一个小函数来复制表

function deep_copy_table(t)
    local result = {}
    for k,v in pairs(t)
    do
        if (type(v) == "table")
        then
            result[k] = deep_copy_table(v)
        else
            result[k] = v
        end
    end
    return result
end

local t = { hello = "no!"; }
local bigT = { innerTable = deep_copy_table(t); }
bigT.innerTable.hello = "world"
print(t.hello)

t.hello = "double world";
print(bigT.innerTable.hello);

这将打印“不!”和“世界” - t和bigT.innerTable现在是不同的表。

答案 1 :(得分:0)

好的,对不起双重答案 - 之前的答案仍然很好,尽管这是对不同问题的答案。

我在你的代码中发现了问题,它是tableMerge函数的预期:

    if type(v) == "table" then
            if type(t1[k] or false) == "table" then
                    tableMerge(t1[k] or {}, t2[k] or {})
            else
                    t1[k] = v -- this is the problematic line
            end
    else

因此,如果 t1 [k] 不是表格,您只需将 v 指定给它,最后会引用 v 而不是副本。这有效地使您的tableCopy函数浅拷贝而不是深拷贝。因为你无论如何都要覆盖 t1 [k] ,这似乎是tableMerge函数的一个很好的实现:

function tableMerge(t1, t2)
    for k,v in pairs(t2) do
        if type(v) == "table" then
            if type(t1[k]) ~= "table" then -- if t1[k] is not a table, make it one
                t1[k] = {}
            end
            tableMerge(t1[k], t2[k])
        else
            t1[k] = v
        end
    end
    return t1
end

这应该有希望解决手头的问题。

如果您不介意,还可以对代码进行一些其他随意的思考:

  • 在mergeTable的原始实现中:type(t1[k] or false)t1[k] or false诡计在这里毫无意义,type()可以正常处理nil值(对于 nil 将返回“nil”)

  • t2 [k] 永远不能 nil 。 Lua中的表不能包含nil作为值,pairs()永远不会返回nil作为键或值的对。 t2[k] or false再次毫无意义。

  • 我在第一个脚本的末尾附近发现了这一行:table.remove(v.Weapons)。我猜你需要第二个参数来调用table.remove

哦,还有最后一条建议:不要只丢掉200行代码并期望人们为你调试 - 没人会打扰。尝试缩小有问题的代码。你已经怀疑tableMerge函数,如果你已经把它自己拿出来并对它进行了一个简单的测试你会发现问题。

快乐的编码!