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

51

我对 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?
5个回答

61

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

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

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

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


4
这与按引用传递略有不同(请参见我的回答)。特别是对于function(x) x={} end的行为是不同的。 - Michael Anderson
7
所有内容都是传值,但某些类型(如表、函数、线程和(完整的)userdata 值)是引用。这些引用是按值传递的。 - Ethan

35
Lua的functiontableuserdatathread(协程)类型是通过引用传递的。其他类型则是通过值传递的。或者有些人喜欢这样说:所有类型都是按值传递的,但functiontableuserdatathread是引用类型。 string也是一种引用类型,但它是不可变的,只读的,并采用了复制-on-write策略 —— 它的行为就像一个值类型,但效率更高。
下面是具体情况:
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

22

jA_cOp是正确的,他说:“所有类型都是按值传递的,但函数、表、用户数据和线程是引用类型。”

这与“表通过引用传递”之间的区别很重要。

在这种情况下,这没有任何区别,

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

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

但对于这个函数来说,这会产生很大的区别。

function modt_2(x)
  x = {}
end
在这种情况下,按引用传递会导致参数被更改为空表。然而,在“按值传递,但它是引用类型”的情况下,一个新的表将局部绑定到x上,并且参数将保持不变。如果在lua中尝试这样做,你会发现这是第二种情况(值是引用)。

我发现一个很好的思考方式就是一切都是按值传递的。然而,有些类型只是引用。这些引用本身是按值传递的,这就是为什么你的例子不会改变“t”的原因。解释得很好 :) - Ethan

9
我不会重复Bas Bossink和jA_cOp在参考类型方面的回答,但是:
-- 因为它被定义为本地变量,所以不应该具有函数作用域
这是不正确的。在Lua中,变量是词法作用域,这意味着它们在代码块及其所有嵌套块中定义。 local创建一个新变量,仅限于语句所在的块中,块可以是函数体、缩进级别或文件。
这意味着每当您引用变量时,Lua将向上扫描,直到找到其中声明该变量为本地变量的代码块,如果没有这样的声明,则默认为全局作用域。
在这种情况下,at被声明为本地变量,但声明在全局作用域中,因此at是全局的;或者至多是当前文件的本地变量。
它们在函数内部不被重新声明为“local”,而是作为参数声明,具有相同的效果。如果它们不是函数参数,在函数体内的任何引用仍将指向外部变量。 lua-users.org上有一个关于范围教程的教程,其中包含一些示例,可能比我尝试解释更有帮助。Programming in Lua的章节也是一个好读物。

1
这是否意味着表变量是通过引用而不是值传递给函数的?
是的。
全局变量的创建如何与已定义的局部变量冲突?
它并没有。这可能是因为你有一个名为“t”的全局变量,并将其传递给一个名为“t”的参数的函数,但这两个“t”是不同的。如果你将参数重命名为其他名称,例如“q”,输出将完全相同。“modt(t)”之所以能够修改全局变量“t”,仅因为你是通过引用传递它的。例如,如果你调用“modt({})”,全局“t”将不受影响。
为什么modt能够修改表格,而moda不能修改变量?
因为参数是本地的。将参数命名为a类似于使用local a声明本地变量,除了明显的参数接收传递的值而常规的本地变量不接收。如果您的参数被称为z(或根本不存在),那么moda确实会修改全局的a

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接