Сразу с кода начнём, чтобы стала понятна тема.
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)