9.Lua如何实现深拷贝

  1. 9.Lua如何实现深拷贝
    1. 9.1 题目
    2. 9.2 深入解析
      1. 什么是深拷贝?
      2. 如何进行深拷贝?
    3. 9.3 答题示例
    4. 9.4 关键词联想

9.Lua如何实现深拷贝


9.1 题目

Lua如何实现深拷贝?


9.2 深入解析

什么是深拷贝?

在Lua中,使用赋值运算符”=”进行拷贝的时候,分两种情况:

  1. string、number、boolean这些基本类型,会进行复制,会创建一个新对象,拷贝出来的对象和原来的互不影响

    local num1 = 5
    local num2 = num1
    num2 = 10
    print(num1) -- 5
    print(num2) -- 10
    
    local str1 = "test123"
    local str2 = str1
    str2 = "韬老狮"
    print(str1) -- test123
    print(str2) -- 韬老狮
    
  2. table类型,是直接进行的引用,拷贝出来的对象和原来是同一个对象,改一处另一处也会变化

    local tbl = { x = 1, y = 2, z = 3 }
    local tb2 = tbl
    tb2.x = 4
    print(tbl.x) -- 4
    print(tb2.x) -- 4
    

因此,一般我们提到Lua中的深拷贝,一般都是希望对table类型的变量实现深拷贝。即拷贝后的内容变化,不会影响原来的内容。而Lua中并没有提供这样的api,因此我们一般会自己封装一个函数。

如何进行深拷贝?

进行table深拷贝整体的封装思路就是递归地遍历表的每一个元素,并且在遇到子表时,对子表也进行深拷贝。这样可以确保拷贝后的新表与原表完全独立,任何对新表的修改都不会影响到原表。

function clone(object)
    -- 记录已经拷贝过的表,防止循环引用
    -- 有可能表自己引用自己,如果不记录有可能无限递归
    local lookup_table = {}

    -- 递归拷贝函数
    local function _copy(object)
        -- 如果遇到的不是表则直接返回这个值(比如nil或者基本类型)
        if type(object) ~= "table" then
            return object

        -- 如果在拷贝过的表中找到该对象,说明已经拷贝过,直接返回拷贝过的表
        elseif lookup_table[object] then
            return lookup_table[object]
        end

        -- 创建新表
        local new_table = {}

        -- 记录这个新表 表明object这个表已经拷贝过
        lookup_table[object] = new_table

        -- 递归拷贝每个键值对 对每个键值对都调用_copy拷贝
        -- 之所以对键也调用_copy函数,是因为键也可能是一个表
        for key, value in pairs(object) do
            new_table[_copy(key)] = _copy(value)
        end

        -- 为拷贝表设置与原来的表相同的元表
        return setmetatable(new_table, getmetatable(object))
    end

    return _copy(object)
end

local tb3 = { x = 1, y = 2, z = 3 }
local tb4 = clone(tb3)
tb4.x = 4
print(tb3.x) -- 1
print(tb4.x) -- 4

其中不太好理解的代码为:

-- 递归拷贝每个键值对 对每个键值对都调用_copy拷贝
-- 之所以对键也调用_copy函数,是因为键也可能是一个表
for key, value in pairs(object) do
    new_table[_copy(key)] = _copy(value)
end

我们举个例子来理解这里的代码:

local original = {
    a = { 1, 2, 3 },
    b = "string",
    c = 42,
    d = { x = 1, y = 2 }
}

local copy = clone(original)

对于键a,值{1,2,3},调用_copy(a)返回a本身,调用_copy({1,2,3})会递归地创建一个新表{1,2,3}

对于键b,值”string”,调用_copy(b)返回b本身,调用_copy(“string”)会返回”string”

对于键c,值42,调用_copy(c)返回c本身,调用_copy(42)会返回42

对于键d,值{x=1,y=2},调用_copy(d)返回d本身,调用_copy({x=1,y=2})会递归地创建一个新表{x=1,y=2}

每次递归调用_copy函数时,都会对原表中的键和值进行深拷贝,并将结果插入到新表”new_table”中。这样就确保了新表和原表之间完全独立。


9.3 答题示例

在Lua中,对基本类型(number、string、boolean)的赋值是值拷贝,但对table的赋值只是引用拷贝。要实现深拷贝,我们一般自己写一个递归函数:

  1. 维护一个“已拷贝表”映射,防止循环引用无限递归;
  2. 对每个键和值都调用同一个拷贝函数,遇到非table直接返回原值,遇到table则新建表并递归拷贝;
  3. 最后用 setmetatable 保留原表的元表。

示例:

function clone(obj)
  local lookup = {}
  local function _copy(o)
    if type(o) ~= "table" then return o end
    if lookup[o] then return lookup[o] end
    local t = {}
    lookup[o] = t
    for k,v in pairs(o) do
      t[_copy(k)] = _copy(v)
    end
    return setmetatable(t, getmetatable(o))
  end
  return _copy(obj)
end

这样 clone 出来的新table 就与原table 完全独立,修改新表不会影响原表。


9.4 关键词联想

  • 值拷贝 vs 引用拷贝
  • 递归遍历 table
  • 防止循环引用
  • lookup 映射表
  • setmetatable / getmetatable
  • 深拷贝与浅拷贝
  • Lua 类型判断(type 函数)
  • pairs 遍历 table


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏