Lua面试学习

1.Table表
基本原理:
在lua中不存在字典,列表等数据类型,同时也不存在class与struct等数据类型,所有的这些类型都是基于Table表实现的。
lua是关联性数据结构,可以使用任意类型的值来表示索引,但不可以是nil
lua是不固定大小的数据结构,可以自动进行扩容
如果在table中允许nil存在,他是会占一个索引位
{}相对于匿名table,如果判断一个table是否为nil,不能直接进行与{}比较,因为相对于2个table,而非空。
if next(data) == nil then end进行判断
1 | --数组(如果使用Table表来实现数组,默认索引是从1开始的,但是可以指定元素索引。并且lua中的数组中元素类型可以不相同) |
对于 {v1, v2, v3} 这种隐式下标写法,Lua 会自动给元素分配从 1 开始的连续整数下标(即 [1]=v1, [2]=v2, [3]=v3),当表中同时出现隐式下标和显式下标时,Lua 会按代码书写顺序依次赋值,后赋值的会覆盖先赋值的
1 | -- 定义表,显示赋值的会进行分配,如果下标重合,会被覆盖 |
此外,对于lua中不存在结构体以及类等结构,我们可以使用table来模拟,因此我们可以在table中创建函数
1 | -- 定义一个表,包含多种类型的元素 |
table模拟类结构
1 | -- 定义一个"学生对象"(包含属性 + 方法) |
遍历:
在lua中对Table进行遍历的时候,一般是使用pairs,ipairs进行。两者都是迭代器。因为lua的表是无序的,使用下标不是固定的,此时遍历都会产生区别。
- 对于pairs会遍历所有key,对于key的类型没有要求,遇到nil时可以跳过,不会影响后面的遍历,既可以遍历数组部分,又能遍历哈希部分,但是不保证遍历顺序,因为Lua的表是无序的。
- pairs 是一个更通用的迭代器生成器,用于遍历表中的所有键值对。它利用了Lua的内置函数
next,该函数会返回表中下一个键值对。 pairs返回三个值:next函数、表t和初始键nil。next函数会从表中返回下一个键值对,直到没有更多的键值对
1 | function pairs(t) |
- 对于ipairs是从索引1开始遍历,步长为1,只能遍历数组部分, 中间不是数字的key忽略, 到第一个不连续的数字为止(不含),遍历时只能取key为整数值,遇到nil时终止遍历。
- 这个实现中,ipairs 返回一个迭代器函数、表
t和初始索引0。迭代器函数每次调用时,会将索引i加1,并检查表中该索引的值是否为nil。如果值为nil,则停止迭代。
1 | function ipairs(t) |
在Lua中,表是基于哈希表的,这意味着它们可以存储任何类型的键,而不仅仅是整数。当你使用非连续的索引(如[3] = 10)时,Lua会将这些索引转换为哈希键,以便在表中存储和检索对应的值。
点和冒号的区别
| 操作符 | 用途 | 隐式参数 | 调用示例 |
|---|---|---|---|
. |
普通成员访问 | 无 | obj.method(obj, arg) |
: |
方法调用语法糖 | 自动传递 self |
obj:method(arg) |
点号:
1 | -- 定义一个表 |
如果函数需要传递本身的话,则在调用的时候需要将本题显示传递;
冒号:
1 | local obj = { |
2.元表
基本概念:
- 每个 table 都可以有一个元表。元表用于“拦截”或“重载”某些操作。元表本身也是一个 table
- 通过
setmetatable()进行设置,通过getmetatable()获取
1 | --设置元表 |
元方法:当对 table 做某些“特殊操作”时,Lua 会去元表中查找对应的字段(称为元方法)。
- __index:当访问 table 中 不存在的字段 时触发。如果在本表中查找不到字段,则取元表中进行寻找。
如果访问一个表中不存在的字段时,如果这个表的元表设置了 __index 且 __index 是函数,Lua 就会自动调用这个函数,并且会把被访问的原表和不存在的那个键作为两个参数传给这个函数,函数的返回值会作为这次 “字段访问” 的结果。
如果__index是一个表,Lua会在这个表里面查找key对应的值。
1 | local proto = { name = "默认" } |
- __newindex:当你给表的一个缺少的索引赋值,解释器就会查找 _ _newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
当赋值时,如果赋值一个不存在的索引。
如果__newindex是一个表,那么会把这个值赋值到newindex所指的表中(有这个索引就修改,没有就创建),不会修改自己;
如果__newindex是一个函数,那么会调用这个函数。
参数:
table:被赋值的表。
key:被赋值的键。
value:被赋值的值。

1 | -- 定义空表,用于接收__newindex重定向的赋值 |
- __add:table 支持运算符,让表可以进行相加操作
1 | -- 1. 定义第一个表(包含数值属性num) |
- __call:
__call是用来让表可以像函数一样被调用的元方法,核心作用是自定义表被当作函数执行时的逻辑
1 | -- 1. 定义一个普通表(包含基础属性) |
- __tostring
关于在lua中如何实现只读表
首先我们需要使用_ _index和 _ _newindex2个元方法。在下列的代码,首先先给obj进行赋值,此时t是一个table,它的值是{“1”,”2”,”#”},然后开始执行onlyread函数,创建一个newT表,创建一个mt表,此时__index函数是一个表, _ _newindex是一个函数,如果
1 | function onlyread(t) |
3.闭包
闭包是一个函数 加上 该函数能访问的所有 非局部变量(upvalue)。
闭包就是 “有记忆的函数”。一个普通的函数用完就忘,但闭包能记住它诞生时接触到的外部变量,并在每次被调用时,基于这个记忆更新状态。就像你办了一张独
立的会员卡,每次消费都会扣除卡内余额——这个余额对外界是隐藏的,只有闭包自己记得。
1 | function 开一张会员卡() |
实际例子
1 | function 计数器工厂() |
4.lua的数据类型
在lua中,实际存在的数据类型包括number,bool,nil,table,thread,string,function,userdata。
nil:代表“无”或“无效值”。
- 全局变量在赋值前的默认值是
nil。 - 将变量赋值为
nil相当于删除它。 - 在布尔上下文中视为
false。
number:表示所有数值,包含整数和浮点数。
- 无需声明类型,运算时自动进行整数/浮点数转换。
- Lua 5.3 开始,
number分为两个子类型:integer(64位有符号整数)和float(双精度浮点数)。 - 但
type()函数对两者都返回"number"。 - 可通过
math.type(42)进一步区分(返回"integer")。
boolean:只有两个——true和 false。
- 在 Lua 中,只有
false和nil被视为假,其他所有值(包括 0、空字符串、空表)在条件判断中都视为真。 if 0 then print("true") end --> 输出"true"
string
- 不可变(immutable),修改会创建新字符串。
- 支持单引号
'...'、双引号"..."和长括号[[...]]。使用..进行连接。 - 字符串驻留:相同字符串在内存中只存一份。
- 数字与字符串自动转换:
print("10" + 1) --> 11 - 显式转换:
tostring(123),tonumber("123")
function:Lua 中的“一等公民”,可赋值、传递、作为返回值。
- 闭包:函数可捕获并访问外层局部变量(upvalue)。
- 支持函数式编程:高阶函数、匿名函数、柯里化等。
table(表):Lua 唯一的数据结构,是所有复杂数据的基础。
关联数组:键可为除
nil外的任何类型。数组部分:以连续整数为键的序列(从 1 开始)。
字典部分:存储其他键值对。
表引用传递:赋值、传参都是传递引用。
元表:可通过元表实现面向对象、操作符重载。
构造灵活:
{1, 2, 3}(数组)、{x=1, y=2}(字典)。
userdata:用于 Lua 与 C 语言扩展交互。
- 表示一块由 C 语言管理的内存。Lua 代码只能使用,不能直接创建或修改其内部结构。
- 通常用于表示文件指针、窗口句柄、C 结构体等。
thread:不是操作系统线程,而是 Lua 协程。
协作式多任务:需主动让出执行权。
轻量级:创建开销小。
通过
coroutine库操作。local co = coroutine.create(function() print("hi") end)
5.C#和xlua的互相调用
C#与xlua的调用原理:
- 在C#中将需要热更的类标记(标签,静态列表,动态列表)。
- 生成函数连接器来连接lua脚本和c#函数。
- 对编译生成的dll进行修改。
- 把代码的执行路径修改到lua脚本中(如果lua中执行了对应的热修复函数,则把il中对应的函数替换为对应的lua函数)。
6.lua与Unity的相互调用
7.lua与Unity的GC
UnityGC
Unity的是GC非分代非压缩的标记清除算法,它会在需要进行GC时占用主线程,进行遍历-标记-垃圾回收的过程,然后在归还主线程控制权。这会导致帧数的突然
下降,产生卡顿。所以我们需要慎重地处理对象的创建(内存请求),还有释放(使用GC管理内存是没有主动释放内存的接口的,但是我们可以通过消除对某个
对象的引用来做到这一点)。此外,Unity的代码分为两部分:托管与非托管,GC影响的只有托管部分的代码使用的堆内存。而且这个托管堆占用的地址空间不会
返还给操作系统。标记清楚算法可以解决两个变量相互引用,产生标记的现象。
LuaGC
Lua 使用增量标记-清除垃圾回收机制,采用三色标记算法(白-灰-黑)。对象被创建时标记为白色;GC 运行时,从根集合出发,将可达对象标记为灰色并放入待
处理列表;递归扫描灰色对象引用的白色对象,将其变为灰色;扫描完毕后,存活对象变为黑色,剩余的白色对象被清除。GC 过程分步执行,避免长时间停顿,
可自动或手动触发,支持调节 GC 速度和内存阈值。
C#GC
C# 使用分代标记-压缩垃圾回收机制。内存分为三代(0、1、2代),新对象分配在第0代。GC触发时,从根引用出发标记存活对象,然后压缩内存消除碎片,存
活对象晋升到下一代。第0代回收最频繁,第2代回收代价最高。托管堆分为小对象段(<85KB)和大对象段(直接进入第2代)。GC有工作站(低延迟)和服务器
(高吞吐)两种模式,支持后台并发回收以减少暂停时间。
Lua减少GC策略
1.减少临时对象的创建:避免在lua脚本里频繁创建和销毁临时对象,可以通过重用变量来减少内存的分配。
2.优化C#和Lua的交互:在使用XLua时,可以通过[GCOptimize]标记来优化C#和Lua之间的传递值类型(如结构体、枚举等)时的GC分配。
3.使用对象池:Lua可以调用C#测的对象池来管理C#对象的引用,避免频繁创建和销毁对象。
4.合理管理引用:Lua对C#对象的引用需要合理管理,保证不再使用的对象可以正确被回收。
5.手动发起Lua GC。
