#variables #scope #lua
#переменные #область действия #lua
Вопрос:
Я полностью сбит с толку определением области действия переменной в Lua и передачей аргумента функции (значения или ссылки).
Смотрите код ниже:
local a = 9 -- since it's define local, should not have func scope
local t = {4,6} -- since it's define local, should not have func scope
function moda(a)
a = 10 -- creates a global var?
end
function modt(t)
t[1] = 7 -- create a global var?
t[2] = 8
end
moda(a)
modt(t)
print(a) -- print 9 (function does not modify the parent variable)
print(t[1]..t[2]) -- print 78 (some how modt is modifying the parent t var)
Как таковое, это поведение полностью сбивает меня с толку.
-
Означает ли это, что табличные переменные передаются функции по ссылке, а не по значению?
-
Каким образом создание глобальной переменной противоречит уже определенной локальной переменной?
- Почему
modt
возможность изменять таблицу, ноmoda
не может изменить переменную a?
- Почему
Ответ №1:
Вы угадали правильно, табличные переменные передаются по ссылке. Цитирую справочное руководство по Lua 5.1:
В Lua существует восемь основных типов: nil, boolean, number, string, функция, пользовательские данные, поток и таблица. ….
Таблицы, функции, потоки и (полные) значения пользовательских данных являются объектами: переменные фактически не содержат этих значений, только ссылки на них. Присвоение, передача параметров и возврат функции всегда управляют ссылками на такие значения; эти операции не подразумевают какого-либо копирования.
Таким образом, nil, логические значения, числа и строки передаются по значению. Это точно объясняет поведение, которое вы наблюдаете.
Комментарии:
1. Это немного отличается от передачи по ссылке. (Смотрите Мой ответ). В частности, поведение для
function(x) x={} end
отличается.2. Все передается по значению, поскольку определенные типы (таблицы, функции, потоки и (полные) значения пользовательских данных) являются ссылками. Эти ссылки передаются по значению.
Ответ №2:
Типы Lua function
, table
userdata
и thread
(сопрограммы) передаются по ссылке. Другие типы передаются по значению. Или, как любят выражаться некоторые люди; все типы передаются по значению, но function
, table
userdata
и thread
являются ссылочными типами.
string
это тоже своего рода ссылочный тип, но неизменяемый, интернированный и копируемый при записи — он ведет себя как тип значения, но с лучшей производительностью.
Вот что происходит:
local a = 9
local t = {4,6}
function moda(a)
a = 10 -- sets 'a', which is a local introduced in the parameter list
end
function modt(t)
t[1] = 7 -- modifies the table referred to by the local 't' introduced in the parameter list
t[2] = 8
end
Возможно, это поможет понять, почему все так, как есть:
local a = 9
local t = {4,6}
function moda()
a = 10 -- modifies the upvalue 'a'
end
function modt()
t[1] = 7 -- modifies the table referred to by the upvalue 't'
t[2] = 8
end
-- 'moda' and 'modt' are closures already containing 'a' and 't',
-- so we don't have to pass any parameters to modify those variables
moda()
modt()
print(a) -- now print 10
print(t[1]..t[2]) -- still print 78
Ответ №3:
jA_cOp прав, когда говорит «все типы передаются по значению, но функция, таблица, пользовательские данные и поток являются ссылочными типами».
Разница между этим и «таблицы передаются по ссылке» важна.
В данном случае это не имеет значения,
function modt_1(x)
x.foo = "bar"
end
Результат: и «передавать таблицу по ссылке», и «передавать таблицу по значению, но таблица имеет ссылочный тип» будут делать то же самое: для поля foo x теперь установлено значение «bar».
Но для этой функции это имеет огромное значение
function modt_2(x)
x = {}
end
В этом случае передача по ссылке приведет к изменению аргумента на пустую таблицу. Однако в «передавать по значению, но его ссылочный тип» новая таблица будет локально привязана к x, а аргумент останется неизменным. Если вы попробуете это в lua, вы обнаружите, что это второе (значения являются ссылками), которое встречается.
Комментарии:
1. Я нашел хороший способ думать об этом, просто что все передается по значению. Однако так уж получилось, что некоторые типы являются просто ссылками. Сами ссылки передаются по значению, поэтому в вашем примере ‘t’ не изменяется. Хорошее объяснение 🙂
Ответ №4:
Я не буду повторять то, что уже было сказано в ответах Bas Bossink и jA_cOp о ссылочных типах, но:
— поскольку это определение локальное, не должно иметь области действия
Это неверно. Переменные в Lua имеют лексическую область действия, что означает, что они определены в блоке кода и всех его вложенных блоках.
Что local
делает, так это создает новую переменную, которая ограничена блоком, в котором находится оператор, блоком, являющимся либо телом функции, «уровнем отступа», либо файлом.
Это означает, что всякий раз, когда вы делаете ссылку на переменную, Lua будет «сканировать вверх», пока не найдет блок кода, в котором эта переменная объявлена локальной, по умолчанию используя глобальную область действия, если такого объявления нет.
В этом случае a
и t
объявляются локальными, но объявление находится в глобальной области видимости, поэтому a
и t
являются глобальными; или, самое большее, они являются локальными для текущего файла.
В этом случае они не объявляются повторно local
внутри функций, но они объявляются как параметры, что имеет тот же эффект. Если бы они не были параметрами функции, любая ссылка внутри тел функций все равно ссылалась бы на внешние переменные.
Есть руководство по области по lua-users.org с некоторыми примерами, которые могут помочь вам больше, чем моя попытка объяснения. Программирование в разделе Lua на эту тему также полезно прочитать.
Ответ №5:
Означает ли это, что табличные переменные передаются функции по ссылке, а не по значению?
ДА.
Каким образом создание глобальной переменной противоречит уже определенной локальной переменной?
Это не так. Это может показаться таким образом, потому что у вас есть вызываемая глобальная переменная t
и вы передаете ее функции с вызываемым аргументом t
, но эти два t
параметра разные. Если вы переименуете аргумент во что-то другое, например, g, q
результат будет точно таким же. modt(t)
может изменять глобальную переменную t
только потому, что вы передаете ее по ссылке. Например, если вы вызовете modt({})
, глобальная t
область не будет затронута.
Почему modt может изменять таблицу, а moda не может изменять переменную a?
Поскольку аргументы являются локальными. Присвоение имени вашему аргументу a
похоже на объявление локальной переменной с помощью local a
, за исключением того, что, очевидно, аргумент получает переданное значение, а обычная локальная переменная — нет. Если бы ваш аргумент был вызван z
(или вообще не присутствовал), то moda
это действительно изменило бы глобальный a
параметр.