Lua 用一个名为environment 普通的表来保存所有的全局变量。(更精确的说,Lua 在一系列的environment 中保存他的“global”变量,但是我们有时候可以忽略这种多样 性)这种结果的优点之一是他简化了Lua 的内部实现,因为对于所有的全局变量没有必 要非要有不同的数据结构。另一个(主要的)优点是我们可以像其他表一样操作这个保存 全局变量的表。为了简化操作,Lua 将环境本身存储在一个全局变量_G 中,(_G._G 等 于_G)。例如,下面代码打印在当前环境中所有的全局变量的名字: for n in pairs(_G) do print(n) end 这一章我们将讨论一些如何操纵环境的有用的技术。
1. 全局变量声明: Lua中的全局变量不需要声明就可以使用。尽管很方便,但是一旦出现笔误就会造成难以发现的错误。我们可以通过给_G表加元表的方式来保护全局变量的读取和设置,这样就能降低这种笔误问题的发生几率了。见如下示例代码:
--该table用于存储所有已经声明过的全局变量名
local declaredNames = {}
local mt = {
__newindex = function(table,name,value)
--先检查新的名字是否已经声明过,如果存在,这直接通过rawset函数设置即可。
if not declaredNames[name] then
--再检查本次操作是否是在主程序或者C代码中完成的,如果是,就继续设置,否则报错。
local w = debug.getinfo(2,"S").what
if w ~= "main" and w ~= "C" then
error("attempt to write to undeclared variable " .. name)
end
--在实际设置之前,更新一下declaredNames表,下次再设置时就无需检查了。
declaredNames[name] = true
end
print("Setting " .. name .. " to " .. value)
rawset(table,name,value)
end,
__index = function(_,name)
if not declaredNames[name] then
error("attempt to read undeclared variable " .. name)
else
return rawget(_,name)
end
end
}
setmetatable(_G,mt)
a = 11
local kk = aa
--输出结果为:
--[[
Setting a to 11
lua: d:/test.lua:21: attempt to read undeclared variable aa
stack traceback:
[C]: in function 'error'
d:/test.lua:21: in function <d:/test.lua:19>
d:/test.lua:30: in main chunk
[C]: ?
--]]
2. 非全局的环境: 全局环境存在一个刚性的问题,即它的修改将影响到程序的所有部分。Lua 5为此做了一些改进,新的特征可以支持每个函数拥有自己独立的全局环境,而由该函数创建的closure函数将继承该函数的全局变量表。这里我们可以通过setfenv函数来改变一个函数的环境,该函数接受两个参数,一个是函数名,另一个是新的环境table。第一个参数除了函数名本身,还可以指定为一个数字,以表示当前函数调用栈中的层数。数字1表示当前函数,2表示它的调用函数(这对写一个辅助函数来改变他们调用者的环境是很方便的),以此类推。见如下代码:
下面这段代码是企图应用setfenv 失败的例子: a = 1 -- create a global variable -- change current environment to a new empty table setfenv(1, {}) print(a) 导致: stdin:5: attempt to call global `print' (a nil value) (你必须在单独的chunk 内运行这段代码,如果你在交互模式逐行运行他,每一行 都是一个不同的函数,调用setfenv 只会影响他自己的那一行。)一旦你改变了你的环境, 所有全局访问都使用这个新的表,如果她为空,你就丢失所有你的全局变量,甚至_G, 所以,你应该首先使用一些有用的值封装(populate)她,比如老的环境:
a = 1 -- create a global variable -- change current environment setfenv(1, {_G = _G}) _G.print(a) --> nil _G.print(_G.a) --> 1
现在,当你访问"global" _G,他的值为旧的环境,其中你可以使用print 函数。
你也可以使用继承封装(populate)你的新的环境: a = 1 local newgt = {} -- create new environment setmetatable(newgt, {__index = _G}) setfenv(1, newgt) -- set it print(a) --> 1 在这段代码新的环境从旧的环境中继承了print 和a;然而,任何赋值操作都对新表 进行,不用担心误操作修改了全局变量表。另外,你仍然可以通过_G 修改全局变量:
-- continuing previous code a = 10 print(a) --> 10 print(_G.a) --> 1 _G.a = 20 print(_G.a) --> 20 当你创建一个新的函数时,他从创建他的函数继承了环境变量。所以,如果一个 chunk 改变了他自己的环境,这个chunk 所有在改变之后定义的函数都共享相同的环境, 都会受到影响。这对创建命名空间是非常有用的机制,我们下一章将会看到。
|
请发表评论