环境的概念
Lua中类型为thread,function和userata的对象都可以关联一个表,称之为环境。环境也是一个常规的table。可以和普通的table一样进行操作,存放与对象相关的各种变量。
- 关联的thread上的环境只能通过C代码中访问。
- 关联在userdata上的环境在 Lua 中没有意义。 这个东西只是为了在程序员想把一个表关联到一个 userdata 上时提供便利。
- 关联在function上的环境用来接管本函数内全局变量的访问。
全局变量
Lua中的全局变量存在放当前函数的环境中,Lua标准库中的函数如setmetable, string.find等注册在函数的环境中,在Lua脚本就可以直接访问。
操作函数的环境
Lua代码中可以访问和操作和函数关联的环境,Lua标准库为此提供了两个方法
- getfenv 获取当前函数的环境
- setfenv 设置当前对象函数关联的环境
查看所有的全局变量
通过getfenv(0)获取当前环境后遍历,可以查看所有的全局变量。
1 |
|
定义全局变量与局部变量
Lua中定义的变量默认为全局变量,保存在当前函数的环境中。 如下面的代码
1 |
|
变量var为全局变量,存放在当前的环境中,通过getfenv().var
可以访问,而变量local_var为局部变量,getfenv().local_var
值为nil
执行结果
1 |
|
嵌套函数的环境
在thread中通过load等方式创建的函数称为非嵌套函数,非嵌套函数的默认环境为此thread的环境。在函数中创建函数时,会将自己的环境设置为新创建的函数的默认环境。
访问全局变量时访问的是当前所在函数的环境,如下面的代码
1 |
|
执行结果 f,f1,f2三个函数的环境是同一个table
1 |
|
改变函数的环境
因为函数f1和f2都是在函数f中创建的,他们的环境的初始值就是函数f的环境。 下面通过setfenv改变函数的环境
1 |
|
执行结果如下,getfenv本身就在环境中存放,通过setfenv(f2, {})
将函数f2的环境设置成一个空的table,在函数f2中调用getfenv时由于getfenv是nil,导致脚本报错。
1 |
|
将函数f的环境通过闭包保存下来,就可以在函数f2中调用其中的方法。如下所示
1 |
|
此时可以看到函数f2的环境与函数f和f1不同
1 |
|
_G
Lua中的_G是一个指向全局环境(thread的环境)的全局变量。详细一点可以这样理解
- thread有一个环境,称为全局环境
- 全局环境中有一个变量,变量名为_G, 值为全局环境(_G._G = _G?)
- thread中调用load(string)或者loadstring等,解析Lua代码,生成一个函数
- 生成的函数的环境默认为thread的环境
- 在非嵌套函数中使用_G如
print(_G)
, 打印的是全局环境,也是此函数的环境。
输出_G和_G._G,值完全相同
1 |
|
如果通过setfenv设置了环境,新的环境是没有_G这个变量的。
1 |
|
执行结果
1 |
|
GETGLOBAL 和 lua_getglobal的迷惑
调用print输出字符串print("Hello")
,可以通过luac查看编译后的lua虚拟机的指令
1 |
|
这里只关注GETGLOBAL这条指令,print是全局变量,需要通过GETGLOBAL取到这个变量的值
对于GETGLOBAL,Lua虚拟机的执行如下(以lua5.1为例),在函数luaV_execute中
1 |
|
注意sethvalue(L, &g, cl->env);
这句,cl代表当前的函数,cl->env指向当前函数的环境。即GETGLOBAL指令取得的是从当前函数的环境中取得变量的值的。
而lua_getglobal这个函数确是直接访问thread的全局环境,取得全局环境中key为name
的值。
1 |
|
同样都是”getglobal”,却是两个不同的意思,直接让我迷惑了很久,差点分不清什么是全局变量了。