函数/变量范围(通过值或引用传递?)

Posted

技术标签:

【中文标题】函数/变量范围(通过值或引用传递?)【英文标题】:Function/variable scope (pass by value or reference?) 【发布时间】:2011-09-01 22:59:43 【问题描述】:

我完全被 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 Reference Manual:

Lua 中有八种基本类型:nil、boolean、number、string、function、userdata、thread 和 table。 ....

表、函数、线程​​和(完整的)用户数据值都是对象:变量实际上并不包含这些值,只是对它们的引用。赋值、参数传递和函数返回总是操纵对这些值的引用;这些操作并不意味着任何形式的复制。

所以 nil、布尔值、数字和字符串都是按值传递的。这正好解释了您观察到的行为。

【讨论】:

这与传递引用略有不同。 (见我的回答)。特别是 function(x) x= end 的行为是不同的。 一切都是按值传递的,按某些类型(表、函数、线程​​和(完整)用户数据值)是引用。这些引用是按值传递的。【参考方案2】:

Lua 的functiontableuserdatathread(协程)类型通过引用传递。其他类型按值传递。或者像某些人喜欢说的那样;所有类型都按值传递,但functiontableuserdatathread 是引用类型。

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

结果:“按引用传递表”和“按值传递表,但表是引用类型”都将执行相同的操作:x 现在将其 foo 字段设置为“bar”。

但是对于这个功能,它会产生很大的不同

function modt_2(x)
  x = 
end

在这种情况下,通过引用传递将导致参数更改为空表。但是在“按值传递,但它是引用类型”中,一个新的表将在本地绑定到 x,并且参数将保持不变。如果你在 lua 中尝试这个,你会发现它是第二个发生的(值是引用)。

【讨论】:

我找到了一种很好的思考方式,即一切都是按值传递的。然而,有些类型只是引用而已。引用本身是按值传递的,这就是您的示例不更改“t”的原因。很好的解释:)【参考方案4】:

我不会重复在 Bas Bossink 和 jA_cOp 关于引用类型的回答中已经说过的话,但是:

-- 因为它是本地定义的,所以不应该有 func 范围

这是不正确的。 Lua 中的变量是lexically scoped,这意味着它们定义在一个代码块及其所有嵌套块中。local 所做的是创建一个新变量,该变量仅限于语句所在的块,一个块可以是函数体、“缩进级别”或文件。

这意味着每当你引用一个变量时,Lua 都会“向上扫描”,直到它找到一个代码块,其中该变量被声明为本地的,如果没有这样的声明,则默认为全局范围。

在这种情况下,at 被声明为本地但声明是在全局范围内,所以 at 是全局的;或者最多,它们是当前文件的本地文件。

它们不会在函数内部重新声明local但是它们被声明为参数,具有相同的效果。如果它们不是函数参数,那么函数体内的任何引用仍然会引用外部的变量。

在 lua-users.org 上有一个 Scope Tutorial,其中的一些示例可能比我试图解释的方式更能帮助您。 Programming in Lua's section on the subject 也是一本好书。

【讨论】:

【参考方案5】:

这是否意味着表变量是通过引用而不是值传递给函数的?

是的。

全局变量创建与已经定义的局部变量如何冲突?

不是。可能会出现这种情况,因为您有一个名为 t 的全局变量并将其传递给带有名为 t 的参数的函数,但两个 ts 是不同的。如果您将参数重命名为其他名称,例如q,则输出将完全相同。 modt(t) 能够修改全局变量 t 只是因为您通过引用传递它。例如,如果您调用modt(),则全局t 将不受影响。

为什么 modt 可以修改表而 moda 不能修改 a 变量?

因为参数是本地的。将参数命名为 a 类似于使用 local a 声明局部变量,但显然参数接收传入的值而常规局部变量没有。如果您的论点被称为z(或根本不存在),那么moda 确实会修改全局a

【讨论】:

以上是关于函数/变量范围(通过值或引用传递?)的主要内容,如果未能解决你的问题,请参考以下文章

构造函数的最佳形式?通过值或引用传递?

C++ 是不是通过值或引用传递对象?

如何通过值或常量引用传递 std::string_view

通过值或引用传递容器

通过指针或引用将映射传递给函数?

golang中的传值或传引用