第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于较新版本,但有一些不同之处。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目


14.2 – 声明全局变量

Lua 中的全局变量不需要声明。虽然这对于小型程序来说很方便,但在大型程序中,一个简单的错别字可能会导致难以发现的错误。但是,如果我们愿意,可以更改这种行为。因为 Lua 将其全局变量保存在一个常规表中,所以我们可以使用元表来更改其在访问全局变量时的行为。

第一种方法如下

    setmetatable(_G, {
      __newindex = function (_, n)
        error("attempt to write to undeclared variable "..n, 2)
      end,
      __index = function (_, n)
        error("attempt to read undeclared variable "..n, 2)
      end,
    })
在该代码之后,任何尝试访问不存在的全局变量都会触发一个错误
    > a = 1
    stdin:1: attempt to write to undeclared variable a

但是我们如何声明新变量?使用 rawset,它绕过了元方法

    function declare (name, initval)
      rawset(_G, name, initval or false)
    end
使用 orfalse 确保新的全局变量始终获得与 nil 不同的值。请注意,您应该在安装访问控制之前定义此函数,否则会收到错误:毕竟,您正在尝试创建一个新的全局变量 declare。有了该函数,您可以完全控制全局变量
    > a = 1
    stdin:1: attempt to write to undeclared variable a
    > declare"a"
    > a = 1             -- OK

但是现在,要测试变量是否存在,我们不能简单地将其与 nil 进行比较;如果它是 nil,访问将引发错误。相反,我们使用 rawget,它避免了元方法

    if rawget(_G, var) == nil then
      -- `var' is undeclared
      ...
    end

将该控件更改为允许具有 nil 值的全局变量并不困难。我们只需要一个辅助表来保存已声明变量的名称。每当调用元方法时,它都会在该表中检查变量是否已声明。代码可能如下所示

    local declaredNames = {}
    function declare (name, initval)
      rawset(_G, name, initval)
      declaredNames[name] = true
    end
    setmetatable(_G, {
      __newindex = function (t, n, v)
        if not declaredNames[n] then
          error("attempt to write to undeclared var. "..n, 2)
        else
          rawset(t, n, v)   -- do the actual set
        end
      end,
      __index = function (_, n)
        if not declaredNames[n] then
          error("attempt to read undeclared var. "..n, 2)
        else
          return nil
        end
      end,
    })

对于这两种解决方案,开销可以忽略不计。使用第一个解决方案,在正常操作期间永远不会调用元方法。在第二个解决方案中,它们可能会被调用,但仅当程序访问保存 nil 的变量时。