找回密码
 立即注册
查看: 143|回复: 0

Unity 面试基础知识汇总 (Lua 方向)

[复制链接]
发表于 2023-1-10 17:47 | 显示全部楼层 |阅读模式
一 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("调用__call:"..arg)
        return arg
end
}
setmetatable(mytable, mymetatable)
mytable(5)                                                        --调用__call:5

原文链接:Lua元表_ailinlin1997的博客-CSDN博客_lua 元表
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-24 17:31 , Processed in 0.128783 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表