通常,赋值操作对于访问和设置全局变量已经足够了。然而,有时我们也需要某些形式的元编程( meta-programming
)。例如,我们需要操作一个全局变量,而这个全局变量的名称却存储在另一个变量中或者经由运行时计算得到。为了获取这个变量的值,许多程序员会写出如下的代码:
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) --> 10 print(getfield("t.x.y")) --> 10