函数/变量作用域(传值还是传引用?)

我对**Lua**的变量作用域和函数参数传递(值传递还是引用传递)完全困惑了。

请看下面的代码:

local a = 9        -- 由于是定义为局部变量,不应该具有函数作用域
local t = {4,6}    -- 由于是定义为局部变量,不应该具有函数作用域

function moda(a)
  a = 10           -- 创建了一个全局变量?
end
function modt(t)
  t[1] = 7         -- 创建了一个全局变量?
  t[2] = 8
end

moda(a)
modt(t)
print(a)  -- 输出9(函数没有修改父变量)
print(t[1]..t[2])  -- 输出78(某种方式下modt正在修改父t变量)

因此,这种行为完全让我困惑。

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

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

    • 为什么modt能够修改表,而moda无法更改变量a?

原文链接 https://stackoverflow.com/questions/6128152

点赞
stackoverflow用户388847
stackoverflow用户388847

Lua 的 functiontableuserdatathread(协程)类型是按引用传递的。其他类型则是按值传递。或者像一些人所说的那样:所有类型都是按值传递的,但 functiontableuserdatathread 是引用类型。

string 也是一种引用类型,但是是不可变的,经过内部化(interned)和写时复制(copy-on-write),它的行为类似于值类型,但具有更好的性能。

以下是正在发生的事情:

local a = 9
local t = {4,6}

function moda(a)
  a = 10 -- 设置 'a',它是在参数列表中引入的本地变量
end

function modt(t)
  t[1] = 7 -- 修改由参数列表中引入的本地变量 't' 引用的表
  t[2] = 8
end

也许这会让事情变得更清晰:

local a = 9
local t = {4,6}

function moda()
  a = 10 -- 修改闭包 'a'
end

function modt()
  t[1] = 7 -- 修改闭包 't' 引用的表
  t[2] = 8
end

-- 'moda' 和 'modt' 已经包含了 'a' 和 't',
-- 所以我们不必传递任何参数来修改那些变量
moda()
modt()
print(a)  -- 输出 10
print(t[1]..t[2])  -- 仍然输出 78
2011-05-25 17:16:23
stackoverflow用户74198
stackoverflow用户74198

你猜对了,表变量是通过引用传递的。引用 Lua 5.1参考手册 的说明:

Lua 有八种基本类型: nil, boolean, number, string, function, userdata, thread, and table. ……

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

因此,nil、布尔值、数字和字符串是按值传递的。这完全解释了您观察到的行为。

2011-05-25 17:16:49
stackoverflow用户400127
stackoverflow用户400127

我不会重复 Bas Bossink 和 jA_cOp 关于引用类型的答案,但是:

-- 因为它是定义本地的,所以不应该具有函数作用域

这是错误的。在 Lua 中,变量是 词法作用域,这意味着它们被定义在一段代码块及其嵌套的所有块中。

local 所做的是创建一个新的变量,该变量仅限于语句所在的块,块可以是一个函数的主体,一个“缩进级别”或一个文件。

这意味着每当你引用一个变量时,Lua 将“向上扫描”,直到找到一个代码块,在该代码块中声明了该变量为本地变量,如果没有这样的声明,则默认为全局作用域。

在这种情况下,at 被声明为本地变量,但声明在全局范围内,因此 at 是全局的;或者至多是在当前文件中本地的。

然后它们在函数中没有被重新声明为 local,但它们被声明为参数,这具有相同的效果。如果它们不是函数参数,函数体中的任何引用仍将指向外部的变量。

在 lua-users.org 上有一个 作用域教程,其中有一些示例,可能比我尝试的解释更有帮助。Programming in Lua 中的这个主题也是一个不错的阅读材料。

2011-05-25 19:07:07
stackoverflow用户12048
stackoverflow用户12048

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

是的。

全局变量的创建与已经定义的局部变量有什么冲突?

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

为什么modt能够修改表,而moda不能修改变量a?

因为参数是局部的。将参数命名为a类似于使用local a声明局部变量,除了显然该参数接收传入值,而常规局部变量不会。如果你的参数被叫做z(或者根本不存在),那么moda确实会修改全局变量a

2011-05-25 19:20:46
stackoverflow用户221955
stackoverflow用户221955

jA_cOp 在他所说的 "所有类型都是按值传递的,但是函数、表、用户数据和线程是引用类型" 的观点上是正确的。

这与 "表是按引用传递的" 的说法有着重要的区别。

在这种情况下没有什么区别,

function modt_1(x)
  x.foo = "bar"
end

结果:无论是 "按引用传递表" 还是 "按值传递表,但表是引用类型" 都会导致相同的结果:x 现在具有其 foo 字段的值为 "bar"。

但是对于这个函数这就有了天壤之别

function modt_2(x)
  x = {}
end

在这种情况下,按引用传递将导致参数被更改为空表。然而在 "按值传递,但它是引用类型" 中,一个新表将被绑定到 x 中,而参数本身不会被改变。如果你在 lua 中尝试这个操作,你会发现是第二个(值是引用)发生了。

2011-12-02 08:31:07