Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/lua/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Lua:高效复制表(深度复制)_Lua_Arguments_Parameter Passing_Deep Copy_Pass By Reference Value - Fatal编程技术网

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

Lua:高效复制表(深度复制),lua,arguments,parameter-passing,deep-copy,pass-by-reference-value,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

我试图高效地复制一个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
我们可以看到,除了noop()中我尝试对现有表进行根本性修改之外,对表的引用是通过函数(当我只是读取它或添加内容时)不变地传输的

我读了一篇文章,然后回答了问题。关于将或表作为参数传递,他们强调了“由ref传递的参数”和“由值传递的参数和表是引用”之间的区别,并举例说明了这种区别

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


我的问题是:传递表格的机制到底是如何工作的?有没有一种方法可以更有效地复制表中的数据,而不必承担我的copyTable的负担?

传入Lua的参数规则类似于C:所有内容都是按值传递的,但表和用户数据是作为指针传递的。传递引用的副本在用法上并没有太大区别,但它与通过引用传递完全不同

例如,您专门提出了这一部分

function noop(x)
  x={}
  return x
end
print(noop(tab))      -->table: 0x1e76f90 tab={1, 2, 3}
您正在将新表[1]的值赋给变量
x
x
现在保存一个新指针值)。您没有修改原始表,
选项卡
变量仍然保存指向原始表的指针值。从
noop
返回时,将传回新表的值,该值为空。变量保存值,指针是值,而不是引用

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


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

如果您确定这3个假设(a)对“tab”(正在复制的表)有效:

  • 没有桌子钥匙

    t1 = {}
    tab = {}
    tab[t1] = value
    
  • 没有重复的表值

    t1 = {}
    tab = {}
    tab.a = t1
    tab.b = t1
    -- or
    -- tab.a.b...x = t1
    
  • 没有递归表:

    tab = {}
    tab.a = tab
    -- or
    -- tab.a.b...x = tab
    
  • 那么您提供的代码是最小的,并且几乎是尽可能高效的

    如果A1不适用(即您有表键),则必须将代码更改为:

    function copyTable(datatable)
      local tblRes={}
      if type(datatable)=="table" then
        for k,v in pairs(datatable) do 
          tblRes[copyTable(k)] = copyTable(v) 
        end
      else
        tblRes=datatable
      end
      return tblRes
    end
    
    function copyTable(datatable, cache)
      cache = cache or {}
      local tblRes={}
      if type(datatable)=="table" then
        if cache[datatable] then return cache[datatable]
        for k,v in pairs(datatable) do 
          tblRes[copyTable(k, cache)] = copyTable(v, cache) 
        end
        cache[datatable] = tblRes
      else
        tblRes=datatable
      end
      return tblRes
    end
    
    如果A2不成立(即,您有重复的表值),那么您可以将代码更改为:

    function copyTable(datatable)
      local tblRes={}
      if type(datatable)=="table" then
        for k,v in pairs(datatable) do 
          tblRes[copyTable(k)] = copyTable(v) 
        end
      else
        tblRes=datatable
      end
      return tblRes
    end
    
    function copyTable(datatable, cache)
      cache = cache or {}
      local tblRes={}
      if type(datatable)=="table" then
        if cache[datatable] then return cache[datatable]
        for k,v in pairs(datatable) do 
          tblRes[copyTable(k, cache)] = copyTable(v, cache) 
        end
        cache[datatable] = tblRes
      else
        tblRes=datatable
      end
      return tblRes
    end
    
    不过,这种方法只有在有大量重复的大型表的情况下才有回报。因此,这是一个评估哪个版本对于您的实际生产场景更快的问题

    如果A3不起作用(即,您有递归表),那么您的代码(以及上面的两个调整)将进入无限递归循环,并最终引发堆栈溢出

    最简单的处理方法是保持回溯并在发生表递归时抛出错误:

    function copyTable(datatable, cache, parents)
      cache = cache or {}
      parents = parents or {}
      local tblRes={}
      if type(datatable)=="table" then
        if cache[datatable] then return cache[datatable]
        assert(not parents[datatable])
        parents[datatable] = true
        for k,v in pairs(datatable) do 
          tblRes[copyTable(k, cache, parents)]
            = copyTable(v, cache, parents) 
        end
        parents[datatable] = false
        cache[datatable] = tblRes
      else
        tblRes=datatable
      end
      return tblRes
    end
    

    我对处理递归表、保留原始结构的deepcopy函数的解决方案可以在这里找到:

    然后我理解了“通过ref传递的参数”和“通过值传递的参数以及表是引用传递的参数”之间的区别:只要我使用指针,例如通过读取x.foo或通过影响x.foo=“foo”,一切都非常类似于通过ref传递。ref的不同之处在于我可以通过x={}重新影响x,这是我不能用引用来做的。对吗?完全正确。引用是变量的别名,而不是具有相同值的变量。例如,在C++中,由于这个事实,不能将新对象指派给引用。但是如果使用指针,则可以更改指针,因为指针是值。这是一个微妙的区别,但却是有用的。Lua使用指针语义、递归和尾调用。这是你能得到的最有效的了。@warspyking:我能在速度和空间上获得更多吗?当我需要组装子表时,我应该如何“限定”尾部调用?有趣的是,有一个“开箱即用”的函数在各种上下文中执行deepcopy。你能解释一下函数deepCopy的变量(值、缓存、承诺、副本)或/和一个工作示例吗?