【Unity面试】 Lua语言基础核心 | 面试真题 | 全面总结 | 建议收藏

你知道的越多,你不知道的越多 🇨🇳🇨🇳🇨🇳
点赞再看,养成习惯,别忘了一键三连哦 👍👍👍
文章持续更新中 📝📝📝


🎬📢📢📣🔔🍭🍭🍭🏆🏆🏆🥇🥈🥉🎖🏅☎️☎️⏰⏰❤️💔💕💗💖💘⚠️⚠️⚠️➡️➡️➡️⬅️⬅️⬅️⬆️⬆️⬆️⬇️⬇️⬇️🔑🔑🔑
🤡🤖👀🎃🌻🌼🌸⭐️⭐️🌟💫✨🌙☀️💥🔥🔥⚡️
1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 0️⃣
🇨🇳🇨🇳🇨🇳❤️❤️❤️🈲🈲🈲㊙️🈚️🈶⭕️⭕️🚫🚫⛔️⛔️❌❌‼️❓❗️⁉️💯🅰️🅱️🔻🔻🔺🔺🚩🚩🚩🚩🎁🎁🎁👍👍👍📝📝📝


1️⃣ lua中ipairs和pairs的区别?🔥🔥🔥

Table的组成:哈希表(键值对,链表解决冲突),数组(数字,表,)

  1. 根据元素列表可以分为:哈希表{[1]=1,[3]=3,[5]=5.[6]=6} , 数组{2,4}
  2. 将数组中的元素放入哈希表,将会发生变化 [1]=2,[2]=4,[1]的键值对发生冲突,重新匹配,得到新的哈希表元素{[1]=2,[2]=4,[3]=3,[5]=5,[6]=6}``
local t = {[1]=1,2,[3]=3,4,[5]=5,[6]=6}
print('ipairs')
for index, value in ipairs(t) do
    print(value)
end
print('pairs')
for key, value in pairs(t) do
    print(value)
end
--答案是ipairs [2 4 3] , pairs [2 4 3 6 5] 无序 

local testTab ={1,2,3,4,5};-- '纯表'
local testTab1 = {a = 1, b = 2, c =3};
local testTab2 = {"zi",a = 5,b = 10, c = 15,"miao","chumo"};-- '杂表1'
local testTab3 = {"zi",a = 5,b = 10, c = 15,"miao",nil,"chumo"};-- '杂表2'

--testTab3答案是ipairs [zi,miao] , pairs [zi,miao,chumo,5,10,15] 

总结:

先按索引值顺序输出数组元素,再输出哈希表键值对

所以当ipairs遍历table时,从键值对索引值[1]开始连续递增,当键值对索引值[ ]断开或遇到nil时退出,所以上面的例子中ipairs遍历出的结果是2,4,3。

而pairs遍历时,会遍历表中的所有键值对,先按照索引值输出所有数组后,在输出其它键值对,且元素是根据哈希算法来排序的,得到的不一定是连续的,所以pairs遍历出的结果是2,4,3,6,5。

如何解决既要顺序遍历,也要遇到nil也能遍历完全???
1.现获取table的长度,包含其中的nil,maxSize

for idx=1, maxSize do
     if pTable[idx] ~= nil then --不等于
          -- 做相应的处理...
     end
end

2️⃣解释下lua中的元表和元方法??🔥🔥🔥

🎬元表metetable
允许该表table行为,行为关联元方法,类似一种“操作指南”,包含各种操作行为的解决方案!!!
🎬元方法
当表执行某些操作失败的时候,操作指南里的元方法指导你的行为~~

A = {}
B = {}
setmetatable(
    A,
    {
        __index = B, --索引查询
        __newindex = B, --索引更新
        __add = B, --加法
        __sub = B, --减
        __mul = B, --乘
        __div = B, --除
        __mod = B, --取模
        __pow = B, --乘幂
        __unm = B, --取反
        __concat = B, --连接
        __len = B, --长度
        __eq = B, --相等
        __lt = B, --小于
        __le = B, --小于等于
        __call = B, --执行方法调用
        __tostring = B, --字符串输出
        __metatable = B	 --保护元表
    }
)

Table是非常重要的数据结构,由数组和哈希表两种内部实现,既可以当做数组也可以当做字典。
🔑当Table当做为C#的Dictionary时,很多元素都是由Key-value组成。
🔑如果尝试table表中不存在的元素时,就会触发lua的查找机制,这个机制模拟了类似“类”的行为~!!!
🔑只设置元表是不管用,需要同时设置元表和对应的元方法的代码

father = {  
    prop1=2  
}  
father.__index = father -- 把father的__index方法指向它本身
son = {  
    prop2=1  
}  
setmetatable(son, father) --把son的metatable设置为father  
print (son.prop1)
--输出结果为2

Lua并不是直接在fahter中找到名为prop1的成员,而是先调用father的__index方法),如果__index方法为nil,则直接返回nil。如果__index指向了一张表(上面的例子中father的__index指向了自己本身),那么就会到__index方法所指向的这个表中去查找名为prop1的成员。


3️⃣说说lua中如何实现面向对象?🔥🔥🔥🔥🔥

面向对象特性:封装、继承、多态
Lua中table是非常强大的数据结构,利用table再结合元表metatable,可以方便的在Lua中模拟类,并实现面向对象编程中具有的特性:封装、继承、多态
1. 📢利用Lua实现类

--类:属性,构造函数,成员,属性等等
--类的声明,属性,初始值
class = {x = 0, y = 0}
--设置元表的索引,要模拟类,这一步最重要
class.__index = class --表的元表索引是自己
--构造方法,习惯性命名new()
function class:new(x, y)
    local t = {} --初始化t,如果没有这一句,会导致类所建立的对象一旦发生改变,其它对象都会改变
    setmetatable(t, class) --设置t的元表为class , 把class设为t的原型
    t.x = x --属性值初始化
    t.y = y
    return t --返回自身
end
--这里定义类的其它方法,self标识是非常重要的核心点,冒号:隐藏了self参数
function class:test()
    print(self.x, self.y)
end
function class:plus()
    self.x = self.x + 1
    self.y = self.y + 1
end

2. 📢利用lua实现继承
将元表设置为父类,在实现lua索引查找机制,要注意构造函数新表和设定元表

require("Class")
--类的继承
SubClass = {z = 0}
 --声明新的属性Z
--两步完成类Class的继承
setmetatable(SubClass, Class)
 --设置类型是class
SubClass.__index = SubClass --设置表的索引为自身
--构造方法,习惯性命名new()
function SubClass:new(x, y, z)
    local t = {} --初始化对象自身
    t = Class:new(x, y) --将对象自身设定为父类,相当于C#里的base
    setmetatable(t, SubClass) --将对象自身元表设定为SubClass
    t.z = z --新属性赋值
    return t
end
--定义一个新的方法
function SubClass:go()
    self.x = self.x + 10
end

--重定义父类的方法,相当于override
function SubClass:test()
    print(self.x, self.y, self.z)
end

3.📢利用lua实现多态(重写)

require("Class")
require("SubClass")
local a = Class:new() -- 首先实例化父类,并调用父类方法
a:plus()
a:test()

a=SubClass:new() -- 然后实例化子类对象
a:plus() --重写
a:go()   --新增
a:test()  --重写

4️⃣lua深拷贝和浅拷贝的区别?如何实现深拷贝??🔥🔥

1、lua深拷贝和浅拷贝的区别?如何实现深拷贝?
1.📢如何实现浅拷贝
使用 = 运算符进行浅拷贝

  1. 拷贝对象是string、number、bool基本类型。拷贝的过程就是复制黏贴!修改新拷贝出来的对象,不会影响原先对象的值,两者互不干涉
  2. 拷贝对象是table表,拷贝出来的对象和原先对象时同一个对象,占用同一个对象,只是一个人两个名字,类似C#引用地址,指向同一个堆里的数据~,两者任意改变都会影响对方.

2.📢如何实现深拷贝
复制对象的基本类型,也复制源对象中的对象
常常需用对Table表进行深拷贝,赋值一个全新的一模一样的对象,但不是同一个表

Lua没有实现,封装一个函数,递归拷贝table中所有元素,以及设置metetable元表

如果key和value都不包含table属性,那么每次在泛型for内调用的Func就直接由if判断返回具体的key和value
如果有包含多重table属性,那么这段if判断就是用来解开下一层table的,最后层层递归返回。
核心逻辑:使用递归遍历表中的所有元素。

  1. 先看copy方法中的代码,如果这个类型不是表的话,就没有遍历的必要,可以直接作为返回值赋值;
  2. 当前传入的变量是表,就新建一个表来存储老表中的数据,下面就是遍历老表,并分别将k,v赋值给新建的这个表,完成赋值后,将老表的元表赋值给新表。
  3. 在对k,v进行赋值时,同样要调用copy方法来判断一下是不是表,如果是表就要创建一个新表来接收表中的数据,以此类推并接近无限递归。
local numTest1=5
local numTest2=numTest1 --使用 == 进行浅拷贝
local numTest2=10 --修改numTest2,不会改变numTest1
print(numTest1) 
--答案 5
print(numTest2) 
--答案 10

local tab={}
tab["好好学习"]="游戏开发"
tab["热更"]="Xlua"
for key, value in pairs(tab) do
    print(key.."对应"..value)
end

local temp = tab
tab["好好学习"]="热更"
tab["热更"]="好好学习"
for key, value in pairs(tab) do
    print(key.."对应"..value)
end
--输出答案,tab和temp都发生了改变
--热更对应Xlua
--好好学习对应游戏开发

t={name="asd",hp=100,table1={table={na="aaaaaaaa"}}};

--实现深拷贝的函数
function copy_Table(obj)

		function copy(obj)
			if type(obj)  ~= "table" then						--对应代码梳理“1”  (代码梳理在下面)
				return obj;
			end
			local newTable={};									--对应代码梳理“2”

			for k,v in pairs(obj) do
				newTable[copy(k)]=copy(v);						--对应代码梳理“3”
			end
			return setmetatable(newTable,getmetatable(obj));
		end
		
	return copy(obj)
end

a=copy_Table(t);

for k,v in pairs(a) do
	print(k,v);
end

--1.先看copy方法中的代码,如果这个类型不是表的话,就没有遍历的必要,可以直接作为返回值赋值;
--2.当前传入的变量是表,就新建一个表来存储老表中的数据,下面就是遍历老表,并分别将k,v赋值给新建的这个表,完成赋值后,将老表的元表赋值给新表。
--3.在对k,v进行赋值时,同样要调用copy方法来判断一下是不是表,如果是表就要创建一个新表来接收表中的数据,以此类推并接近无限递归。

5️⃣说说Lua中的闭包?🔥🔥🔥🔥

🎬闭包=函数+引用环境
子函数可以使用父函数中的局部变量,这种行为可以理解为闭包!

1、📢闭包的数据隔离
不同实例上的两个不同闭包,闭包中的upvalue变量各自独立,从而实现数据隔离

2、📢闭包的数据共享
两个闭包共享一份变量upvalue,引用的是更外部函数的局部变量(即Upvlaue),变量是同一个,引用也指向同一个地方,从而实现对共享数据进行访问和修改。

3、📢利用闭包实现简单的迭代器
迭代器只是一个生成器,他自己本身不带循环。我们还需要在循环里面去调用它才行。
1)while…do循环,每次调用迭代器都会产生一个新的闭包,闭包内部包括了upvalue(t,i,n),闭包根据上一次的记录,返回下一个元素,实现迭代
2)for…in循环,只会产生一个闭包函数,后面每一次迭代都是使用该闭包函数。内部保存迭代函数、状态常量、控制变量。


6️⃣__index和__newindex元方法的区别??🔥🔥🔥

🎬__newindex用于表的更新,__index用于表的查询
如果访问不存在的数据,由__index提供最终结果
如果对不存在的数据赋值,由__newindex对数据进行赋值
🔑__index元方法可以是一个函数,Lua语言就会以【表】和【不存在键】为参数调用该函数
🔑__index元方法也可以是一个表,Lua语言就访问这个元表
🔑对表中不存在的值进行赋值的时候,解释器会查找__newindex
🔑__newindex元方法如果是一个表,Lua语言就对这个元表的字段进行赋值

Lua实现面向对象:继承
元表带有元方法__index,并且可以使用__index来实现单继承

prototype={x=0,y=0,width=100,height=200} --元表
local mt = {} --空表
function new(o)
    setmetatable(o,mt) --把mt设为元表
    return o
end
mt.__index=function(_,key) --元表元方法赋值一个函数,不存在键和表
    return prototype[key]
end

w=new{x=10,y=20}
print(w.width)--100

mt.__index=prototype --元表

7️⃣table的一些重要知识点有哪些?🔥

(1)、table 是 Lua 的一种数据结构,用于帮助我们创建不同的数据类型,如:数组、字典等;
(2)、table 是一个关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil,所有索引值都需要用 “[“和”]” 括起来;如果是字符串,还可以去掉引号和中括号; 即如果没有[]括起,则认为是字符串索引,Lua table 是不固定大小的,你可以根据自己需要进行扩容;
(3)、table 的默认初始索引一般以 1 开始,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编;
(4)、table 的变量只是一个地址引用,对 table 的操作不会产生数据影响;
(5)、table 不会固定长度大小,有新数据插入时长度会自动增长;
(6)、table 里保存数据可以是任何类型,包括function和table;
(7)、table所有元素之间,总是用逗号 “,” 隔开;


8️⃣简单描述一下Lua的GC原理??🔥🔥🔥

1、Lua的GC垃圾回收机制算法
Lua的GC使用了标记清除算法Mark and Sweep

标记:每一次执行GC前,从根节点开始遍历每一个相关节点,进行标记
清除:标记完成后,遍历对象链表,然后对需要执行清除标记的对象,进行清除

使用三色法:白,灰,黑,作为对象的三种状态
新白:可以回收的对象;新创建的对象,初始状态是新白,但不会被清除
旧白:可以回收的对象;lua只会清除旧白,GC后,会更新新白
灰色:等待回收的对象:该对象已被GC访问过,但该对象引用的其它对象还未标记
黑色:不可回收的对象

简单流程
1.根对象开始标记,将白色对象重置为灰色对象,加入灰色链表
2.如果灰色链表不为空,取出一个对象,重置为黑色,并遍历相关引用的对象,重置为黑色
3.如果灰色链表为空,清除一次灰色链表
4.根据不同类型对象分布回收,类型的存储表
5.判断是否遍历到链表尾
6.判断对象是否为白色
7.将对象重置为白色
8.释放资源

总结

Lua通过借助grey链表,依次利用reallymarkobject对对象进行了颜色的标记,之后通过遍历alloc链表,依次利用sweeplist清除需要回收的对象。

2、Lua中的GC优化,防止内存泄露

  1. Xlua可以打标签[GCOptimize] ,C#复杂类型struct类型是引用传递到Lua,对满足条件的struct,在Lua一侧做到无GC

条件:struct允许嵌套其它struct,但它以及它嵌套的struct只能包含这几种基本类型:byte、sbyte、short、ushort、int、uint、long、ulong、float、double;例如UnityEngine定义的大多数值类型:Vector系列,Quaternion,Color。。。均满足条件,或者用户自定义的一些struct

  1. 游戏逻辑层:实体管理器如果是本身持有了实体,那么就不应该有create/remove接口。所有实体资源,主要是目前的玩家逻辑数据, 必须直接绑定在角色上,确保角色的销毁会引发实体资源的销毁;全局资源性的数据,可以考虑放在weak table中
  2. 可以对lua中的模块进行分代, 不同的数据使用不同的保存,封装和清除策略,保证在最大效率的情况下准确的完成垃圾收集。
  3. Collectgarbage方法就是开放给Lua开发人员的API,用于监听Lua的内存使用情况(collectgarbage(“count”)),或者需要定期调用collectgarbage(“collect”),又或者collectgarbage(“step”))进行显式回收。

1.如果系统性能还能够承受的话,建议不要直接引用对象,可以多做 一层间接层。2.lua里面的弱引用是非常有用的。3.比较大的物理内存是必要的, 这可以为大家查证问题争取足够多的时间:) 4.可以把查找泄漏的部分写入到关机 逻辑里面,每次关机的时候自动查找泄漏,然后出具报告。Lua的内存监测和回收


9️⃣C#与Lua交互原理??🔥🔥

Wrap文件:每一个Wrap文件都是对一个C#类的包装。

C# Call Lua交互过程
C#文件先调用Lua的解析器底层的dll库(C语言编写),再由DLL文件执行相应的Lua文件

Lua Call C# 交互过程
1.Wrap方式:首先生成C#源文件对应的Wrap文件,Lua文件会调用生成的Wrap文件,再由Wrap文件去调用C#文件。
2.反射方式:当索引系统API、DLL库或者第三方库,如果无法将代码具体实现进行代码生成,可通过反射来获取,执行效率较低。

C#与Lua交互原理:虚拟栈!!
交互通过虚拟栈实现,栈的索引分为正数和负数,如果索引是正数,则1表示栈底,如果索引是负数,则-1表示在栈顶

C# Call Lua交互原理
C#先将数据放入栈中,然后Lua去栈中获取数据,然后返回数据对应的值到栈顶,再由栈顶返回至C#

Lua Call C#交互原理
C#源文件生成Wrap文件、或C#源文件生成C模块,将Wrap文件和C模块注册到Lua的解析器中,最后再由Lua去调用这个模块的函数~

从代码文件方面解释:
lua调用C#过程:
lua->wrap->C#
先生成Wrap文件(中间文件/适配文件),wrap文件把字段方法,注册到lua虚拟机中(解释器luajit),然后lua通过wrap就可以调C#了、或者在config文件中添加相应类型也可以

C#调用Lua过程:
C#生成Bridge文件,Bridge调dll文件(dll是用C写的库),先调用lua中dll文件,由dll文件执行lua代码
C#->Bridge->dll->Lua 或 C#->dll->Lua

从内存方面解释:说白了就是对栈进行操作
C# Call Lua:C#把请求或数据放在栈顶,然后lua从栈顶取出该数据,在lua中做出相应处理(查询,改变),然后把处理结果放回栈顶,最后C#再从栈顶取出lua处理完的数据,完成交互。


🔟lua是如何实现热更新的?🔥🔥🔥

Lua的模块加载机制
热更的核心就是替换Package.loaded表中的模块

🔑导出函数require(mode_name)
🔑查询全局缓存表package.loaded
🔑通过package.searchers查找加载器

package.loaded
存储已经被加载的模块:当require一个mode_name模块得到的结果不为假时,require返回这个存储的值。require从package.loader中获得的值仅仅是对那张表(模块)的引用,改变这个值并不会改变require使用的表(模块)。

package.preload
保存一些特殊模块的加载器:这里面的值仅仅是对那张表(模块)的引用,改变这个值并不会改变require使用的表(模块)。

package.searchers
require查找加载器的表:这个表内的每一项都是一个查找器函数。当加载一个模块时,require按次序调用这些查找器,传入modname作为唯一参数。此方法会返回一个函数(模块的加载器)和一个传给这个加载器的参数。或返回一个描述为什么没有找到这个模块的字符串或者nil。

🎁🌻🌼🌸 粉丝福利来喽 🎁🌻🌼🌸

  1. 免费领取海量资源 🎁
    简历自测评分表、Unity职级技能表、面试题库、入行学习路径等
  2. 《Unity游戏开发五天集训营 》50个名额 🎁
    我给大家争取到了 50个《游戏开发五天集训营 》名额,原价198,前50个免费
    扫码加入,暗号小听歌
    即可参加ARPG狼人战斗系统、饥荒生存类游戏开发、回合制RPG口袋妖怪游戏等游戏开发训练营
  3. 额外抽奖机会🎁
    参加游戏训练营、还有机会获得大厂老师在线面试指导、或者有机会获得价值1998元的《Unity极速入门与实战》课程
🔻🔻🔻🔻 扫下方二维码,获取游戏开发福利,暗号小听歌 🔻🔻🔻🔻
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值