(BCWL)U3D程序员笔试题

  • 1.请回答下述基础相关问题
    a) 什么是泛型,泛型和传统类型的Boxing/Unboxing有什么区别,写一个泛型类C使其继承于Cbase;
    什么是泛型:
    泛型允许延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
    泛型和传统类型的Boxing/Unboxing有什么区别:
    装箱:值类型转引用类型。开辟一块内存空间进行存放数据。
    拆箱:引用类型转值类型。
    泛型对装箱拆箱的影响在集合的应用上有巨大的体现,诸如用 ArrayList 来储存 int 数据和用 List 来储存 int 数据。未使用泛型的 ArrayList 在每次存新数据时都会有从 int 到 object 的装箱操作,每次读取时都会有拆箱操作,而使用泛型的 List 不需要这些操作,也就避免了这方面的性能消耗。
    写一个泛型类C使其继承于Cbase:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        public class CBase
    {

    }

    public class C<T> : CBase
    {

    }

    public class C1<T> where T : CBase
    {

    }

    public class CTest : CBase
    {

    }
    ----------------------------------------------------------------------------------------------------
    C<CTest> ctest = new C<CTest>();
    C1<CTest> ctest1 = new C1<CTest>();
    b) OnEnable、Awake、Start执行的先后顺序;
    Awake > OnEnable > Start
    c) 请简述反射的用途,写一段代码动态加载一个程序集,从该程序集获取类型T,从类型T获得名为Create的静态方法,调用该静态方法;
    请简述反射的用途:
    (1)使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
    (2)使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
    (3)使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
    (4)使用MethodInfo了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等。
    (5)使用FiedInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
    (6)使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
    (7)使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
    (8)使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
    (9)当您在一个应用程序域的仅反射上下文中工作时,请使用 CustomAttributeData 来发现有关自定义特性的信息。 通过使用 CustomAttributeData,您不必创建特性的实例就可以检查它们。
    写一段代码动态加载一个程序集,从该程序集获取类型T,从类型T获得名为Create的静态方法,调用该静态方法:
    在.Net 中,程序集(Assembly)中保存了元数据(MetaData)信息,因此就可以通过分析元数据来获取程序集中的内容,比如类,方法,属性等,这大大方便了在运行时去动态创建实例。反射提供了封装程序集、模块和类型的对象(Type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问。
    主要用途:
    1.动态加载DLL,实现插件机制。
    2.实例化DLL中的类型。
    3.执行后期绑定,访问在运行时创建的类型的方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    namespace TEST
    {
    public class Reflection
    {
    public string Name { get; set; }
    public bool IsTest { get; set; }


    public static void Create()
    {
    Console.WriteLine("reflection Create");
    }

    public void LogName()
    {
    Console.WriteLine(Name);
    }
    }
    }
    ----------------------------------------------------------------------------------------------------
    using System;
    using System.Reflection;

    namespace TestReflection
    {
    class Program
    {
    static void Main(string[] args)
    {
    //DLL地址
    string dllStr = @"C:\Users\mshz\source\repos\TEST\bin\Debug\netcoreapp3.1\TEST.dll";

    Assembly asm = Assembly.LoadFile(dllStr);
    //获取Reflection类的类型 需要完整的类名,包括命名空间
    var type = asm.GetType("TEST.Reflection");
    //创建该类型的实例
    var instance = asm.CreateInstance("TEST.Reflection");
    //设置属性
    type.GetProperty("Name").SetValue(instance, "https://afoolzwt.github.io/");
    type.GetProperty("IsTest").SetValue(instance, true, null);
    //获取方法
    var method1 = type.GetMethod("LogName");
    var method2 = type.GetMethod("Create");

    //调用方法
    method1.Invoke(instance, null);
    method2.Invoke(instance, null);

    Console.ReadKey();
    }
    }
    }
    ----------------------------------------------------------------------------------------------------
    https://afoolzwt.github.io/
    reflection Create
  • 2.请回答下述资源相关的问题
    a) Prefab的作用,如何获取Prefab实例化出来的GameObject的预置类型,PrefabInstance,ModelPrefabInstance等预置类型有什么不同;
    Prefab的作用:
    预制体(Prefab)可以允许我们创建基于某个游戏对象的模板,通过这个模板我们可以非常方便快捷的反复创建具有相同结构的游戏对象。
    如何获取Prefab实例化出来的GameObject的预置类型:
    PrefabInstance,ModelPrefabInstance等预置类型有什么不同:
    (已过期)

    b) 资源如何通过AssetBundle打包和加载;

    c) AssetDatabase.LoadAssetAtPath和Resource.Load分别适用于什么场合
  • 3.请回答下述几何相关问题(10分):
    a) 正交摄像机和透视摄像机的区别,第一人称摄像机Fov变化会产生怎样的摄像机效果;
    正交摄像机和透视摄像机的区别:

    透视摄像机Perspective:
    有消失点投影,透视视图和我们从眼睛看到的视图是一样的。
    正交摄像机Orthographic:
    无消失点投影,正交视图无法看到一个物体是远离自己还是正在我们面前。
    第一人称摄像机Fov变化会产生怎样的摄像机效果:
    Fov可调节摄像机的视口效果,改变Fov的值会影响摄像机渲染的物体的大小。
    b) 给定屏幕一个点(x,y)如何进行对场景中的GameObject进行射线拾取;
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    c) 拖尾效果中的带子采用Bezier曲线/Catmull-rom曲线的差异;
  • 4.请回答下述渲染相关的问题
    a) HDR中动态范围压缩和Tone Mapping原理;
    b) MeshRender和SkinnedMeshRender的关系与不同;
    c) 写一段代码从上述Render获取材质并切换Shade,并获取该Shader中某uniform constant值,每帧修改该constant值产生uv动画实现装备上流动的流光效果
    d) 法线贴图如何进行烘培(Bake);
    (后续研究)

Unity面试题汇总

C#语言

    1. 重载和重写的区别
  1. 所处位置不同 重载在同类中 重写在父子类中
  2. 定义方式不同 重载方法名相同 参数列表不同 重写方法名和参数列表都相同
  3. 调用方式不同 重载使用相同对象以不同参数调用 重写用不同对象以相同参数调用
  4. 多态时机不同 重载时编译时多态 重写是运行时多态
    1. 面向对象的三大特点
      封装、继承、多态
      1、封装: 封装是将数据和行为相结合,通过行为约束代码修改数据的程度,增强数据的安全性,属性是C#封装实现的最好体现。
      2、继承: 提高代码重用度,增强软件可维护性的重要手段,符合开闭原则。
      3、多态性: 多态性是指同名的方法在不同环境下,自适应的反应出不同得表现,是方法动态展示的重要手段。
    1. 简述值类型和引用类型有什么区别

1.值类型存储在内存栈中,引用类型数据存储在内存堆中,而内存单元中存放的是堆中存放的地址。
2.值类型存取快,引用类型存取慢。
3.值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针和引用。
4.栈的内存是自动释放的,堆内存是.NET 中会由 GC 来自动释放。
5.值类型继承自 System.ValueType,引用类型继承自 System.Object。

    1. 请简述private,public,protected,internal的区别
      private:仅对该类公开。
      public:对任何类和成员都公开,无限制访问。
      protected:对该类和其派生类公开。
      internal:只能在包含该类的程序集中访问该类。
    1. C#中所有引用类型的基类是什么
      引用类型的基类是 System.Object 值类型的基类是 System.ValueType
      同时,值类型也隐式继承自 System.Object
    1. 请简述 ArrayList 和 List的主要区别
      ArrayList 不带泛型 数据类型丢失
      List 带泛型 数据类型不丢失
      ArrayList需要装箱拆箱 List不需要
    1. 请简述 GC(垃圾回收)产生的原因,并描述如何避免?
      GC 为了避免内存溢出而产生的回收机制
      避免:
      1)减少 new 产生对象的次数
      2)使用公用的对象(静态成员)
      3)将 String 换为 StringBuilder
    1. 请描述 Interface 与抽象类之间的不同

1.接口不是类 不能实例化 抽象类可以间接实例化
2.接口是完全抽象 抽象类为部分抽象
3.接口可以多继承 抽象类是单继承

    1. 下列代码在运行中会产生几个临时对象?
      1
      2
      string a = new string("abc");
      a = (a.ToUpper() + "123").Substring(0, 2);

1.a.ToUpper()
2.”123”
3.a.ToUpper()+”123”

    1. 下列代码在运行中会发生什么问题?如何避免?
      1
      2
      3
      4
      5
      6
      7
      List<int> lst = new List<int>(new int[] { 1, 2, 3, 4, 5 });

      foreach (int item in lst)
      {
      Console.WriteLine(item * item);
      lst.RemoveAt(item);
      }
      会产生运行时错误,因为 foreach 是只读的。不能一边遍历一边修改。
    1. 请简述关键字 Sealed 用在类声明和函数声明时的作用
      类声明时可防止其他类继承此类,在方法中声明则可防止派生类重写此方法。
    1. 反射的实现原理?
      可以在加载程序运行时,动态获取和加载程序集,并且可以获取到程序集的信息
      反射即在运行期动态获取类、对象、方法、对象数据等的一种重要手段
      主要使用的类库:System.Reflection
      核心类:

1.Assembly描述了程序集
2.Type描述了类这种类型
3.ConstructorInfo描述了构造函数
4.MethodInfo描述了所有的方法
5.FieldInfo描述了类的字段
6.PropertyInfo描述类的属性
通过以上核心类可在运行时动态获取程序集中的类,并执行类构造产生类对象,动态获取对象的字段或属性值,更可以动态执行类方法和实例方法等。

    1. .Net 与 Mono 的关系?
      
      .Net是一个语言平台,Mono为.Net提供集成开发环境,集成并实现了.NET的编译器、CLR 和基础类库,使得.Net既可以运行在windows也可以运行于 linux,Unix,Mac OS 等。
    1. 在类的构造函数前加上 static 会报什么错?为什么?
      构造函数格式为 public+类名如果加上 static 会报错(静态构造函数不能有访问修饰符)。
      原因:静态构造函数不允许访问修饰符,也不接受任何参数;无论创建多少类型的对象,静态构造函数只执行一次;运行库创建类实例或者首次访问静态成员之前,运行库调用静态构造函数;静态构造函数执行先于任何实例级别的构造函数;显然也就无法使用 this 和 base 来调用构造函数。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
          public class constructor
      {
      static constructor()
      {
      Console.WriteLine("static constructor");
      }

      public constructor()
      {
      Console.WriteLine("public constructor");
      }
      }
      ----------------------------------------------------------------------------------------------------
      class Program
      {
      static void Main(string[] args)
      {
      constructor cs = new constructor();

      Console.ReadKey();
      }
      }
      ----------------------------------------------------------------------------------------------------
      static constructor
      public constructor
    1. C# String 类型比 stringBuilder 类型的优势是什么?
      1、 在处理字符串时:String是创建一个string对象,在创建对象时需要分配内存空间。StringBuilder是在原来的内存中修改,不需要分配内存空间。
      2、 从内存优化方面来说,频繁对字符串的操作使用StringBuilder更好。
      3、 从功能上来说String仍然比StringBuilder更强。
      4、 String主要用于公共API,通用性好,读取性能高,占用内存小。
      5、 StringBuilder主要用于拼接String,修改性能好。
      6、 String是不可变的,所以天然线程同步。
      7、 StringBuilder可变,非线程同步。
      如果是处理字符串的话,用 string 中的方法每次都需要创建一个新的字符串对象并且分配新的内存地址,而 stringBuilder 是在原来的内存里对字符串进行修改,所以在字符串处理方面还是建议用 stringBuilder 这样比较节约内存。但是 string 类的方法和功能仍然还是比 stringBuilder 类要强。string 类由于具有不可变性(即对一个 string 对象进行任何更改时,其实都是创建另外一个 string 类的对象),所以当需要频繁的对一个 string 类对象进行更改的时候,建议使用StringBuilder 类,StringBuilder 类的原理是首先在内存中开辟一定大小的内存空间,当对此 StringBuilder 类对象进行更改时, 如果内存空间大小不够, 会对此内存空间进行扩充,而不是重新创建一个对象,这样如果对一个字符串对象进行频繁操作的时候,不会造成过多的内存浪费,其实本质上并没有很大区别,都是用来存储和操作字符串的,唯一的区别就在于性能上。String 主要用于公共 API,通用性好、用途广泛、读取性能高、占用内存小。StringBuilder 主要用于拼接 String,修改性能好。不过现在的编译器已经把 String 的 + 操作优化成 StringBuilder 了, 所以一般用String 就可以了。
      String 是不可变的,所以天然线程同步。StringBuilder 可变,非线程同步。
    1. C# 函数 Func(string a, string b)用 Lambda 表达式怎么写?
      (a,b) => {};
    1. 数列 1,1,2,3,5,8,13…第 n 位数是多少?用 C#递归算法实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public static int Fibonacci(int index)
      {
      if (index == 1 || index == 2)
      {
      return 1;
      }
      else
      {
      return Fibonacci(index - 1) + Fibonacci(index - 2);
      }
      }
    1. 冒泡排序
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public static List<int> Bubbling(List<int> lst)
      {
      for (int i = lst.Count -1; i > 0; i--)
      {
      for (int j = 0; j < i; j++)
      {
      if (lst[j] > lst[j+1])
      {
      lst[j] = lst[j] + lst[j + 1];
      lst[j + 1] = lst[j] - lst[j + 1];
      lst[j] = lst[j] - lst[j + 1];
      }
      }
      }

      return lst;
      }
    1. C#中有哪些常用的容器类,各有什么特点。
      List:索引泛型容器 访问速度快 修改速度慢
      HashSet/Dictionary:散列表格式 查询效率高 空间占用较大
      Stack:后进先出
      Queue: 先进先出
    1. C#中常规容器和泛型容器有什么区别,哪种效率高?
      不带泛型的容器需要装箱和拆箱操作 速度慢 所以泛型容器效率更高 数据类型更安全
    1. 有哪些常见的数值类?
      简单值类型–包括 整数类型、实数类型、字符类型、布尔类型
      复合值类型–包括 结构类型、枚举类型
    1. C#中委托和接口有什么区别?各用在什么场合?
      接口(interface)是约束类应该具备的功能集合,约束了类应该具备的功能,使类从千变万化的具体逻辑中解脱出来,便于类的管理和扩展,同时又合理解决了类的单继承问题。
      C#中的委托是约束方法集合的一个类,可以便捷的使用委托对这个方法集合进行操作。
      在以下情况中使用接口:

1.无法使用继承的场合
2.完全抽象的场合
3.多人协作的场合
以上等等
在以下情况中使用委托:
多用于事件处理中

    1. C#中unsafe关键字是用来做什么的?什么场合下使用?
      非托管代码才需要这个关键字 一般用在带指针操作的场合
    1. C#中ref和out关键字有什么区别?
      ref修饰参数,表示进行引用传递,out修饰参数也表示进行引用传递,但传递的引用只为带回返回值 ref又进又出 out不进只出
    1. For,foreach,Enumerator.MoveNext的使用,与内存消耗情况
      for 循环可以通过索引依次进行遍历,foreach和Enumerator.MoveNext通过迭代的方式进行遍历。内存消耗上本质上并没有太大的区别。
    1. 函数中多次使用string的+=处理,会产生大量内存垃圾(垃圾碎片),有什么好的方法可以解决。
      通过StringBuilder那进行append,这样可以减少内存垃圾。
    1. 当需要频繁创建使用某个对象时,有什么好的程序设计方案来节省内存?
      设计单例模式进行创建对象或者使用对象池。
    1. JIT 和AOT区别
      Just-In-Time - 实时编译
      执行慢 安装快 占空间小一点
      Ahead-Of-Time - 预先编译
      执行快 安装慢 占内存占外存大
    1. 给定一个存放参数的数组,重新排列数组
      void SortArray(Array arr){Array.Sort(arr);}
    1. Foreach循环迭代时,若把其中的某个元素删除,程序报错,怎么找到那个元素?以及具体怎么处理这种情况?(注:Try…..Catch捕捉异常,发送信息不可行)
      foreach 不能进行元素的删除,因为迭代器会锁定迭代的集合,解决方法:记录找到索引或者key值,迭代结束后再进行删除。
    1. GameObject a=new GameObject() GameObject b=a 实例化出来了A,将A赋给B,现在将B删除,问A还存在吗?
      存在,b删除只是将它在栈中的内存删除,而A对象本身是在堆中,所以A还存在
    1. 你拥有A块钱,一瓶水B块钱,每瓶水可以得到一个瓶盖,每C个瓶盖可以换一瓶水请写出函数求解上面题目,上面题目ABC为参数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      /// <summary>
      /// ABC
      /// </summary>
      /// <param name="A">拥有A块钱</param>
      /// <param name="B">一瓶水B块钱</param>
      /// <param name="C">C个瓶盖可以换一瓶水</param>
      public static int ABC(int A,int B,int C)
      {
      int bottle = A / B;
      return bottle + Exchange(bottle,C);
      }

      public static int Exchange(int bottle,int C)
      {
      if (bottle < C)
      {
      return 0;
      }
      else
      {
      return bottle/C + Exchange(bottle%C + bottle/C,C);
      }
      }
    1. 有一排开关,第一个人把所有的开关打开,第二个人按2的倍数的开关,第三个人按3的倍数的开关,以此类推,现在又n个开关,k个人,写函数求最后灯亮着的开关,输入参数n和k
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      /// <summary>
      /// 灯亮着的开关
      /// </summary>
      /// <param name="n">n个开关</param>
      /// <param name="k">k个人</param>
      public static List<bool> Switchs(int n,int k)
      {
      List<bool> lights = new List<bool>(n);
      for (int i = 0; i < n; i++)
      {
      lights.Add(true);
      }
      if (k<=1)
      {
      return lights;
      }
      for (int i = 2; i <= k; i++)
      {
      for (int j = 0; j < lights.Count; j++)
      {
      if ((j + 1) % i == 0 && j+1 >= i)
      {
      lights[j] = !lights[j];
      }
      }
      }

      return lights;
      }
    1. 数制转换,将任意整数转换成8进制形式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public static void int2Eight(int num)
      {
      if (num > 7)
      {
      int2Eight(num / 8);
      }

      Console.WriteLine(num%8);
      }
    1. 找出200以内的素数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public static void Main(string[] args) {
      int count = 0;
      for (int i = 1; i < 200; i++) { //外层循环:要判断的数
      for (int j = 2; j <=i; j++){
      if (i % j == 0&& i!=j) {
      break;
      }
      if (j == i ) { //结束的条件:最后一个数还没有被整除
      count++;
      Console.WriteLine(i);
      }
      }
      }
      Console.WriteLine(count);
      }