• Lua是由宿主语言调用的,因此“程序”的概念不明确,导致全局变量在逻辑上难以控制全局的范围
  • Lua为了解决该问题,不使用全局变量,而是对全局变量进行模拟

    全局环境table _G

  • Lua将所有全局变量保存在全局环境(global environment)的普通table中(该table为_G

    • 优点:简化Lua内部实现,可以使用table操作修改这个table
    • Lua将全局环境资自身也保存在全局变量_G中(_G._G与_G等价)
      1. --遍历全局环境中的所有变量
      2. for k, v in pairs(_G) do
      3. print(k, v)
      4. end

使用动态名称的全局变量

  • 又称为元编程操作全局变量时,全局变量的名称储存在另一变量中,或需要由计算得到
    • 如:value=load(“return”..varname)()
      • 如果varname是x,就相当于return x,但是涉及代码创建和编译开销大
      • 使用value=_G[varname]效率高出一个数量级
  • 使用_G[varname] = value :为动态计算出的全局变量赋值 ```lua —这些代码解决了不能输出G[“A.B.c”]这类的问题 function getfield(f) local v = _G —从全局变量table _G开始迭代 for k in string.gmatch(f, “[%a][%w_]*”) do
      v = v[k]
    
    end return v end

function setfield(f, v) local t = G for v1, v2 in string.gmatch(f, “([%a][%2_]*)(%.?)”) do if v2 == “.” then —查看是不是要访问的最后一个 t[v1] = t[v1] or {} —该table不存在,则创建 t = t[v1] else t[v1] = v —赋值 end end end


<a name="uZpsz"></a>
### 全局变量的声明、检查
<a name="FW2NQ"></a>
#### 检测不存在的全局变量

- 方法1
   - 通过**对**全局环境**table _G设置元表**,通过元表来**发现不存在的全局变量**的情况
   - 使用**rawset**和**rawget**函数来**绕过元方法**进行全局变量操作
```lua
setmetatable(
    _G,
    {
        __newindex = function(t, k, v)        --元方法:处理新建不存在的全局变量
            error("attempt  to write to undeclared variable" .. k)
        end,

        __index = function(t, k)                    --元方法:处理访问不存在的全局变量
            error("attempt to read undeclared variable " .. k)
        end
    }
)

function declare(name, initval)                
    rawset(_G, name,initval or false)        --绕过元方法进行赋值,
      --使用initval or false来处理赋nil值的情况(模拟创建全局变量而不复制的情况)
end
  • 方法2

    • 新全局变量赋值限制在仅能在函数内进行
      • 使用调试库debug.getinfo(2,”S”)返回一个table,其中what字段表示调用该函数的代码在主代码段还是普通代码段
        --对__newindex进行重写
        __newindex = function(t, k, v)
        local w = debug.getinfo(2, "S").what
        if w ~= "mmain" and w ~= "C" then
        error("attempt to write to undeclared variable" .. k)
        end
        rawset(t, k, v)
        end
        

        测试全局变量是否存在

  • 不能直接与nil进行比较,因为元方法中会对其直接报错

    • 使用rawget函数绕过元方法
      if rawget(_G, var) == nil then
      --'var'未声明代码
      end
      

      创建允许值为nil的全局变量

  • 使用一个辅助table,保存已声明的全局变量,调用元方法就会检查该table

    local declaredName = {}        --用于保存已声明变量的辅助table
    setmetatable(
      _G,
      {
          __newindex = function(t, k, v)
              if not declaredName[k] then        --在辅助table中存在则跳过,不存在则执行
                  local w = debug.getinfo(2, "S")
                  if w ~= "main" and w ~= "C" then
                      error("attempt to write undeclared varible" .. k)
                  end
                  declaredName[n] = true        --在辅助table中加入该键
              end
              rawset(t, k, v)            --填过元表插入table
          end,
    
          __index = function(t, k)        
              if not declaredName[k] then        --在辅助table中存在则跳过,不存在则执行
                  error("attempt to read undeclared varible" .. k)
              else
                  return nil            
              end
          end
      }
    )
    

    strict.lua(重要)

  • 注:上述的多种方法造成的开销基本都可以忽略不计,最好直接调用Lua发行的strict.lua模块

    • 一种在普通table操作期间不会执行
    • 一种只有在访问值为nil的变量时执行

Lua中全局环境的解读(适用于Lua5.2)

  • Lua中实际上并没有全局变量,全局变量只是编译器对其的一种模拟机制
  • 自由名称没有显式声明上的名称,即不出现在对应局部变量的范围内
    • 如:local z = 10 ; x = y + z ;中xy自由名称,而z不是
      • Lua将x转换为_ENV.x使代码转换为_ENV.x = _ENV.y + z
  • _ENV:一个预定义的上值,Lua是在该值存在的情况编译代码
    • 一个外部的局部变量
      • 外部:相对于_ENV来说,可以解释为传递给_ENV的参数。如:_ENV=var;var则为_ENV的外部变量
    • 初始值可以是任意table
    • 加载时,load会使用预定义的上值,进行初始化全局环境

  • --可以将代码的处理流程简单的转换为如下形式
    local _ENV = the_global_environment(全局环境)    --预定义的上值
    return function(...)
      local z = 10
      _ENV.x = _ENV.y + z
    end
    

    Lua处理全局变量的方式:

  1. 编译所有代码前,外层创建全局变量_ENV
  2. 所有自由名称转换为_ENV.var
  3. 函数load,使用全局环境初始化代码段的第一个上值,Lua语言内部维护一个table

使用环境:_ENV

  • _ENV只是一个普通的变量,但是 _ENV = nil 会使得后续代码不能直接访问全局变量
  • 可以使用_ENV来绕过局部声明(_G也可以做到)
    • 通常_G_ENV指的是同一个table
  • 对自由名称的使用会绑定在当前环境中 ```lua local print, sin = print, math.sin _ENV = nil —更改新的环境 print(“hello”) —输出:hello,已保存在局部变量中的可以访问 print(sin(10)) —输出:-0.54402111088937 print(math.cos(10)) —报错,全局变量已经不能再访问

a = 1 —添加至全局变量 local a = 10 —添加至局部变量 print(a) —输出:10,局部变量中的值 print(_ENV.a) —输出:1 ,全局变量中的值

<a name="vx850"></a>
### _ENV VS _G

- _ENV是一个**局部变量**,所有对**“全局变量”**的访问实际上**访问_ENV**
   - 该全局变量指:**当前环境中的全局变量**
   - _ENV永远**指向当前环境**
   - **修改_ENV**即可**修改当前环境**,修改**之后**,**局部变量**就会**保存在新的_ENV环境**中
      - **修改**了_ENV环境之**后**,**全局变量**的**访问**就会使用**新的_ENV table**
      - **新环境**_ENV table中**没有任何全局变量**,包括函数;
         - 在使用新_ENV之前,**应该将有用的值放入新环境中**(如**全局函数_G**)
- _G是一个任何情况下都没有任何特殊状态的“全局变量”
   - 没有改变其值时,**_G**通常指向的是Lua**最初环境**中的**全局环境**
      - 只有在**初始化全局table时**,会自动让**_G指向自己**,这是才会出现特殊状态(即**_G就是_ENV**)
```lua
a = 15                            --在初始环境的全局变量中声明a
_ENV = {_G = _G}        --新建一个环境,将初始环境中的全局变量加入新环境中
a = 1                                --此时的全局变量a储存在,新环境的_ENV中
_G.print(a)                    --输出:1        -_G.print实际上被编译为_ENV.G.print;a被编译为_ENV.a
_G.print(_G.a)            --输出:15        -_G.a被编译为_ENV._G.a

_ENV = {_G = _G}        --再新建一个新环境_ENV,最右边的_G被编译为_ENV._G
a = 25                            --储存在新的环境中
_G.print(a)                    --输出:25
_G.print(_G.a)            --输出:15
--将初始全局变量给新环境之后,_ENV中的_G指代的就是初始环境中的全局变量,
--而_ENV中的是当前环境中的全局变量

继承方式创建新环境

  • 新函数会继承最初环境的全局变量中的函数,
  • 新环境中的赋值都会保存到新table中(_ENV)

    a = 1            --保存到全局变量中
    local newgt = {}    
    setmetatable(newgt, {__index = _G})        --给新环境设置元表
    _ENV = newgt            --设置新环境
    print(a)                    --输出最初环境中的a,并且能够使用原本全局变量中的函数
    

    使用私有环境定义函数

  • 可以做到让多个函数共享一个公共环境

  • 还可以让一个函数使用多个环境 ```lua function factort(_ENV) —使用闭包保存环境 return function() —返回一个匿名函数
      return a
    
    end end

f1 = factort({a = 1}) —a储存在闭包中,调用f1()则可访问环境中的a f2 = factort({a = 2}) print(f1()) —输出:1 print(f2()) —输出:2


<a name="tiLcl"></a>
### 环境和模块

- **模块的特点**:在**模块中修改环境(_ENV),不会导致调用该模块的Lua程序环境(_ENV)改变**
   - 依据此特点:
      - 修改模块的环境,该模块的所有共有函数都会进入该环境中
      - **解决**了**模块**中忘记使用local导致**污染全局环境**的问题
- **注:使用原始的基本方法,会使得代码更加清晰**
   - **可以在模块中使_ENV=nil,这样如果在模块中对全局变量赋值,就会抛出异常**
      - **先将全局变量_G保存为局部变量 或 将要访问的模块保存为局部变量**
```lua
---使用模块中环境的特点来创建模块
--test2.lua
local M = {}        --创建该模块使用的环境
_ENV = M                --替换环境

a = 1                        --虽然a是全局变量,但是是保存在环境M中的全局变量
function add(c1, c2)    --保存在环境M中的全局函数
    return c1 + c2
end

return M
--test.lua
local M = require("test2")    --调用模块test2.lua
print(a)                                        --输出:nil    因为模块的全局变量a并不在此时全局环境中
print(M.add(2, 3))                    --输出:5

---使用_ENV=nil,让模块无法改变全局变量
local _G = _G
_ENV = nil                    --清空环境,只能操作局部变量
local a=1                        --正确,因为a为局部变量
b = 1                                --会报错,因为这样给全局变量a赋值了
--------------
--更规范的方法:将要用的模块保存
local print=print        --只将要用的模块保存
local io=io
_ENV=nil                        --清空环境,只能操作局部变量

Lua按顺序编译,调用时运行

local foo        --设置为局部
do
    local _ENV = _ENV        --将do-end内的局部_ENV设置为全局的_ENV
                                              --因为_ENV为table,Lua中为table类型的标识符可以看成指针,指向同一内存地址,并不是复制
    function foo()            
        print(X)                --编译为_ENV.print(_ENV.X),这里的_ENV为do-end的局部_ENV
    end
end
X = 13                    --保存在初始环境的全局变量中
_ENV = {X=11}        --更改环境变量
foo()                        
--运行函数foo()中已编译的代码,即_ENV.print(_ENV.X)的编译结果,
--但是该_ENV指的是do-end局部变量中的,并不是当前环境变量中的(不是同一指针)