前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是lua常问问题总结,有帮助的可以收藏。
1.pairs与ipairs区别
pairs会遍历所有key,对于key的类型没有要求,遇到nil时可以跳过,不会影响后面的遍历,既可以遍历数组部分,又能遍历哈希部分。
ipairs只会从1开始,步进1,只能遍历数组部分, 中间不是数字的key忽略, 到第一个不连续的数字为止(不含),遍历时只能取key为整数值,遇到nil时终止遍历。
2.点和冒号区别
点 :无法传递自身,需要显示传递
冒号 :隐式传递自身
3.Lua如何实现面向对象
Lua面向对象编程是基于元表metatable,元方法__index来实现的。
通过元表的__index元方法,将一个table的__index元方法设置为另一个table,那么后者的方法就被前者继承。
如果访问了lua表中不存在的元素时,就会触发lua的一套查找机制,也是凭借这个机制,才能够实现面向对象的。
总结元表的查找步骤:
在表中查找该元素,如果找到,返回该元素,找不到则判断该表是否有元表(setmetatable(A,B) -- 把 B设为 A的元表 ),如果没有元表,返回nil,有元表则判断元表有没有__index方法(不是直接看元表有该元素!),如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复上述步骤;如果__index方法是一个函数,则调用该函数,并返回该函数的返回值。
4._index 与 _newindex的区别
注意:__index 和__newindex归为元表方法。
__index:当子脚本不存在被访问的元素的时候,lua就会调用__index,去父脚本里面查找该元素。
__newindex:当赋值时,如果赋值一个不存在的索引。
如果__newindex是一个表,那么会把这个值赋值到newindex所指的表中(有这个索引就修改,没有就创建),不会修改自己;
如果__newindex是一个函数,那么会调用这个函数。
代码:
1.__newindex指定一个函数
2.__newindex指向一个空表
3.__newindex指定一个已经定义的表。
只能在元表中指定
5._rawset与_rawget的区别
_rawget:访问时,不想从__index 对应的元方法中查询值
_rawset:更新时,不想执行__newindex 对应的元方法
6.如何实现一个只读表
利用__index 与 __newindex来实现。
详细请看:Lua中rawset 函数使用_zhaixh_89的博客-CSDN博客_lua rawset
7.Table
为了提高table的插入查找效率,在table的设计上,采用了 array数组 和 hashtable(哈希表) 两种数据的结合。
数组部分:从 1 开始做整数数字索引,这可以提供紧凑且高效的随机访问;数组部分存储在 TValue *array 中,其长度信息存储在 int sizearray 中。
哈希表:存储在 Node *node,哈希表的大小用 lu_byte lsizenode 表示,lsizenode表示的是2的几次幂,而不是实际大小,因为哈希表的大小一定是 2 的整数次幂。哈希冲突后,采取开放定址法,应对 hash 碰撞。
每个 Table 结构,最多会由三块连续内存构成:
(1) 一个 table 结构
(2) 一块存放了连续整数索引的数组
(3) 一块大小为 2 的整数次幂的哈希表
所以table会将部分整形key作为下标放在数组中, 其余的整形key和其他类型的key都放在hash表中。
table中的hash表的实现结合了以上两种方法的一些特性:
查找和插入等同链地址法(拉链法)复杂度。
内存开销近似等同于开放定址法。
详细请看:
【Lua 5.3源码】table实现分析_YzlCoder的博客-CSDN博客
8.string 类型
string:底层实现对字符串做了分类处理。
只要大于 40 字节的字符串,即使内容一样,也会在堆内存中,重新生成一个全新的字符串数据拷贝;
当小于 40 字节时,使用全局 stringtable 进行管理,相同字符串只会有一份数据拷贝,每份相同的对象只是存在一个 hash 值,用来索引 stringtable。
9.lua的GC算法
LuaGC 采用三色增量标记清除算法
详细请看 Lua设计与实现--GC篇 - 知乎
10.C# 与 xlua交互
10.1 交互原理
1、在C#中将需要热更的类标记(标签,静态列表,动态列表)。
2、生成函数连接器来连接lua脚本和c#函数。
3、对编译生成的dll进行修改。
4、把代码的执行路径修改到lua脚本中(如果lua中执行了对应的热修复函数,则把il中对应的函数替换为对应的lua函数)。
10.2 在Lua中,已知GameObject obj。那如下代码在Lua侧调用,如下代码会发生什么底层交互obj.transform.setactive( false )
Lua本身提供了C_API,让我们Push一个值到Lua虚拟栈上,Lua可以通过访问Lua虚拟栈来访问这个对象,Lua虚拟栈是Lua和其他语言交换数据的中介。
Xlua对这个接口进行了封装,并提供了一系列push方法,让我们可以把一个C#对象push到lua的虚拟栈上。
10.3 xLua是如何记录C#或者Unity引擎对象的
引用存储
10.4 如何在Lua里,避免上述的这些问题
注意对象缓存
10.5 C#和xlua如何互相交互?
1. xlua/tolua都是借助的lua虚拟机完成的跨语言调用的。(看虚拟机怎么完成跨语言调用的)
2. xlua/tolua对虚拟机做了上层封装,可以便捷访问Lua侧table或函数。(看下虚拟机上层接口封装这块的代码)
3. 其次就是反射和warp了。Lua在调用C#的时候,实际上传递过来的是字符串,为了避免纯反射,其实xlua/tolua封装了一个keyvalue的字典,这个key是个字符串,就是引用类型的字符串(Lua传递过来的),通过这个字符串就能拿到这个typeofvalue,通过这个方式,避免了反射。
4. 综述:
Lua虚拟机,实现了跨语言交互。
xLua或者toLua正是因为用了这个Lua虚拟机,才具备了跨语言调用的能力;其次,xLua或者tolua对Lua虚拟机进行了一定程度的封装,这样你在xLua或者toLua中,才能便的使用一些接口,比如获取Lua的变量或者table或者函数之类的功能。从这一点上来说,xLua或者toLua只是个上层应用,而Lua虚拟机是底层实现。
xLua或者tolua不仅封装应用了Lua虚拟机,还额外优化了性能,体现在刚才聊到的容器,存储keyvalue映射,这样就不用反射了。
11.Lua 如何与 C# 交互
C#与Lua进行交互主要通过虚拟栈实现,栈的索引分为正数与负数,若索引为正数,则1表示栈底,若果索引为负数,则-1表示栈顶。
C#调用 Lua 实际上是:由 C# 先把数据放入栈中,由 Lua 去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回 C#。
Lua 调 C# 也一样:先编写自己的 C模块,然后注册函数到 Lua 解释器中,然后由 Lua 去调用这个模块的函数。
详细请看:C#与Lua交互过程及原理_钢与铁的博客-CSDN博客_lua 调用c#原理
12.Lua中的类型
1.nil 2. number 整数 3.table 表 4.string 字符
5.userdata 自定义类 6.function 函数 7.bool 布尔 8.therad 线程
13.Lua中的循环
循环类型 |
描述 |
在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。 |
|
重复执行指定语句,重复次数可在 for 语句中控制。 |
|
重复执行循环,直到 指定的条件为真时为止 |
|
可以在循环内嵌套一个或多个循环语句(while do ... end;for ... do ... end;repeat ... until;) |
|
泛型循环 |
pairs会遍历全部的值ipairs会从1开始遍历遍历到非连续位置停止,上面代码改成ipairs最后的999将不会被打印 |
14.闭包
14.1 闭包函数
声明在一个函数中的函数,叫做闭包函数。
14.2 闭包概念
内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
14.3 闭包构成的条件
在函数嵌套(函数里面定义另一个函数)的前提下;
内部函数使用了外部函数的变量(也包括外部函数的参数);
外部函数返回了内部函数。
14.4 闭包的作用
闭包可以保存外部函数内的变量,可以根据外部函数内的变量来得到不同的结果,不会随着外部函数调用完而销毁,当闭包执行完成后,仍可以保持当前的运行环境,执行结果依赖于该函数上一次的运行结果。
但是由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,这样会比较消耗内存。
14.5 闭包实例
function add()
local x = 0
return function ()
x = x + 1
return x
end
end
add1 = add()
print(add1()) --1
print(add1()) --2
add2 = add()
print(add2()) --1
当函数add1执行时,函数add 已经返回,add1的局部变量x已经在栈中退出,但是add1却能访问x。这是因为x是函数add的upvalue。
而add2函数执行的结果表明add1和add2并没有共享upvalue,而是单独有一份自己的upvalue。
详细请看:
15. LoadFile、loadstring、dofile、require
Loadfile
只编译,不运行,它只会加载文件,编译代码,不会运行文件里的代码。
dofile(filename)
用于加载一个指定路径filename的代码块,并运行。
require(modname)
对于同一个代码块只会加载一次(加载过会保存起来),和dofile类似,加载编译后也会自动运行。
require和dofile有点像,不过又很不一样,require在第一次加载文件的时候,会执行里面的代码。但是,第二次之后,再次加载文件,则不会重复执行了。换句话说,它会保存已经加载过的文件,不会重复加载。require的返回值会被存储cache起来,所以一个文件最多只会执行一次,即使被require很多次。
Load
用于加载字符串代码块
详细请看:
16. C# 内存管理与 lua的内存管理有什么区别?C++呢?
两个都有垃圾回收机制,不过c#有clr提供更多的支持,c++没有垃圾回收,要自己调用析构函数,手动释放。
17.lua怎么实现C#委托的功能
利用元表的__index方法,多个元表层级嵌套,利用多个__index实现。
利用lua实现 观察者模式
详细请看:观察者模式--lua实现_ljf551的博客-CSDN博客
希望此篇文章可以帮助到更多的同学