Копирование таблиц

Копирование таблиц

Сразу с кода начнём, чтобы стала понятна тема.

local table1 = {1, 2, 3}
local table2 = table1
table1[3] = 4

print("table1: " .. table.concat(table1, ", "))
--> table1: 1, 2, 4

print("table2: " .. table.concat(table2, ", "))
--> table2: 1, 2, 4

Как видно, мы, вроде как, присвоили таблицу table1 в table2, изменили только первую, но значение поменялось и во второй.

Это может быть полезно (ниже будет пример), но в большинстве случаев такое поведение странно и неожиданно.

Дело в том, что копируются не таблицы, а ссылки на них — а именно их адреса в памяти. Такое поведение свойственно многим языкам и называется "shallow copy". Таким образом, обе переменные ссылаются на одинаковый адрес в памяти. И при обращении к любой из переменной меняется одна и та же таблица.

Более тго, такая работа переменных не зависит от типа. Это будет так же и с функциями, и со строками, и с числами. Только особенность таблиц в том, что это единственный мутабельный (изменяемый) тип данных в Lua.

Действительно, если в переменной число, то мы его можем только перезаписать другим. А таблицы могут изменяться.

Чтобы скопировать не адрес, а значения таблицы, придётся воспользоваться "deep copy", причём рекурсивным алгоритмом. Создаётся совершенно новая таблица и заполняется значениями, равными элементам данной.

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

Как уже было сказано, shallow copy может оказаться полезным. Таблицы могут быть довольно тяжёлыми: с тысячами, миллионами элементов. Если при вызове функций и передаче туда таблиц они бы копировались, мы бы быстро израсходовали всю память, что в OC особенно актуально. Но вместо этого Lua передаёт аргументы, как это называется в C++, по ссылке.

Поэтому код ниже будет спокойно работать:

local function removeOddNums(tbl)
  for i = #tbl, 1, -1 do
    if tonumber(tbl[i]) and tbl[i] % 2 == 1 then
      table.remove(tbl, i)
    end
  end
end

local tbl = {5, 26, 249586, 457139, 876, 42, 153}
removeOddNums(tbl)
print(table.concat(tbl, ", "))
--> 26, 249586, 876, 42

Именно из-за этого функция table.sort не возвращает новую таблицу с отсортированными элементами, а изменяет переданную.

Такое поведение можно использовать для эмуляции поинтеров. Ниже — пример их реализации с сахаром. Дереференс выполняется через оператор #, а новое значение присваивается вызовом и передачей значения (ptr(newValue)).

local ptr do
  local meta = {}

  function meta:__len()
    return self[1]
  end

  function meta:__eq(value)
    return self[1] == value[1]
  end

  function meta:__call(value)
    self[1] = value
    return self[1]
  end

  function ptr(x)
    return setmetatable({x}, meta)
  end
end

local function modify(p1, p2)
  p2(#p1)
  p1("new value")
end

local p1 = ptr("old value")
local p2 = ptr(54)

print(#p1, #p2)
modify(p1, p2)
print(#p1, #p2)

-- Можно даже что-то вроде такого безумного делать
local p3 = ptr(ptr(p2))
print(###p3)

Подробнее

results for ""

    No results matching ""