Lua学习笔记 第十四章 环境

Lua将所有的全局变量保存在一个常规的table中,这个table称为"环境(enviroment)".

这种组织结构的优点在于,其一、不需要再为全局变量创造一种新的数据结构,因此简化了Lua的内部实现。

另一个优点是,可以像其他table一样操作这个table。为了便于实施这种操作,Lua将环境table自身保存在一

个全局变量_G中。示例 —— 打印当前环境中所有全局变量的名称:

for n in pairs(_G) do print(n) end

 

14.1 具有动态名字的全局变量

对于访问和设置全局变量,通常赋值操作就可以。有时也会用到元编程的形式。如当操作一个全局变量时,

而它的名称却存储在另一个变量中,或者需要通过运行时的计算才能得到。因为环境是一个常规的table,

我们可以使用一个key去索引它,如:

value = _G[varname]     -- 获取全局变量varname的值

_G[varname] = value     -- 设置全局变量varname的值

 

上面问题的一般化形式是,允许使用动态的字段名,如"io.read" 或 "a.b.c.d"。如果直接写_G["io.read"]不会

从table io 中得到字段read。但可以写一个函数getfield来实现这个效果。这个函数是一个循环,从_G开始逐

个字段深入求值:

function getfield(f)

    local v = _G

    for w instring.gmatch(f, "[%w_]+") do

        v = v[w]

    end

    return v

end

通过string库中的gmatch函数来遍历f中所有的单词。

设置字段的函数必须一直检索到最后一个名称,然后分别进行操作。下面setfield函数就完成了这项任务,并

且创建路径中间那些不存在的table:

function setfield(f, v)

    local t = _G

    for w, d instring.gmatch(f, "([%w_]+)(%.?)") do

        if d =="." then

    t[w] = t[w]or {}

            t =t[w]

        else

            t[w] = v

        end

    end

end

上例中用到了一种字符串模式,通过这种模式可以将字段名捕获到变量w中,并将一个可选的句号捕获到d中。

 

14.2 全局变量的声明

Lua中全局变量不需要声明就可以使用。由于Lua将全局变量存放在一个普通的table中,因此可以通过元表来

修改其访问全局变量的行为。

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

setmetatable(_G, {__newindex = function(_, n)

                    error("attempt to write toundeclared variable"..n, 2)

                    end,

__index = function(_,n)

error("attemptto read undeclared variable"..n, 2)

                    end,})

执行这段代码后,所有对全局table中不存在的key的访问都将引发一个错误。这时声明新变量的方法有两种,

其一是使用rawset,它可以绕过元表:

function declare(name, initval)

rawset(_G, name,initval or false)

end

另外一种更简单的方法是只允许在主程序块中对全局变量进行赋值,当声明全局变量时只需检查此赋值是否

在主程序块中。可以使用debug库,调用debug.getinfo(2,"S")将返回一个table,这个table中的字段what表示

调用元方法的函数是主程序块还是普通函数,又或是C函数。因此可以将__newindex元方法重写为:

__newindex = function(t, n, v)

    local w =debug.getinfo(2, "S").what

    if w ~="main" and w ~= "C" then

error("attemptto write to undeclared variable"..n, 2)

    end

    rawset(t,n,v)

end

这时为了测试一个变量是否存在,就不能简单地将它与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 toundeclared variable"..n, 2)

end

                declaredNames[n] = true

            end

    rawset(t,n, v)

        end,

     __index = function(_, n)

        if not declaredNames[n] then

            error("attemptto read undeclaredvariable"..n, 2)

        else

            return nil

        end

    end,

})

此时,即使是x=nil这样的赋值也可以起到声明全局变量的作用。

上述两种声明全局变量的方法所导致的开销可以忽略不计。第一种方法中,完全没有涉及到元方法的调用。

第二种方法虽然会调用到元方法,但只有当程序访问一个为nil的变量时才会发生。

 

14.3 非全局的环境

关于环境的一大问题在于它是全局的,任何对它的修改都会影响程序的所有部分。例如,若安装一个元表用

于控制全局变量的访问,那么整个程序都必须遵循这个规范。当使用某个库时,没有声明就使用了全局变量,

那么这个程序就无法运行。Lua5.1对这个问题进行了改进,它允许每个函数都拥有一个自己的环境来查找全局变量。

可以通过函数setfenv来改变一个函数的环境。这个函数的参数是一个函数和一个新的环境table。第一个参数

处理可以指定为函数本身,还可以指定为一个数字,以表示当前函数调用栈中的层数。数字1表示当前函数,

数字2表示调用当前函数的函数,以此类推。

一旦改变了环境,所有的全局访问都会使用新的table。如果新table是空的,那么就会丢失所有的全局变量,

包括_G.所以应该先将一些有用的值录入其中,例如原来的环境:

a = 1               -- 创建一个全局变量

setfenv(1, {g=_G})  --改变当前环境

g.print(a)          -->nil

g.print(g.a)        -->1

另一种组装新环境的方法是使用继承:

a = 1

local newgt = {}    --创建新环境

setmetatable(newgt,{__index = _G})

setfenv(1, newgt)   --设置它  

print(a)            --> 1

这段代码,新环境从原环境中继承了print 和 a。然而,任何赋值都发生在新的table中。

若误改了一个全局变量也没什么,仍然能通过_G来修改原来的全局变量:

-- 继续前面的代码

a = 10

print(a)        -->10

print(_G.a)     -->1

_G.a = 20

print(_G.a)     -->20

每个函数及某些closure都有一个继承的环境。下面这段代码就演示了这种机制:

function factory()

    return function()

return a    -- 全局的a

    end

end

a = 3

f1 = factory()

f2 = factory()

print(f1())     -->3

print(f2())     -->3

 

setfenv(f1,{a=10})

print(f1())     -->10

print(f2())     -->3

factory函数创建了一个简单的closure,这个closure返回了它的全局a的值。每次调用factory都会创建一个

新的closure和一个属于该closure的环境。每个新创建的函数都继承了创建它的函数的环境。因此上例中

的closure都共享一个全局环境。这个环境中a为3,当调用setfevn(f1,{a=10})时,就改变了f1的环境,在新

环境中a为10.这期间f2的环境并未受到影响。

由于函数继承了创建其函数的环境。所以一个程序块若改变了它自己的环境,那么后续由它创建的函数都

将共享这个新环境。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值