使用过一段时间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 2025-04-26 21:43  Dylaris  阅读(69)  评论(0)    收藏  举报