全局变量在大多数变成语言中是让人爱恨交织又不可或缺的。一方面,使用管全局变量会明显地使无关的代码部分纠缠在一起,容易导致代码复杂。另一方面,谨慎地使用全局变量又能更好地表达程序中真正的全局概念;此外,虽然全局常量看似无害,但像Lua语言这样的动态语言是无法区分常量和变量的。像Lua这样的嵌入式语言更复杂:虽然全局变量时再整个程序中均可见的变量,但由于Lua语言是由宿主应用调用代码段的,因此“程序”的概念不明确。
Lua语言通过不使用全局变量的方法来解决这个难题,但又不遗余力地在Lua语言汇总对全局变量进行模拟。在第一种近似的模拟中,我们可以认为Lua语言把所有的全局变量保存在一个称为全局环境的普通表中。
由于不需要再为全局变量创造一种新的数据结构,因此使用一个表来保存全局变量的一个优点是简化了Lua 语言的内部实现。另一个优点是,可以像操作其他表一样操作这个表。为了便于实现这种操作方式,Lua语言将全局环境自身保存在全局变量_G中。例如,如下代码输出了全局环境中所有全局变量的名称:
for n in pairs(_G) do print(n) end
具有动态名称的全局变量
通常,赋值操作对于访问和设置全局变量已经足够了。
通常,赋值操作对于访问和设置全局变量已经足够了。然而,有时我们也需要某些形式的元变成。例如,我们需要操作一个全局变量,而这个全局变量的名称却存储在另一个变量中或者经由运行时计算得到。为了获取这个变量的值,许多程序员会写出下面的代码:
value = load("return " .. varname)()
例如,如果varname是x,那么字符串连接的结果就是"return x",当执行时就能得到期望的结果。然而,在这段代码中涉及一个新代码段的创建和编译,在一定程度上开销昂贵。我们可以使用下面的代码来实现相同的效果,但效率却比之前的高出一个数量级:
value = _G[varname]
由于全局环境是一个普通的表,因此可以简单地使用个对应的键直接进行索引。
类似地,我们可以通过编写_G[varname] = value给一个名称为动态计算出的全局变量赋值。不过,请注意,有些程序员对于这种机制的使用可能有些过度而写出诸如_G[“a”] = _G[“b”]这样的代码,而这仅仅是a = b 的一种复杂写法。
上述问题的一般化形式是,允许字段使用诸如"io.read"或"a.b.c.d"这样的动态名称。如果直接使用_G[“io.read”],显然是不能从表io中得到字段read的。但我们可以编写一个函数getfield让getfield(“io.read”)返回想要的结果。这个函数主要是一个循环,从_G开始逐个字段地进行求值:
function getfield(f)
local v = _G
for w in string.gmatch(f,"[%a_][%w_]*") do
v = v[w]
end
return v
end
我们使用函数gmatch来遍历f中的所有标识符。
与之对应的设置字段的函数稍显复杂。像a.b.c.d = v这样的赋值等价于一下的代码:
local temp = a.b.c
temp.d = v
也就是说,我们必须一直取到最后一个名称,然后再单独处理最后的这个名称。
示例 函数setfield
function setfield(f,v)
local t = _G
for w, d in string.gmatch(f, "([%a_][%w_]*)(%.?)") do
if d == "." then
t[w] = t[w] or {
}
t = t[w]
else
t[w] = v
end
end
end
上例中使用的模式将捕获字段名称保存在变量w中,并将其后可选的点保存在变量d中。如果字段名后没有点,那么该字段就是最后一个名称。
下面的代码通过上例中的函数创建了全局表t和t.x,并将10赋值给了t.x.y:
setfield("t.x.y", 10)
print(t.x.y)
print(getfield("t.x.y"))
全局变量的声明
Lua语言中的全局变量不需要声明就可以使用。虽然这种行为对于小型程序来说较为方便,但在大型程序中一个简单的手误可能造成难以发现的bug。不过,如果我们乐意的话,也可以改变这种行为。由于Lua语言将全局变量存放在一个普通的表中,所以可以通过元表来访问不存在全局变量的情况。
一种方法是简单地检测所有对全局表中不存在键的访问:
setmetatable(_G,{
__newindex = funciton (_,n)
error("attempt to write to undeclared variable" .. n, 2)
end,
__index = function(_,n)
error("attempt to read undeclared variable " .. n , 2)
end,
})
这段代码执行后,所有试图对不存在全局变量的访问都将引发一个错误:
print(a