1.环境
-- lua将全局环境自身保存在全局变量_G中
for n in pairs(_G) do print(n) end
2.非全局环境
local z = 10
x = y + z
-- 等价形式
local z = 10
_ENV.x = _ENV.y + z
-- lua语言把所有代码段均当作匿名函数,lua语言编译器将上述代码编译为。
local _ENV = some value
return function(...)
local z = 10
_ENV.x = _ENV.y + z
end
为了维持全局变量存在的幻觉,lua语言在内部维护了一个表来用作全局环境。
通常,加载一个代码段时,函数load会使用预定义的上值来初始化全局环境。
总结,Lua语言中处理全局变量的方式:
a.编译器在编译所有代码段前,在外层创建局部变量_ENV;
b.编译器将所有自由名称var变换为_ENV.var;
c.函数load(或函数loadfile)使用全局环境初始化代码段的第一个上值,即Lua语言内部维护的一个普通的表。
3.使用_ENV
交互模式下一行一行的输入代码,则每一行代码会变成一段独立的代码,故每一行会有一个不同的_ENV变量。为了把代码当作一个代码段运行,要么把代码保存在一个文件中运行,要么使用do–end将代码段包围起来。
// 1.lua
local print, sin = print, math.sin
_ENV = nil
print(13)
print(sin(13))
print(math.cos(13)) -->交互模式下执行dofile("1.lua")到这里会抛出异常
再看一个
a = 13
local a = 12
print(a) -->交互模式执行dofile,这里输出12
print(_ENV.a) -->交互模式执行dofile,这里输出13
再看一个
a = 13
local a = 12
print(a) -->交互模式执行dofile,这里输出12
print(_G.a) -->交互模式执行dofile,这里输出13
_G和_ENV指向同一个表。
4.弱引用表
正常情况下,若表中存在某个键,或值。则存在对相应键,值的引用,进而使得其不会被垃圾回收。
lua当某个实例的元表的__mode为“k”,"v"或"kv"时,表示相应的键,值,键值是弱引用的。
a = {}
mt = {__mode = "k"}
setmetatable(a, mt)
key = {}
a[key] = 1
print(key)
key = {}
print(key)
a[key] = 2
collectgarbage() -->强制执行一次垃圾回收
for k,v in pairs(a) do print(v) end
对以上的解释是,key先后指向两个实例。
由于a表中的key不会计入对实例的引用,所以,执行到第二个key赋值处,之前key所指向的实例已经变为0引用,此时,原来的key代表的实例会从a中移除。
再看一个
a = {}
mt = {__mode = "v"}
setmetatable(a, mt)
key = {}
a[1] = key
print(key)
key = {}
print(key)
a[2] = key
collectgarbage()
for k,v in pairs(a) do print(k,v) end
输出为:
一样的道理。第二次对key赋值后,前一次赋值的实例引用数变为0,此时会自动将这个值及其对应的键从a中移除。
5.析构器
o = {x = "hi"}
setmetatable(o, {__gc = function (o) print(o.x) end})
o = nil
collectgarbage()
针对具有析构器的对象,需要经历两轮垃圾回收才会被释放。
第一次被回收时,由于存在析构器,先将其放入等待析构队列。
析构队列处理完毕,后续再次被回收时,实际回收。
6.反射
调试库由两类函数组成:自省函数和钩子。
自省函数允许我们检查一个运行中程序的几个方面,如活动函数的栈,当前在执行的代码行,局部变量的名称和值。钩子则允许我们跟踪一个程序的执行。
7.自省机制
对getinfo,该函数的第一个参数可以是一个函数或一个栈层次。当为某个函数foo调用debug.getinfo(foo)时,函数会返回一个包含与该函数有关的一些数据的表。这个表可能有以下字段:
source:用于说明函数定义的位置,如函数定义在一个字符串中,则source就是这个字符串。如函数定义在一个文件中,则source就是用@作为前缀的文件名。
short_src:source的精简版本。
linedefined:该字段是函数定义在源代码中第一行的行号。
lastlinedefined:该字段是该函数定义在源代码中最后一行的行号。
what:该字段用于说明函数的类型。如foo是一个普通的函数,则为"Lua";如是一个C函数,则为"C";如是一个Lua代码段的主要部分,则为"main"。
name:该字段是该函数的一个适当的名称。
namewhat:该字段用于说明上一个字段的含义,可能是"global",“local”,“method”,“field"或”"。空字符串表示Lua语言找不到该函数的名称。
nups:该字段是该函数的上值的个数。
nparams:该字段是该函数的参数个数。
isvararg:该字段表明该函数是否为可变长参数函数。
activelines:该字段是一个包含该函数所有活跃行的集合。活跃行是指除空行和只包含注释的行外的其他行。
func:该字段是函数本身。
当foo是一个C函数时,Lua语言没多少关于该函数的信息。对于这种函数,只有what,name,namewhat,nups,func有意义。
当使用一个数字n作为参数调debug.getinfo(n)时,可得到有关栈层次上活跃函数的数据。
层次0是getinfo自己。
通过带有栈层次的debug.getinfo查询一个活跃函数时,返回的表中还有两个字段:
currentline:当前行
istailcall:尾调用
8.访问局部变量
可通过debug.getlocal来检查任意活跃函数的局部变量。
该函数有两个参数,一个是要查询函数的栈层次,另一个是变量的索引。
函数返回两个值,变量名和变量的当前值。
如变量索引大于活跃变量的数量,则getlocal返回nil。
如栈层次无效,则抛出异常。
可通过debug.setlocal来设置局部变量。
该函数有三个参数,一个是要查询函数的栈层次,另一个是变量的索引,一个是变量的新值。
9.访问非局部变量
可通过getupvalue访问一个被Lua函数所使用的非局部变量。
被一个函数所引用的非局部变量即使在引用它的函数已经不活跃的情况下也会一直存在。(闭包)
getupvalue的第一个参数不是栈层次,是一个函数。第二个参数是变量索引,Lua语言按引用非局部变量的顺序对它们编号,但由于一个函数不能用同一个名称访问两个非局部变量,故顺序无所谓。
还可通过debug.setupvalue更新非局部变量的值。该函数有三个参数:一个闭包,一个变量索引,一个新值。
10.钩子
调试库的钩子机制允许用户注册一个钩子函数,这个钩子函数会在程序运行中某个特定事件发生时被调用。有四种事件能触发一个钩子:
a.当调用一个函数时产生的call事件
b.当函数返回时产生的return事件
c.当开始执行一行新代码时产生的line事件
d.执行完指定数量的指令后产生的count事件。
Lua语言用一个描述导致钩子函数被调用的事件的字符串为参数来调用钩子函数,包括"call",“return”,“line"或"count”。对line事件来说,还有第二个参数,即新行号。
要注册一个钩子,需用两个或三个参数来调用函数debug.sethook:第一个参数是钩子函数,第二个参数是描述要监控事件的掩码字符串,第三个参数是一个用于描述以何种频度获取count事件的可选数字。如要监控call,return,line,则需把这几个事件的首字母(c,r或l)放入掩码字符串。如要监控count,则需在第三个参数中指定一个计数器。如关闭钩子,只需不带任何参数地调用sethook即可。
debug.sethook(print, "l") -->把print安装为钩子,告诉Lua在line事件发生时调用它。
11.c语言API
,第二个参数是描述要监控事件的掩码字符串,第三个参数是一个用于描述以何种频度获取count事件的可选数字。如要监控call,return,line,则需把这几个事件的首字母(c,r或l)放入掩码字符串。如要监控count,则需在第三个参数中指定一个计数器。如关闭钩子,只需不带任何参数地调用sethook即可。
debug.sethook(print, "l") -->把print安装为钩子,告诉Lua在line事件发生时调用它。