MIL4——模拟面试题

问题

C#

  1. 请说明字符串中 string str = null​、string str = ""​、string str = string.Empty​ 三者的区别
  2. C# 重载运算符,重载 ==​ 和 !=​ 以及 万物之父 object​ 基类中的虚方法 virtual bool Equals(Object obj)​ 对于我们的意义是什么?
  3. 在开发时,对 string​ 和 StringBuilder​ 我们应该如何选择
  4. 请简要说明 .NET 跨语言原理
  5. 请简要说明 .NET 跨平台原理

Unity

  1. Unity中的 Destroy​ 和 DestroyImmediate​ 的区别是什么?

  2. 请问最终打印的 s​ 的结果为?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    string s = string.Empty;
    GameObject go = new GameObject();
    DestroyImmediate(go)
    if (!go)
    s += "A";
    if (go is null)
    s += "B"
    if (go == null)
    s += "C"
    if ((System.Object)go == null)
    s += "D"
    Debug.Log(s);
  3. 第一次执行 GameObject.Instantiate​ 时可能出现明显的卡顿 如何解决该问题?

  4. Lua如何实现面向对象的三大特性?

  5. Unity使用IL2CPP打包时,我们应该注意什么?如何避免(可以举例说明)

答案

C#

  1. 请说明字符串中 string str = null​、string str = ""​、string str = string.Empty​ 三者的区别

    答案:

    • str = null​ 在堆中没有分配内存地址
    • str = ""​ 和 string.Empty​ 一样都是在堆内存中分配了空间,里面存储的是空字符串,而 string.Empty​ 是一个静态只读变量
  2. C# 重载运算符,重载 ==​ 和 !=​ 以及 万物之父 object​ 基类中的虚方法 virtual bool Equals(Object obj)​ 对于我们的意义是什么?

    答案:

    为了判断两个对象的非引用地址相等,我们可以选择 使用 重载运算符 ==​ 和 !=​ 或者重写 Equals​ 方法,来自定义判断两个对象是否相等
    如果想保留原有的引用地址相等判断,那么一般我们选择重写 Equals​ 方法

  3. 在开发时,对 string​ 和 StringBuilder​ 我们应该如何选择

    答案:

    string​ 在每次拼接时都会产生垃圾,而 StringBuilder​ 在拼接时,是在原空间中进行修改,不会产生垃圾,会自动帮助我们扩容
    所以当字符串需要频繁修改拼接时,我们使用 StringBuilder

  4. 请简要说明 .NET 跨语言原理

    答案:

    .NET 制定了 CLI 公共语言基础结构的规则,只要是按照该规则设计的语言在进行 .NET 相关开发时
    编译器会将源代码(C#、VB等等)编译为CIL通用中间代码。也就是说不管什么语言进行开发,最终都会统一规范变为中间代码
    最终通过CLR(公共语言运行时或者称为 .NET 虚拟机)将中间代码翻译为对应操作系统的原生代码(机器码)在操作系统(Windows)上运行

  5. 请简要说明 .NET 跨平台原理

    答案:

    由于 .NET Framework 中利用 CLI 和 CLR 实现了跨语言,CLR主要起到一个翻译、运行、管理中间代码的作用
    .NET Core 和 Mono 就是利用了 CLR 的这一特点,为不同操作系统实现对应 CLR(公共语言运行时或.NET虚拟机)
    那么不同操作系统对应的CLR就会将IL中间代码翻译为对应系统可以执行的原生代码(机器码)达到跨平台的目的

Unity

  1. Unity中的 Destroy​ 和 DestroyImmediate​ 的区别是什么?

    答案:

    • Destroy​ 方法
      可以指定删除的延迟时间,如果第二个参数不填写,最快也会在下一帧前完成删除。
      也就是如果 Destroy​ 对象后马上判空,该对象不会为空。
      实际的对象销毁操作始终延迟到当前更新循环结束,但始终在渲染前完成
    • DestroyImmediate​ 方法,会立即销毁删除对象
  2. 请问最终打印的 s​ 的结果为?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    string s = string.Empty;
    GameObject go = new GameObject();
    DestroyImmediate(go)
    if (!go)
    s += "A";
    if (go is null)
    s += "B"
    if (go == null)
    s += "C"
    if ((System.Object)go == null)
    s += "D"
    Debug.Log(s);

    答案:AC

    主要考点

    1. DestroyImmediate​ 方法会立即将 GameObject​ 对象从场景上删除

    2. UnityEngine.Object​ 中对 ==​、!=​、!​ 进行了重载,如果用 !go​ 和 go == null​ 去判断对象是否为空,
      由于重载了,所以能够返回正确的结果 true​ 和 false

      但是本质上此时的 go​ 还不是真正意义上的 null​,所以如果用 go is null
      或者 将其转换为 万物之父 object​,(System.Object)go == null​ 去判断时 并不会为true
      因此只会进入 AC​ 的 if​ 语句

    这里的重点内容就是 UnityEngine.Object​ 中重载了 逻辑非 !​ 和 ==​、!=​ 运算符,因为使用他们来判断 null​ 是可以的,
    但是此时的 GameObject​ 在内部并不是真正意义的null​,我们在使用时最好手动置空

  3. 第一次执行 GameObject.Instantiate​ 时可能出现明显的卡顿 如何解决该问题?

    答案:

    我们可以通过Unity自带的性能分析工具Profiler分析实例化时造成卡顿的原因

    • 程序上,一般我们可以从以下3个方面去优化它

      1. 相关资源加载:如果是由于资源加载带来的卡顿,我们可以在进入场景时进行资源预加载,总体思路就是将较大资源提前或者分帧加载
      2. 脚本初始化:实例化对象时,会同步执行它身上挂载所有脚本的初始化工作,
        我们可以策略性的改变一些初始化逻辑,尽量不要在 Awake​ 和 Start​ 中做较复杂的逻辑,或者将复杂逻辑提前或者分帧处理
      3. 对于会频繁使用的对象,我们可以使用缓存池
    • 美术上

      不能只追求好的美术效果,而不考虑资源的消耗,要根据项目的实际情况,来设定模型的骨骼数、面数以及贴图的数量和大小上限。
      在制作粒子特效时,粒子数、粒子面积、贴图等都要尽量少和小。

      美术上要遵循:用最少的资源做出做好的效果,不能一味的用性能去换效果,最终会得不偿失

  4. Lua 如何实现面向对象的三大特性?

    答案:面向对象三大特性

    • 封装:利用 table​ 进行封装
    • 继承:利用元表和 __index​ 模拟继承关系,设置子类的元表为父类,父类的 __index​ 为父类自己
      当子类身上找不到对应属性和方法时
      会查找元表的 __index​ 中的内容,也就是会查找父类中的内容,通过这种方式来模拟继承
    • 多态:子类自己去实现带:​的同名方法即可
  5. Unity使用IL2CPP打包时,我们应该注意什么?如何避免(可以举例说明)

    答案:

    使用IL2CPP打包时,最可能出现的问题就是代码裁剪,IL2CPP会自动将它认为不会使用的代码裁剪掉,
    比如我们在使用Lua开发时,其实会用到很多UnityEngine或者我们自己写的C#代码,但是这些代码并不会在引擎中直接使用,
    都是在Lua中使用的,此时最容易出现的问题就是代码裁剪,导致打包后出现异常和报错。

    要避免IL2CPP的裁剪有3种方式,我们可以组合使用

    1. 设置打包时的裁剪等级
    2. 通过xml文件配置明确规定哪些内容不裁剪
    3. 在静态方法中显示调用不想被裁剪的内容