作用域教程 |
|
local 关键字来控制变量的存活范围。
此页面上的示例将以脚本文件的形式编写,而不是交互式解释器会话,因为在其中处理局部变量非常困难。稍后将解释原因。
要创建局部变量,请在赋值前添加 local 关键字
local a = 5 print(a)
更改变量时不再需要 local 关键字
local a = 5 a = 6 -- changes the local a, doesn't create a global
局部变量仅存在于创建它们的块中。在块的外部,它们不再存在。
local a = 5 print(a) --> 5 do local a = 6 -- create a new local inside the do block instead of changing the existing a print(a) --> 6 end print(a) --> 5
变量可见的地方称为变量的“作用域”。
现在让我们使用函数来展示这有多么有用
function bar() print(x) --> nil local x = 6 print(x) --> 6 end function foo() local x = 5 print(x) --> 5 bar() print(x) --> 5 end foo()
如您所见,每个变量都从声明它的点到它声明的块的末尾都是可见的。尽管 bar 的 x 与 foo 的 x 同时存在,但它们不是在同一个块中编写的,因此它们是独立的。这就是所谓的词法作用域。
local function 语法糖
local function f() end -- is equivalent to local f f = function() end -- not local f = function() end
最后两个示例之间的区别很重要:局部变量在赋予它初始值的 = 的右侧仍然不存在。因此,如果函数的内容使用 f 来获取对自身的引用,它将正确地获取第一个和第二个版本中的局部变量,但第三个版本将获取全局 f(如果不是其他代码设置的完全无关的值,则为 nil)。
函数可以使用在它们外部创建的局部变量。这些被称为上值。使用上值的函数称为闭包
local x = 5 local function f() -- we use the "local function" syntax here, but that's just for good practice, the example will work without it print(x) end f() --> 5 x = 6 f() --> 6
即使变量在函数外部被更改,函数也能看到更改。这意味着函数中的变量不是副本,而是与外部作用域共享的。
此外,即使外部作用域已经结束,函数仍然会保留对该变量的引用。如果在作用域中创建了两个函数,即使外部作用域已消失,它们仍然会共享该变量。
local function f() local v = 0 local function get() return v end local function set(new_v) v = new_v end return {get=get, set=set} end local t, u = f(), f() print(t.get()) --> 0 print(u.get()) --> 0 t.set(5) u.set(6) print(t.get()) --> 5 print(u.get()) --> 6
由于对 f 的两次调用返回的两个值是独立的,我们可以看到每次调用函数时,它都会创建一个新的作用域和新的变量。
同样,循环在每次迭代时创建一个新的作用域
local t = {} for i = 1, 10 do t[i] = function() print(i) end end t[1]() --> 1 t[8]() --> 8
因为它在新的作用域中运行每一行
> local a=5; print(a) 5 > print(a) -- a is out of scope now, so global a is used nil
您可以做的一件事是将代码包装在一个 do-end 块中,但直到您完成编写整个块之前,它都不会是交互式的
> do >> local a = 5 >> print(a) -- works on a new line >> end 5
您可能来自另一个默认情况下使变量成为局部变量的语言,并且可能在想“所有这些额外的复杂性有什么意义?为什么不默认使变量成为局部变量?”
x = 3 -- more code, you might have even forgotten about variable x by now... function () -- ... x = 5 -- does this create a new local x, or does it change the outer one? -- ... end -- some more code...
改变外部变量的问题在于,您可能打算创建一个新变量,但却改变了您可能甚至不知道的现有变量,从而引入了错误。
创建新变量的问题在于,如果您实际上想更改外部变量怎么办?
使用 local 关键字,一切都很明确:没有 local,您将更改现有变量;使用它,您将创建一个新变量。
有关此内容的更多讨论,请参阅 LocalByDefault。
总的规则是始终使用局部变量,除非程序的每个部分都需要访问该变量(这非常罕见)。
由于很容易忘记使用 local,而且 Lua 不会对此发出警告(而是默默地创建一个全局变量),因此它可能成为错误的根源。一个解决方案是使用像 strict.lua(如下所示)这样的脚本,它使用元表(在后续教程中介绍)来捕获全局变量的创建并引发错误。您可以将脚本放在项目中的一个文件中,然后执行 require("strict") 来使用它。
-- -- strict.lua -- checks uses of undeclared global variables -- All global variables must be 'declared' through a regular assignment -- (even assigning nil will do) in a main chunk before being used -- anywhere or assigned to inside a function. -- local mt = getmetatable(_G) if mt == nil then mt = {} setmetatable(_G, mt) end __STRICT = true mt.__declared = {} mt.__newindex = function (t, n, v) if __STRICT and not mt.__declared[n] then local w = debug.getinfo(2, "S").what if w ~= "main" and w ~= "C" then error("assign to undeclared variable '"..n.."'", 2) end mt.__declared[n] = true end rawset(t, n, v) end mt.__index = function (t, n) if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then error("variable '"..n.."' is not declared", 2) end return rawget(t, n) end function global(...) for _, v in ipairs{...} do mt.__declared[v] = true end end
有关强制使用局部变量的更多信息,请参阅 DetectingUndefinedVariables。