RhinoFreak 发表于 2022-12-19 20:07

Unity 面试基础知识汇总 (c#方向)

一; c#数据类型 Array,ArrayList,dictionary, hashtable,List 详解
1:Array   数组在内存中是连续的存储的(只需存储velue值),所以索引速度很快 时间复杂度 o1,声明数组的时候 必须同时声明数组的长度,插入和删除效率比较低,需要移动大量的元素
2:ArrayList(动态数组) 继承自IList接口,声明的时候不需要声明数组的长度,会动态进行扩充和收缩的,可以插入不同类型的数据,因为类型是object类型的,会频繁的装箱拆箱
3:List (泛型List)继承自IList接口,存储内存类型跟ArrayList相似,区别是声明List集合的时候同时需要声明类型,不能插入不同的类型
4: 不支持泛型,Hashtable中key-value键值对均为object类型,所以Hashtable可以支持任何类型的keyvalue键值对,任何非 null 对象都可以用作键或值。允许单线程写入, 多线程读取, 无序
5:dictionary支持泛型,单线程读取速度对比Hashtable有优势,Dictionary<K,V>是泛型的,当K或V是值类型时,其速度远远超过Hashtable。,如果key是字符串型,Dictionary的效率没有Hashtable快。有序(遍历时输出的顺序就是加入的顺序)
HashTable或者Dictionary,他是根据Key和Hash算法分析产生的内存地址,因此在宏观上是不连续的,由于这样的不连续,在遍历时,Dictionary必然会产生大量的内存换页操作,而List只需要进行最少的内存换页即可,这就是List和Dictionary在遍历时效率差异的根本原因。而且在尾部插入时,List只需要在其原有的地址基础上向后延续存储即可,而Dictionary却需要经过复杂的Hash计算,这也是性能损耗的地方。
hashtable 和dictionary的区别
单线程程序中推荐使用 Dictionary, 有泛型优势, 单条读取速度较快, 容量利用更充分.
多线程程序中推荐使用 Hashtable, 默认的 Hashtable 允许单线程写入, 多线程读取, 对 Hashtable 进一步调用 Synchronized() 方法可以获得完全线程安全的类型. 而 Dictionary 非线程安全, 必须人为使用 lock 语句进行保护, 效率大减.
Dictionary 有按插入顺序排列数据的特性 (注: 但当调用 Remove() 删除过节点后顺序被打乱), 因此在需要体现顺序的情境中使用 Dictionary 能获得一定方便.

二 抽象类和接口的区别
抽象类:
抽象类可以继承自抽象类
特别注意的是,抽象类中定义的abstract方法必须在其子类中override,否则报错。这和虚拟virtual方法不同。
1、抽象类和接口都不能直接实例化。只能子类实例化
2、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
6、抽象方法只能申明,不能实现。抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。,接口变量必须指向实现所有接口方法的类对象。
7、抽象类里可以没有抽象方法,如果—个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可以继承接口,并且可多继承接口,但类只能单—继承。
11.接口可以通过匿名内部类实例化。接口是对动作的抽象,抽象类是对根源的抽象。抽象类表示的是,这个对象是什么。而接口表示的是,这个对象能做什么。



三 结构体和类的区别
C# 中的结构有以下特点:
结构可带有方法、字段、索引、属性、运算符方法和事件。
结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
与类不同,结构不能继承其他的结构或类。
结构不能作为其他结构或类的基础结构。
结构可实现一个或多个接口。
结构成员不能指定为 abstract、virtual 或 protected。
当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
相同点
2,两者都是container类型,这表示它们可以包含其他数据类型作为成员。
3,两者都拥有成员,包括:构造函数、方法、属性、字段、常量、枚举类型、事件、以及事件处理函数。
4,两者的成员都有其各自的存取范围。例如,可以将某一个成员声明为Public,而将另一个成员声明为Private。
5,两者都可实现接口。
6,两者都可公开一个默认属性,然而前提是这个属性至少要取得一个自变量。
7,两者都可声明和触发事件,而且两者都可以声明委托(Delegate)
区别:
1.结构体是值类型,类是引用类型。栈中保存的只是引用
2.结构体不能有默认构造函数,类有默认构造函数。
3.结构体可以自己添加有参构造函数,但是构造函数内必须声明所有变量,类没有限制。



4.结构体中不允许初始化字段,类可以。


5.结构体可以不使用new字段进行初始化,类不可以。


6.结构体没有析构函数,类有。



7.结构体不能有abstract,sealed修饰符,不能使用protected成员变量。结构不支持继承。类可以。


结构和类的适用场合分析:
1、当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
2、对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
3、在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。

四 c# 字段说明
1:sealed
修饰符表示密封用于类时,表示该类不能再被继承,不能和 abstract 同时使用,因为这两个修饰符在含义上互相排斥用于方法和属性时,表示该方法或属性不能再被重写,必须和 override 关键字一起使用,因为使用 C# sealed修饰符的方法或属性肯定是基类中相应的虚成员通常用于实现第三方类库时不想被客户端继承,或用于没有必要再继承的类以防止滥用继承造成层次结构体系混乱恰当的利用 C# sealed修饰符也可以提高一定的运行效率,因为不用考虑继承类会重写该成员。
当对一个类应用 sealed 修饰符时,此修饰符会阻止其他类从该类继承。类似于Java中final关键字。
在下面的示例中,类 B 从类 A 继承,但是任何类都不能从类 B 继承。
2. sealed 修饰方法或属性
能够允许类从基类继承,并防止它们重写特定的虚方法或虚属性。
1)sealed是对虚方法或虚属性,也就是同override一起使用,如果不是虚方法或虚属性会报出错误:cannot be sealed because it is not an override

2:C#中readonly的理解与使用const
const与readonly 很像,都是将变量声明为只读,且在变量初始化后就不可改写。那么,const与readonly 这两个修饰符到底区别在什么地方呢?
其实,这个牵扯出C#语言中两种不同的常量类型:静态常量(compile-time constants)和动态常量(runtime constants)
(1) const修饰的常量在声明的时候必须初始化;readonly修饰的常量则可以延迟到构造函数初始化
(2) const修饰的常量在编译期间就被解析,即常量值被替换成初始化的值;readonly修饰的常量则延迟到运行的时候。
(const修饰的常量无法在VS中打断点调试,它的值在编译期间就确定了。VS中打断点调试是在运行期间)
此外const常量既可以声明在类中也可以在函数体内,但是static readonly常量只能声明在类中。
(3) const默认就是静态的,而readonly如果设置成静态的就必须显示声明。
(4) const修饰的值的类型也有限制,它只能为下列类型之一(或能够转换为下列类型):sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、enum类型或引用类型。注意能够声明为const的引用类型只能为string或值为null的其他引用类型。readonly可以是任何类型。
class Age
    {
      //年纪添加了 readonly修饰符
      //1.可以在定义时对其赋值,一旦赋值就不可以再在他出赋值
      readonly int year=20;
      //2.在构造函数中赋值
      Age(int year)
      {
            this.year = year;
      }
      //3.但是不能在其他地方赋值
      void ChangeYear()
      {
            year = 1967; // 如果这里赋值了,会出现编译错误
      }
    }3:static

[*]仅包含静态成员。
[*]无法实例化。
[*]静态类的本质,是一个抽象的密封类,所以不能被继承,也不能被实例化。
[*]不能包含实例构造函数。
[*]如果一个类下面的所有成员,都需要被共享,那么可以把这个类定义为静态类。
静态变量是放在栈里面的,不受GC控制,只有程序关闭退出,内存才会被回收
五; C# 运算符 ++ , –
++i :当使用 ++i 时 i 会先自身增加1,然后才会赋值给 j ,所以 i 和 j 得到的结果是相同的 。(就是 i 先自增1 , 后赋值给 j)
i++:当使用 i++ 时 i 会先赋值给 j ,然后才会自身增加1,所以 i 和 j 得到的结果是不同的 ,i 比 j 大。(就是 i 先赋值给 j , 后自增1)
注意{减减 (- -)}: - -i(先自身减1后赋值),i - -(先赋值后自身减1)
六;C#GC
1..NET 中超过80%都是托管资源(非托管资源为插件,或其他类库)
2.。NET GC机制
(1) 并不能释放所有资源,他不能释放非托管资源
(2)GC并不是实时性的 者将会造成系统性能上的瓶颈和不确定性(所以有了IDisposable接口,内包含Dispose方法,使用这个方法可以释放非托管资源,使用using简化资源管理)
3. GC.Collect()强制经行垃圾回收 GC.colIlect (int 32)强制经行0代到指定代垃圾回收
GC Collect(int 32,GCCollectionMade)强制指定时间经行0代到指定代垃级回收。
4.GC注意事项:
(1)只管理内存
(2)在独立的线程中运行 去删除不在引用的内存
(3)每次运行压缩托管堆
(4)必须对非托管资源进行释放 可以通过定义Finalizer来保证资源释放
(5) Finalizer在对象不被应用的某个不确定时间内执行
5.GC算法
(1)压缩算法 挂起线程->找到root节点->拿到对象图->标记(未标记的是要进行回收的垃圾-回收掉为被标记的垃圾-)->堆压缩->修复指针(压缩后堆索引到栈的内存地址发生变化,需要将指针指到正确的位置)
(2)分代算法
条件:1大量的短期的生命周期的对象创建(例如局部变量 ) 较老的生命周期的对象例如全局的对象会或者静态的
      2 对于大部分的内存回收 要比 全部的进行垃圾回收的操作快(有些生命周期长的对象不需要每次都对其进行判断是否需要进行回收)
      3 .Net将heap分为三个代龄区域 Gen0 Gen1 Gen2
首先如何Geno区域的内存达到阈值则Gc处理将存活的对象存入 Gen1.
如果Gen1中的内存达到阈值经行GC将存活的对象存入Gen3
他们执行的频率为100: 10: 1.
6判断足否被标记为存活对象算法:
Finalization Queue(完成队列]   FreachableQueuel(可达队列)存储的足对象指针,
进性Mark 可分辨出那些是垃圾
通过:NET对象提供的Finalize方法
Finalization Queue 从堆中发现没执行Finalize合将他冲垃级椎里就出来放到可达队列执行Finalize在杀死
Unty内存管理,
自行内存回收,定时堆上回收拉圾
强制垃级回收
堆上内存不够进行内存回收。
Unity Gc在. net上E层UNITY自己又封装了一些算法他去不定时去调。net的Gc.,
原文链接:C#GC的简单概述_Unityyyds的博客-CSDN博客_c# gc
页: [1]
查看完整版本: Unity 面试基础知识汇总 (c#方向)