Lua:高效复制表(深复制)

use*_*398 6 lua arguments parameter-passing deep-copy pass-by-reference-value

我尝试有效地制作 lua 表的副本。我编写了以下运行良好的函数 copyTable() (见下文)。但我想我可以使用函数的“按值传递”机制获得更有效的东西。我做了一些测试来探索这个机制:

function nop(x)
  return x
end

function noop(x)
  x={}
  return x
end

function nooop(x)
  x[#x+1]=4
  return x
end

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

tab={1,2,3}
print(tab)            -->table: 0x1d387e0 tab={1,2,3}
print(nop(tab))       -->table: 0x1d387e0 tab={1,2,3}
print(noop(tab))      -->table: 0x1e76f90 tab={1,2,3}
print(nooop(tab))     -->table: 0x1d387e0 tab={1,2,3,4}
print(tab)            -->table: 0x1d387e0 tab={1,2,3,4}
print(copyTable(tab)) -->table: 0x1d388d0
Run Code Online (Sandbox Code Playgroud)

我们可以看到,对表的引用通过函数(当我刚刚读取它或添加内容时)不变地传输,除了在 noop() 中,我尝试对现有的进行彻底修改。

我读了Bas Bossink和Michael Anderson这个 Q/A中的回答。关于将表或表作为参数传递,他们强调了“通过引用传递的参数”和“通过值和表传递的参数是引用”之间的区别,并举例说明了这种差异的出现。

但这到底意味着什么呢?我们是否有引用的副本,但这与传递 ref 有什么区别,因为指向并因此操作的数据仍然相同,而不是复制?nooop() 中的机制是特定于我们尝试对表影响 nil 时,特定于避免删除表还是在什么情况下触发(我们可以通过 nooop() 看到,当表已修改)?

我的问题:传递表的机制实际上是如何工作的?有没有一种方法可以更有效地复制表的数据,而不会增加我的 copyTable 的负担?

ktb*_*ktb 2

Lua 中参数传递的规则与 C 类似:一切都是按值传递,但表和用户数据作为指针传递。传递引用的副本在用法上看起来并没有那么不同,但它与通过引用传递完全不同。

比如你专门提出了这一部分。

function noop(x)
  x={}
  return x
end
print(noop(tab))      -->table: 0x1e76f90 tab={1, 2, 3}
Run Code Online (Sandbox Code Playgroud)

您正在将新表 [1] 的值分配给变量xx现在保存一个新的指针值)。您没有改变原始表,tab变量仍然保存指向原始表的指针值。当您返回时,noop您将传回新表的值,该表是空的。变量保存值,而指针是值,而不是引用。

编辑:

错过了你的另一个问题。不,如果你想深复制一个表,类似于你写的函数是唯一的方法。当表变大时,深度复制非常慢。为了避免性能问题,您可以使用“回滚表”之类的机制,该机制跟踪对它们所做的更改,以便可以在以后的时间点撤消它们(在回溯上下文的递归中非常有用)。或者,如果您只是需要防止用户破坏表内部结构,请编写“可冻结”特征。

[1] 假设{}语法是一个构造新表并返回指向新表的指针的函数。