目录
3.请简述GC(垃圾回收)产生的原因,并至少说出避免GC发生的三种方式?
4.List和Dictionary的区别,还有了解过哪些数据结构,它们的特性是什么?
5.string、stringBuild、stringBuffer区别
7.string、int、float、interface、enum、Object、struct、UnityEngine.Color、UntiyEngine.Ray哪些是值类型哪些是引用类型
9.Array、ArrayList、List、Hashtable、Dictionary、Stack、Queue区别:
4.Collider和Trigger区别,碰撞的必要条件是什么?
2.以下代码结果是什么:local table = {1,2,[2] = 3}
5.元表中的__index、__newindex、__call、__tostring、rawset、rawget是什么
C#知识点:
1.多态是什么??
执行同一操作(方法),实现不同效果
2.值类型和引用类型区别
1.存储方式不同:
- 值类型存储在栈中,引用类型存储在堆中
- 值类型在栈中直接存储对应的数据。引用类型在栈中存储的是地址,在堆中存储的是对应的数据
- 值类型是自动释放,引用类型需要触发GC回收
2.执行速度不同:值类型执行速度快,引用类型执行速度慢
3.请简述GC(垃圾回收)产生的原因,并至少说出避免GC发生的三种方式?
堆中的容量达到一定界限,就会自动触发GC垃圾回收机制。
避免GC方式:
1.少用string,使用stringBuild
2.使用公共对象,少new
3.使用对象池
底层原理:三代标记清除压缩。在分配空间时导致空间碎片化、使用临时对象,用完后无引用时就会变成垃圾。
4.List和Dictionary的区别,还有了解过哪些数据结构,它们的特性是什么?
-----------------------------------底层原理面试官没问到时,可不回答-------------------------------------------
List是顺序表,相当于容器用于装载相同类型的数据。
List底层实现原理:底层通过数组实现,在添加数据时如果空间不够了,它会自动以1倍的方式扩容(比如目前只有2byte空间,需要的空间需要7空间,它会自动2*2*2=8)
List每次扩容时,都会创建一个新的空间,然后把原空间的数据放到新创建的空间;从而原空间就变成垃圾了,需要等待GC回收
Dictionary是散列表,以键值对的方式存储对应的数据
Dictionary底层实现原理:底层通过数组和链表的方式实现。
1.在申明空间长度时通过C#官方的质数数组进行判断(数组里面全是质数),如果需要的长度小于数组中的质数,长度就为数组中的质数。
2.通过两个数组,Buckets数组和Entries数组进行数据存储,Buckets数组初始化时会将此数组数据都置为-1,Entries数组通过Hash算法,存储对应的Dictionary的值
Queue:队列,先进先出。
Stack:栈,先进后出。
5.string、stringBuild、stringBuffer区别
string字符串内容被改变时,会创建一个新的空间,然后将当前字符串内容传给新开辟的空间,当前字符串空间也变成了垃圾等待GC回收。
- 创建string会放入字符池中:通过以下三种方式才会放入字符池中,字面量值new string、字面量值拼接、string.Internal
- 字符串底层是通过键值对的方式存储数,键是字符串内容、值是字符串在堆中的地址;创建一个字符串时会在字符池中搜索是否有当前字符串内容的键,如果有就用相同的键和值,如果不存在就创建对应的键和值
stringBuild和stringBuffer都只会开辟一个空间。
stringBuild是单线程,线程安全。性能比stringBuffer较好一些
stringBuffer是多线程,线程不安全。性能比stringBuild较差一些
三者性能比较:stringBuild > stringBuffer > string
6.面试题虚函数相关:
因为Model2继承Model类后,Fun函数被覆盖了,不会执行;只会执行父类的Fun函数 。而Model3override没有base,只会执行当前类的Fun
7.string、int、float、interface、enum、Object、struct、UnityEngine.Color、UntiyEngine.Ray哪些是值类型哪些是引用类型
值类型:int、float、enum、struct、UnityEngine.Color、UnityEngine.Ray
引用类型:string、interface、Object、
8.类和接口的区别:
1.类只能被继承一个,接口可以继承无限制个。
2.继续接口后,里面的方法必须要被实现;类只有抽象方法才必须要被实现
3.接口里面不能实现方法;类里面可以实现方法
4.接口里面不能赋值变量,类可以赋值变量
9.Array、ArrayList、List、Hashtable、Dictionary、Stack、Queue区别:
Array(数组):存储在栈中。需要确定长度,静态存储在连续的内存中,查找速度O(1),但是插入或删除需要移动大量数据O(n)
链表:存储在堆中。动态存储在不同的内存块中,查找速度要遍历每个节点所有为O(n),插入删除为O(1)
ArrayList:存储的类型是Object,通过动态扩容,可以存储不同类型的变量,存储不同类型的变量也会频繁的产生装箱拆箱
List:存储的类型是泛型,通过动态扩容,不可以存储不同类型的变量,不会产生装箱拆箱
Hashtable:键值对字典,键和值都是Object类型,写入是单线程,读取是多线程
Dictionary:键值对字典,键和值都是泛型,读\写都是单线程
Hashtable和Dictionary区别:单线程建议使用Dictionary,因为是泛型读取效率较快,容量利用更充分。
在多线程中Dictionary不安全,需要通过lock锁进行处理
Stack:栈,先进后出
Queue:队列,先进先出
10.浅拷贝和深拷贝
最直观的是值类型和引用类型,值类型是深拷贝,引用类型是浅拷贝(引用类型也可以深拷贝)
浅拷贝:B复制A,A的内容进行改变,B的内容也会进行改变
深拷贝:B复制A,A内容改变,B内容不会改变
11.ref和out的区别
ref:使用前需要初始化,想让值类型当引用类型使用
- 举例:比如确定链表的节点总数是单数还是双数,可以事先定义一个bool变量isOdd。然后利用函数将isOdd为参数,GetListNodeSum(ListNode head,ref bool isOdd)。当函数逻辑将isOdd改变时,函数外的isOdd也会受影响
out:使用时不需要初始化,快捷使用(输出新的值)
- 举例:比如在道具系统中,字典存储了道具id和道具数据。那么想获取指定道具可以使用out,dic.TryGetValue(道具id,out item)
参考: ref 和 out 关键字详解_c#中ref和out关键字-CSDN博客
Unity知识点:
1.Unity中生命周期函数执行顺序
Awake->OnEnable->Start->FixedUpdate->Update->LateUpdate->OnGUI->OnDisable->OnDestroy
初始化时间:Awake(对象实例化时)->OnEnable(对象激活时)->Start(对象实例化完后执行)->FixedUpdate(每帧固定物理帧)->Update(渲染前的每帧,可以执行人物移动等等)->LateUpdate(渲染后的每帧,可以执行相机跟随等等)->OnGUI(渲染和处理GUI事件)->OnDisable(对象失活)->OnDestroy(对象销毁)
【扩展问题:Awake为什么不能执行协程???】
因为对象在实例化时就会执行Awake,对象都没有实例化好,就去调用对象自然会报错或异常。
建议在Start启动协程
2.协程是什么
协程是通过IEnumerator迭代器实现的一种状态机。协程并不是单独的线程,它属于主线程的一部分,如果协程卡死那么主线程也会卡死
协程函数通过yield return语法糖分成各个代码块,其中迭代器中有MoveNext函数和Current属性,MoveNext函数用来确定迭代器是否执行完(MoveNext的返回值如果为true代表还有下一个代码块,如果为flase代表执行完了);Current存有yield return对象,然后转换为IEnumerator对象返回给协程函数
3.点乘、叉乘和归一化的几何意义是什么?
点乘可以计算两个向量之间的夹角。判断对象在自己的前方还是后方
叉乘用于判断对象在自己的左方还是右方
归一化:没有大小,只有方向的向量
4.Collider和Trigger区别,碰撞的必要条件是什么?
Collider是碰撞器,Trigger是触发器。触发器是基于碰撞器实现的,触发器在碰撞器的Inspector面板下
它们都是物理系统。碰撞器类似于现实世界的物体,无法穿透。而触发器可以穿透物体
必要条件:双方都有Collider碰撞器,至少一方有RigidBody刚体
5.了解过四元数吗??
用于处理旋转方面。相对于欧拉角,四元数不存在万向节死锁;在Inspector面板上所看到的也没有那么直观,只能看到180至-180度,不能很好的确定物体所旋转了多少圈
6.Unity提供了几种光源,分别是什么?
Directional Light:平行光
Point Light:点光源
Area Light:范围光。这个稍微有点特殊,需要把被光源照射的物体设置为static(我的是cube对象);还有在Windows选项卡--Rendering--Lighting Setting--界面最下面勾选Auto Generate(自动生成)。不然创建了范围光,但是在场景中看不到效果
【我的是2019版本,我看别人那些太老的版本没有Auto Generate的选项】
Spot Light:聚光灯
7.委托和事件的区别:
委托的本质是一个类,是用来装载函数的容器,可以+=、-=添加或者删除函数
而事件是委托类型的一个变量
委托可以在定义外出用=赋值函数,但是事件只能在定义的时候用=赋值,不可在外部用=赋值
8.Mono和IL2CPP的区别
Mono和IL2CPP都是Unity打包的一种方式
- Mono:将C#代码转换为CLR(中间代码),然后通过各平台的Mono虚拟机转换为机器码
- IL2CPP:IL2CPP会直接转换为C++代码,然后通过C++编辑器转换为机器码
IL2CPP比Mono性能更好;Mono可以通过ILSpy反编译,但是IL2CPP不能反编译
排序算法:
1.用快排写一个逆序:
简单理解
理论
- 选取基准,产生左右标识,左右比基准,满足则换位
- 排完一次,基准定位
- 左右递归,直到有序
我自己的简单理解
- 确定左右标识、左右基准、基准值(一开始为左基准值,也就是下图的数值3)
- 当前值为基准值,然后与左右基准值进行比较,通过换位确定基准值应该存在的位置
交换位置:基准值为3,从右基准的位置开始找比3小的数放到左边;然后再从左基准开始找比3大的数放到右边。
- 缩小范围继续比较(除基准值以外,确定两个区间范围)
static void Main(string[] args)
{
//测试数据
int[] array = { 1, 4, 2, 5 };
//将数据排序
QuickSort(array,0,array.Length - 1);
//排序后的数据
foreach (int arr in array)
{
Console.WriteLine(arr);
}
}
public static void QuickSort(int[] arr, int left, int right)
{
if (left >= right) return;
int tempLeft, tempRight, temp;
tempLeft = left;
tempRight = right;
temp = arr[left];
while (tempLeft != tempRight)
{
while (tempLeft < tempRight && arr[tempRight] < temp)
{
tempRight--;//移动右标识
}
arr[tempLeft] = arr[tempRight];//处理小于基准值的数,移动到左边
while (tempLeft < tempRight && arr[tempLeft] > temp)
{
tempLeft++;//移动左标识
}
arr[tempRight] = arr[tempLeft];//处理大于基准值的数,移动到右边
}
arr[tempLeft] = temp;//基准值通过排序,确定它应该放的位置
//缩小范围处理,除基准值之外的范围
QuickSort(arr, left, tempLeft - 1);
QuickSort(arr, tempRight + 1, right);
}
Lua知识点:
1.Lua有几种变量类型
8种。number、string、bool、table、function、Thread、userdata、nil
2.以下代码结果是什么:local table = {1,2,[2] = 3}
会首先将[2]=3存储到内存,然后再将数字索引1和2存储到内存,数字2存储的时候会覆盖[2]=3。最后的输出结果为:1,2
3.代码结果是什么:
结果为1,因为ipairs遍历碰到nil就会中断
4.遍历中ipairs和pairs区别是什么
ipairs:只能遍历数字,有序遍历,在遍历的过程中如果碰到nil就会中断,不会再往下遍历
pairs:所有的类型都可以遍历,无序遍历,可以遍历table中的所有变量
5.元表中的__index、__newindex、__call、__tostring、rawset、rawget是什么
__index:如果通过setmetatable设置了元表,获取当前表没有的变量,就会去元表的__index寻找该变量。如果__index是函数,那么就执行函数(如果是表,则在表中寻找变量)
__newindex:赋值当前表中没有的变量,则会调用__newindex。__newindex赋值的是表就修改表,如果是函数就执行函数
__call:执行表中传入的参数为数值时,则会调用__call
__tostring:将当前表赋值给其他对象时,则会调用__tostring
--rawset会绕开__newindex原方法
local meta12 = {}
meta12.__newindex = {}
local t12 = {}
setmetatable(t12,meta12)
t12.age = 17
rawset(t12,"name","小白白")
print(t12.age)--nil
print(t12.name)--17
--绕开元表中的__index方法
local t12 = {}
local meta12 = {}
meta12.__index = {a = 11}
setmetatable(t12,meta12)
print(rawget(t12,a))--nil
print(t12.a)--11
6.table如何判当前是空表,如何获取table长度
如果确定table中只有数字一种类型,那么可以使用#获取table长度。否则使用table.getn(),此函数不能在在线编辑器中使用,因为在线编辑器没有对应的库
7.function中的点和冒号的区别
点:直接调用函数
冒号:将方法的对象隐含的传给function的第一个参数。函数使用冒号创建函数时,那么在使用函数的时候也要使用冒号;如果使用点的方式调用函数,需要显示的传入第一个对象(表)
8.闭包是什么
闭包我们可以简单理解为有“外部函数”和“内部函数”,当“外部函数”执行完了,“内部函数”依旧可以调用“外部函数”的变量。其中“外部函数”的变量是地址,以C#为例如果是值类型,那么执行完函数该变量就会自动销毁,这很明显不满足闭包的特性
9.C#如何调用Lua
lua是通过c语言制作的。其中c语言可以直接调用lua,所以C#会通过一些接口调用c语言,然后c语言在调用lua
其中调用流程:C#调用c,c调用Lua
10.Lua如何实现面向对象三大特性
面向对象三大特性
封装:利用table进行封装
继承:利用元表和__index模拟继承关系
设置子类的元表为父类,父类的__index为父类自己
当子类身上找不到对应属性和方法时
会查找元表的__index中的内容,也就是会查找父类中的内容
通过这种方式来模拟继承
多态:子类自己去实现带:的同名方法即可
UI框架
渲染优先级
Sorting Layer(只能Sprite,可在组件中添加Sprite Renderer) > Sort Order > PlanceDistance > Hierarchy中顺序
- Sorting Layer(越小越先渲染)
- Sort Order(越小越先渲染)
- PlanceDistance(越大越先渲染,越大离摄像机越近)
运行时改变UI层级
- transform.SetAsFirstSibling -- 最先渲染
- transform.SetSiblingIndex(N) -- N为0和transform.SetAsFirstSibling渲染顺序一样
- transform.SetAsLastSibling -- 最后渲染
Protobuf
Protobuf优缺点
优点:通过二进制的方式编码。体积小,传输和解析速度快。跨平台
缺点:可读性差。需要编译时静态编译,不能动态添加、删除字段