范围教程 |
|
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。