范围教程

lua-users home
wiki

到目前为止,你只是将值分配给名称,并且可以通过在脚本中的任何地方使用名称来获取该值。对于小型示例来说,这很好,但是现在你已经了解了函数,这可能是一个大问题:如果不同的函数使用相同的名称来存储临时值怎么办?它们会发生冲突并相互覆盖,使你的脚本成为一个无法调试的混乱。解决方案是使用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


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2013 年 12 月 21 日上午 10:07 GMT (差异)