|
一 lua 闭包
lua闭包_汪汪富贵的博客-CSDN博客_lua 闭包
print("\nexample 3:");
function counter()
local count = 0;
return function()
count = count + 1;
return count;
end
end
func = counter();
print(func());
print(func());
print(func());我期待的输出是1,1,1,结果输出了1,2,3 3次打印的时候,访问的count都是这个匿名函数外的局部变量count。这个涉及到知识点
ua函数可以被当成参数传递,也可以被当成结果返回,在函数体中仍然可以定义内嵌函数。lua闭包是Lua函数生成的数据对象。每个闭包可以有一个upvalue值,或者多个闭包共享一个upvalue数值。
1、upvalue
如果函数f2定义在函数f1中,那么f2为f1的内嵌函数,f1为f2的外包函数,外包和内嵌都具有传递性,即f2的内嵌必然是f1的内嵌,而f1的外包也一定是f2的外包。
内嵌函数可以访问外包函数已经创建的局部变量,而这些局部变量则称为该内嵌函数的外部局部变量(或者upvalue)
function f1(n)
-- 函数参数也是局部变量
local function f2()
print(n) -- 引用外包函数的局部变量
end
return f2
end
g1 = f1(1979)
g1() -- 打印出1979
g2 = f1(500)
g2() -- 打印出500
当执行完g1 = f1(1979)后,局部变量n的生命本该结束,但因为它已经成了内嵌函数f2的upvalue,f2又被赋给了变量g1,所以它仍然能以某种形式继续“存活”下来,从而令g1()打印出正确的值。
2、闭包
Lua编译一个函数时,其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的函数时,它就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(用来查找全局变量的表)的引用以及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。g1和g2的值严格来说不是函数而是闭包,并且是两个不相同的闭包,而这两个闭包有各自的upvalue值。
使用upvalue代码如下:
function f1(n)
local function f2()
print(n)
end
n = n + 10
return f2
end
g1 = f1(1979)
g1() -- 打印出1989
g1()打印出来的是1989,原因是打印的是upvalue的值。
upvalue实际是局部变量,而局部变量是保存在函数堆栈框架上的,所以只要upvalue还没有离开自己的作用域,它就一直生存在函数堆栈上。这种情况下,闭包将通过指向堆栈上的upvalue的引用来访问它们,一旦upvalue即将离开自己的作用域,在从堆栈上消除之前,闭包就会为它分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该upvalue。当执行到f1(1979)的n = n + 10时,闭包已经创建了,但是变量n并没有离开作用域,所以闭包仍然引用堆栈上的n,当return f2完成时,n即将结束生命,此时闭包便将变量n(已经是1989了)复制到自己管理的空间中以便将来访问。
3、upvalue和闭包数据共享
upvalue还可以为闭包之间提供一种数据共享的机制。
(1)单重内嵌函数的闭包 (函数创建的闭包)
一个函数创建的闭包共享一份upvalue。
function Create(n)
local function foo1()
print(n)
end
local function foo2()
n = n + 10
end
return foo1,foo2
end
f1,f2 = Create(1979)--创建闭包
f1() -- 打印1979
f2()
f1() -- 打印1989
f2()
f1() -- 打印1999
f1,f2这两个闭包的原型分别是Create中的内嵌函数foo1和foo2,而foo1和foo2引用的upvalue是同一个,即Create的局部变量n。执行完Create调用后,闭包会把堆栈上n的值复制出来,那么是否f1和f2就分别拥有一个n的拷贝呢?其实不然,当Lua发现两个闭包的upvalue指向的是当前堆栈上的相同变量时,会聪明地只生成一个拷贝,然后让这两个闭包共享该拷贝,
这样任一个闭包对该upvalue进行修改都会被另一个探知。上述例子很清楚地说明了这点:每次调用f2都将upvalue的值增加了10,随后f1将更新后的值打印出来。upvalue的这种语义很有价值,它使得闭包之间可以不依赖全局变量进行通讯,从而使代码的可靠性大大提高。
(2)多重内嵌函数的闭包 (闭包创建的闭包)
同一闭包创建的其他的闭包共享一份upvalue。
内层闭包在创建之时其需要的变量就已经不在堆栈上,而是引用更外层外包函数的局部变量(实际上是upvalue)。
function Test(n)
local function foo()
local function inner1()
print(n)
end
local function inner2()
n = n + 10
end
return inner1,inner2
end
return foo
end
t = Test(1979)--创建闭包(共享一份upvalue)
f1,f2 = t()--创建闭包
f1() -- 打印1979
f2()
f1() -- 打印1989
g1,g2 = t()
g1() -- 打印1989
g2()
g1() -- 打印1999
f1() -- 打印1999
执行完t = Test(1979)后,Test的局部变量n就结束生命周期了,所以当f1,f2这两个闭包被创建时堆栈上根本找不到变量n。Test函数的局部变量n不仅是foo的upvalue,也是inner1和inner2的upvalue。t = Test(1979)之后,闭包t 已经把n保存为upvalue,之后f1、f2如果在当前堆栈上找不到变量n就会自动到它们的外包闭包(这里是t的)的upvalue引用数组中去找.
g1和g2与f1和f2共享同一个upvalue。因为g1和g2与f1和f2都是同一个闭包t 创建的,所以它们引用的upvalue (变量n)实际也是同一个变量,而它们的upvalue引用都会指向同一个地方。
二;LuaGC
1.Lua垃圾回收算法原理简述
lua采用了标记清除式(Mark and Sweep)GC算法,算法简述:
标记:每次执行GC时,先以若干根节点开始,逐个把直接或间接和它们相关的节点都做上标记;
清除:当标记完成后,遍历整个对象链表,把被标记为需要删除的节点一一删除即可。
2.Lua垃圾回收中的三种颜色
所谓的颜色就是上文中“算法简述”提到过的标记,lua用白、灰、黑三色来标记一个对象的可回收状态。(白色又分为白1、白2)
白色:可回收状态。
详解:如果该对象未被GC标记过则此时白色代表当前对象为待访问状态。举例:新创建的对象的初始状态就应该被设定为白色,因为该对象还没有被GC标记到,所以保持初始状态颜色不变,仍然为白色。如果该对象在GC标记阶段结束后,仍然为白色则此时白色代表当前对象为可回收状态。但其实本质上白色的设定就是为了标识可回收。
灰色:中间状态。
详解:当前对象为待标记状态。举例:当前对象已经被GC访问过,但是该对象引用的其他对象还没有被标记。
黑色:不可回收状态。
详解:当前对象为已标记状态。举例:当前对象已经被GC访问过,并且对象引用的其他对象也被标记了。
备注:白色分为白1和白2。原因:在GC标记阶段结束而清除阶段尚未开始时,如果新建一个对象,由于其未被发现引用关系,原则上应该被标记为白色,于是之后的清除阶段就会按照白色被清除的规则将新建的对象清除。这是不合理的。于是lua用两种白色进行标识,如果发生上述情况,lua依然会将新建对象标识为白色,不过是“当前白”(比如白1)。而lua在清扫阶段只会清扫“旧白”(比如白2),在清扫结束之后,则会更新“当前白”,即将白2作为当前白。下一轮GC将会清扫作为“旧白”的白1标识对象。通过这样的一个技巧解决上述的问题。如下图:(下图中为了方便颜色变换的理解,没有考虑barrier的影响)
三;Lua 元表(Metatable)
1、元表的作用
Lua中元表(metatable)的作用:扩充对table的操作
元表可以定义一个table在遇到未知操作(元方法)时的行为,对于a和b两个table,是无法进行相加操作(a + b)。
a, b = {1,2,3}, {4,5}
c = a + b --error
而通过元表和元方法可以实现两个table相加。当Lua遇到两个table相加时a + b,会先检测两者任一是否有元表,且该元表中是否有__add(元方法)字段,如果找到了,就会调用__add字段的值。
2、如何设置元表
mytable = {} --普通表
mymetatable = {} --元表,扩展了普通表的行为
setmetatable(mytable, mymetatable) --设置mytable的元表为mymetatable
mymetatable = getmetatable(mytable) --获取mytable的元表
通过元表和元方法可以实现两个table相加。当Lua遇到两个table相加时a + b,会先检测两者任一是否有元表,且该元表中是否有__add(元方法)字段,如果找到了,就会调用__add字段的值。
--例1:table的+运算符
a, b = {1,2,3}, {4,5}
setmetatable(a, { --设置a的元表
__add = function(t1, t2) --__add元方法,定义了table相加的操作
for k, v in pairs(t2) do
table.insert(t1, v)
end
return t1
end,
__tostring = function(t) --__tostring元方法,定义table的print操作
s = ""
for k, v in pairs(t) do
s = s..v.." "
end
return s
end
})
print(a) -- 1 2 3
c = a + b
print(c) -- 1 2 3 4 5
元方法
1、__index元方法
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由__index返回结果
mytable = {1,2,3}
mymetatable = {
__index = function(tab, key) --当访问tab中一个不存在的索引时,会调用__index的函数
return "metatablevalue"
end
}
setmetatable(mytable, mymetatable)
print(mytable[3]) --输出3
print(mytable[4]) --输出metatablevalue
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
3.判断元表有没有__index 方法,如果 __index 方法为 nil,则返回 nil;如果__index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
2、__newindex方法
__newindex 元方法用来对表更新,__index则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
mytable = {1,2,3}
mymetatable = {
__newindex = function(tab, key, value) --对表更新新的键值对时,会调用该函数
print("添加key值"..key..",value值为"..value)
rawset(tab, key, value) --对mytable进行更新
end
}
setmetatable(mytable, mymetatable)
mytable[1] = 1
mytable[4] = 4 --打印:添加key值4,value值为4
print(mytable[4]) --打印:4。没有rawset,则会输出nil
mytable = {1,2,3}
newtable = {}
mymetatable = {
__newindex = newtable --如果是table,则将新键值对添加到table中
}
setmetatable(mytable, mymetatable)
mytable[1] = 1
mytable[4] = 4
print(mytable[4]) --输出nil
print(newtable[4]) --输出4
见例1
模式 描述
__add 对应的运算符 ‘+’.
__sub 对应的运算符 ‘-’.
__mul 对应的运算符 ‘*’.
__div 对应的运算符 ‘/’.
__mod 对应的运算符 ‘%’.
__unm 对应的运算符 ‘-’.
__concat 对应的运算符 ‘…’.
__eq 对应的运算符 ‘==’.
__lt 对应的运算符 ‘<’.
__le 对应的运算符 ‘<=’.
4、__call元方法
当把元表作为函数来使用时,会调用__call元方法
mytable = {1,2,3}
mymetatable = {
__call = function(tab, arg)
print(&#34;调用__call:&#34;..arg)
return arg
end
}
setmetatable(mytable, mymetatable)
mytable(5) --调用__call:5
原文链接:Lua元表_ailinlin1997的博客-CSDN博客_lua 元表 |
|