Lua中的全局变量不需要声明就可以使用。对于小程序十分方便,但是大型程序中 一处简单的笔误就可能造成难以发现的bug。

不过,这种性能可以改变。由于Lua将全局变量放在一个普通的table中,可以通过元表来改变其访问全局变量时的行为。

一种方法是简单地检测所有对全局table中不存在key的访问:

setmetatable(_G,{
        __newindex = function(_, n)
                        error("attempt to write to undeclared variable " .. n ,2)
                    end ,    --这里的逗号不能省,因为是在table构造式里哦
        __index = function(_, n)
                        error("attempt to read undeclared variable " .. n, 2)
                    end ,   --这里的逗号不能省,因为是在table构造式里哦   
            }
)

执行这段代码后,所有对全局table中不存在的key访问都将引发一个错误:

print(a)        --> stdin:1: attempt to read undeclared variable a

但是该如何声明一个新的变量呢?

一种方法是使用rawset,可以绕过元表:

function declare(name ,initval)
    rawset(_G,name ,initval or false)    --这里的false确保 新变量不为 nil
end

另外一中更简单的方法就是只允许在主程序块中对全局变量进行赋值,那么当声明以下变量时:

a  =  1

就只需检查此赋值是否在主程序块中。这可以使用debug库,调用debug.getinfo(2,"S")将返回一个table,

其中的字段what表示了调用元方法的函数是主程序块还是普通的Lua函数,或是C函数。

可以通过该函数将__newindex元方法重写:

__newindex = function(t,n,v)
    local w = debug.getinfo(2,"S").what
    if w ~= "main" and w ~= "C" then  --接收main和C代码的修改赋值
        error("attempt to write to undeclared variable ".. n,2)
    end
    rawset(t,n,v)
end

这个版本可以接收C代码的赋值,因为一般C代码都知道自己是做什么的。

为了测试一个变量是否存在,就不能简单地与nil 比较。因为如果它为nil ,访问就会抛出一个错误,这时同样可以使用rawget来绕过元方法:

if rawget(_G , var ) ==nil then
    -- 'var' 没有声明
    ...
end

正如前面提到的,不允许全局变量具有nil 值,因为具有nil 的全局变量都会被自动认为是未声明的。

但要纠正这个问题并不难,只需引入一个辅助table用于保存已经声明变量的名称。

一旦调用了元方法,就检查该table,以确定变量是否已经声明过:

local declaredNames = {}
setmetatable( _G , {
        __newindex = function( t , n, v)
                        if not declaredNames[n] then
                            local w = debug.getinfo(2,"S").what
                            if w ~= "main" and w ~= "C" then
                                error("attempt to write to undeclared variable " .. n, 2 )
                            end
                            declaredNames[n] = true
                        end
                            rawset(t , n ,v )    --完成实际的设置
                    end , 
        __index = function (_, n)
                    if not declaredNames[n] then
                        error("attempt to read undeclared variable " .. n, 2 )
                    else
                        return nil
                    end
                end ,
            }
)

此时,即使是下面的赋值也可以起到声明全局变量的作用:

x = nil

上述两中方法所导致的开销基本可以忽略不计;

第一种方法中,完全没有涉及到元方法的调用。

第二种方法,只有当程序访问一个为nil的变量时才会去调用元方法。

以上内容来自:《Lua程序设计第二版》和《Programming in Lua  third edition》