【问题标题】: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 变量?

【问题讨论】:

    标签: variables scope lua


    【解决方案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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-11
          • 2015-03-28
          • 2021-11-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-20
          • 1970-01-01
          相关资源
          最近更新 更多