使用过一段时间lua,基本上都会知道 全局变量 和 局部变量,但实际上,在lua中,却不仅仅只是如此,与他们相关联的是 环境
在lua中我们怎么使用全局变量呢?
a = nil -- 什么也不干,相当于不存在
a = 1 -- 声明并初始化了一个全局变量
_G.a = 1 -- 同上
好了,在进入正题之前,我们只需要知道有以下这些概念,读者不必完全明白(这是我的工作,只需要知道有这么个东西即可)
-
在最后一个示例中,我们使用了一个表
_G,我们就将其称作 全局环境表, -
将 不用
local修饰的变量称为 自由变量吧,避免歧义 -
将
_ENV称作 当前环境表 -
代码段 是lua解释器编译和执行的基本单位,通常是lua文件,或者由 load动态加载的代码(本质上还是由load动态加载的代码,我们后面会详细说明)
-
upvalue 上值,在函数作用域之外的局部变量(简单来说,就是你在函数内使用了一个函数外声明的local变量),它和函数一起构成了闭包(简单来说,就是 upvalue+函数=闭包,函数会记住周围的环境)
我们有以下代码,非常简单的答应两个变量
local a = 1
b = 2
print(a, b)
lua解释器在编译执行这个 代码段 前,会先做几件事情:
- 将所有代码段看成是一个匿名函数(接收可变参数),并返回这个匿名函数,代码如下:
return function(...) -- 匿名函数
-- 原本代码段
local a = 1
b = 2
print(a, b)
end
- 在匿名函数外层创建一个局部变量
_ENV,也就是一个 upvalue 上值(还记得我们的定义吗),代码如下:
local _ENV -- upvalue上值
return function(...) -- 匿名函数
-- 原本代码段
local a = 1
b = 2
print(a, b)
end
- 将匿名函数中的所有 自由变量 加上前缀 _ENV.,比如自由变量
x,就变为_ENV.x,代码如下:
local _ENV -- upvalue上值
return function(...) -- 匿名函数
-- 原本代码段
local a = 1
_ENV.b = 2
print(a, _ENV.b)
end
- 由函数load初始化当前环境表 _ENV,默认用 全局环境表 _G 初始化,但也可以指定其他值,代码如下:
local _ENV = _G -- upvalue上值
return function(...) -- 匿名函数
-- 原本代码段
local a = 1
_ENV.b = 2
_ENV.print(a, _ENV.b)
end
好了,经过一番处理,我们得到了最终形态的代码段,接下来就是执行阶段了,我们最终执行代码段,其实就相当于调用这个匿名函数,这个可变参数可以看成是接收命令行参数(我猜的)
当我们执行local a = 1,没什么变化
再执行_ENV.b = 2,相当于_G.b = 2,因为此时 _ENV 和 _G 指向同一个表,这就回到我们熟悉的使用方式了
最后执行_ENV.print(a, _ENV.b),什么?print不是函数吗?没错,在Lua中,函数其实也是一种变量,并且一些标准库的函数已经被提前加载到 _G 中了,所以我们才能在代码段中直接使用他们,所以和上面一句类似,这一句也等同于_G.print(a, _G.b)
现在,让我们看看在开始阶段,_G 中都有什么
-- 遍历 _G
for key, val in pairs(_G) do
print(key, val)
end
PS D:\Vim\workspace> lua53.exe .\test.lua
dofile function: 00000000684992c0
tonumber function: 00000000684986f0
require function: 00000000006a8070
setmetatable function: 0000000068498eb0
assert function: 0000000068499340
rawget function: 0000000068498980
utf8 table: 00000000006a9c00
next function: 0000000068498be0
loadfile function: 00000000684991f0
bit32 table: 00000000006a9d80
string table: 00000000006a9740
_VERSION Lua 5.3
load function: 00000000684990f0
collectgarbage function: 0000000068498de0
rawlen function: 00000000684989d0
xpcall function: 00000000684984a0
rawequal function: 0000000068498a30
pcall function: 00000000684985a0
table table: 00000000006a92f0
coroutine table: 00000000006a9340
_G table: 00000000006a6eb0 --------- 包含了自身
pairs function: 0000000068499030
type function: 0000000068498540
arg table: 00000000006a9780
io table: 00000000006a9980
ipairs function: 0000000068499050
select function: 0000000068498650
math table: 00000000006a9880
package table: 00000000006a80c0
error function: 0000000068498d60
debug table: 00000000006a9d40
os table: 00000000006a9700
print function: 0000000068498a80
tostring function: 0000000068498620
getmetatable function: 0000000068499260
rawset function: 0000000068498920
可以看到,除了一些标准库之外,它还包含了自身,这就意味着_G._G._G等价于_G
好了,现在在升级一下,让我们在代码中直接修改这两个表,看看会发生什么
-- 修改代码
print("before:")
print(_G == _ENV)
print(_ENV._ENV)
print(_ENV._ENV == _ENV)
print(_ENV._G == _ENV)
_G = {} -- 指向了一个新的表
print("\nafter:")
print(_ENV._ENV)
print(_G == _ENV)
print(_ENV._ENV == _ENV)
print(_ENV._G == _ENV)
_G.a = 1
a = 2
_ENV.a = 3
print(a, _G.a, _ENV.a)
执行结果
PS D:\Vim\workspace> lua53.exe .\test.lua
before:
true
nil
false
true
after:
nil
false
false
false
3 1 3
我这里就不细讲了,大家按照前面讲过的方法,分析分析,就能够明白了
至于load函数,其实蛮简单的,你把它看成是一个嵌入的文件也没问题,只不过它有自己的环境,默认初始值是_G,所以相当于就在当前环境,但是你可以设置不同的初始环境,这时候就有不同的表现了
最后在讲一个,我们可以在一个作用域内创建一个新的环境,而不影响作用域外的环境
local function test()
-- 创建了一个新的环境,但仅限于函数内,
local _ENV = {print = function (...) print("hello"); print(...) end}
-- 会调用当前环境表 _ENV,最开始的 _ENV会被当前函数内的 _ENV覆盖
-- 就和普通变量一样,函数优先调用局部变量
print("new environment in test")
end
test()
print("nothing happend (no hello)")
PS D:\Vim\workspace> lua53.exe .\test.lua
hello
new environment in test
nothing happend (no hello)
好了,以上就是我的分享,希望能够有所帮助,如有不当,感谢指出
posted on
浙公网安备 33010602011771号