网站建设的公司工作室,宝安网站设计,免费收录链接网,做海报的软件前言
博客已经1年多没有更新了#xff0c;这一年主要在实习并准备秋招和春招#xff0c;目前已经上岸Unity客户端岗位#xff0c;现将去年校招遇到的一些面试题的事后整理分享出来。答案是笔者自己整理的不一定保证准确#xff0c;欢迎大家在评论区指出。
Unity客户端岗的…前言
博客已经1年多没有更新了这一年主要在实习并准备秋招和春招目前已经上岸Unity客户端岗位现将去年校招遇到的一些面试题的事后整理分享出来。答案是笔者自己整理的不一定保证准确欢迎大家在评论区指出。
Unity客户端岗的面试题目的方向一般为C#、Lua/热更、Unity、简单的图形学(客户端岗位也会问但比引擎岗简单的多)。由于篇幅有限本文仅整理了部分C#面试题。 一、请简述ArrayList和List的主要区别
非泛型集合ArrayList存在不安全类型ArrayList会把所有插入其中的数据都当做Object来处理装箱拆箱的操作费时List是泛型类功能跟ArrayList相似但不存在ArrayList的安全问题其必须为同一类型的值更为规范更适合日常的使用。 二、拆装箱
解释一下拆装箱
装箱(box)将值类型转换为引用类型的过程
拆箱(unbox)将引用类型转换为值类型的过程注只有装箱后才能拆箱
装箱可以隐式转换而拆箱必须显示转换
装箱过程
对值类型在堆中分配一个对象实例并将该值复制到新的对象中。按三步进行。 第一步新分配托管堆内存(大小为值类型实例大小加上一个方法表指针也称类型对象指针和一个SyncBlockIndex同步块索引)。 第二步将值类型的实例字段拷贝到新分配的内存中。 第三步返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
过程耗时耗空间 所以应尽量减少拆装箱次数 三、同步块和同步块索引
在程序运行时CLR 管理一个同步块数组。它是一个总共 32/64 位的多功能结构其中前 6 位的值提示访问者目前同步块索引的功能是什么高 6 位就像 6 个开关有的打开1有的关闭0不同位的打开和关闭有着不同的意义。
它的用处非常广泛例如线程同步和 GC 都会用到它它还会储存对象的哈希码。
同步块索引在线程同步中用来判断对象是被使用还是闲置。
默认的情况是同步块索引被赋予一个特殊的值,一般是-1此时对象没有被线程独占。当一个线程拿到对象并打算对其操作时它会检查对象的同步块索引。
如果索引的值为特殊值说明没有任何线程正在操作它此时这个线程获得它的操作权并在 CLR 的同步块数组中分配一个新的同步块给它并将该块的索引值写入实例的同步块索引值中。
这时如果有其他线程来访问该实例它就不能操作这个实例了因为它的同步块索引的值不为特殊值。
当独占的线程操作完之后同步块索引的值被重设回特殊值。 四、类型对象指针方法表指针
类型对象指针其实就是指向类型对象的引用。类型对象是反射的重要操作对象其实就是System.Type的实例对象类型对象中存有该类型的方法表和静态字段创建之后就不会再改变通过这个事实可以验证静态字段的全局性。
小栗子: People people new People(); 类型对象可以简单理解为System.Type pType new People.Class() 实际上是通过GetType获取的, pType就是类型对象指针引用其指向后面new的类型对象。
方法表就是记录类型所有的方法包括静态方法和实例方法。方法会在初次执行时经由 JIT 编译为机器码并将机器码存在内存之中获得一个入口地址。
此时方法表中的该方法指向一个 jmp 指令使得其可以跳跃到该入口地址。在下次调用该 方法时直接跳到入口地址无需再次编译。
注意所有对象内部均具有类型对象指针普通对象指向其对应的类型对象类型对象本身也是对象其内部的类型对象指针指向自身。每个对象均继承来自Object的GetType()方法其能获取此对象内部的类型对象指针其实也就是获取了对象对应的类型对象的引用进而能对类型对象加以操作。 五、C#内存布局和对齐
整个内存布局如下 同步块索引。 方法表指针指向方法表它位于类型对象中而类型对象一般位于同一个应用程序域的加载堆中。 类型所有父对象的实例成员静态成员存储在类型的类型对象中其中所有引用类型成员都分配 4 字节因为只需要分配地址。分配顺序不定CLR 会尽量消除对齐带来 的负面影响。 类型自己的实例成员静态成员存储在类型的类型对象中引用类型成员分配同上。
同步块索引和方法表指针类型对象指针只在引用类型中有 值类型中没有它们在32位机器上均占4字节在64位机器上均占8字节。
类型对象指针指向类型对象方法表指针指向方法表方法表是类型对象的最重要的部分所以这两者之间一般也不加以区分。----待核实
引用类型在栈中内存的分配
首先对内存分配从同步块索引开始它占据 4 个字节。
栈上的引用将指向同步块索引后边的部分称为偏移量也就是说同步块索引的地盘是从 -4 字节到 0。
然后方法表指针又名类型对象指针上场占据 4 个字节。
这 8 个字节是每个引用类型都一定会有的没有办法直接操作它们这会破坏类型安全性。
下面就轮到类型的实例字段静态字段在类型对象中。32 位机上任何对象占据的字节数都必须是 4 的倍数。 所以即使一个引用类型仅有一个 byte 类型的字段它也占据 12 字节实际占据9 字节3 字节被浪费而下一个引用类型不能从第 10 字节而必须从第 13 字节开始分配内存这称为内存的对齐alignment。
----这里解释一下一个引用类型中至少有8字节的同步块索引和类型对象指针8Byte-1 9 需要对齐。
而在 64 位机上任何对象占据的字节数都必须是 8 的倍数所以仅有一个 byte 类型的字段的引用类型占据 24 字节实际占据 17 字节7 字节被浪费。
默认情况下CLR 会智能地将可以合并到 4/8 字节的对象尽量合并到一起以免内存空间浪费除非你显式地阻止它。例如64 位机器上两个 int四个 short8 个 byte 可以合并到一起。 六、请简述sealed关键字用在类声明时与函数声明时的作用
sealed修饰的类为密封类类声明时可防止其他类继承此类在方法中声明则可防止派生类重写此方法。
类似于Java 的 final关键字。 七、请简述privatepublicprotectedinternal的区别
public对任何类和成员都公开无限制访问
private仅对本类公开
protected对该类和其派生类公开
internal只能在包含该类的程序集中访问该类 八、请描述Interface与抽象类之间的不同 接口所有的成员必须是public abstract类型的抽象类除抽象方法和属性不能是private abstract外没有限制字段不能抽象 接口定义的是一组对外的行为规范只能包含抽象方法和属性成员抽象类则可以具有普通方法和字段。 实现接口必须实现接口定义的全部的一组方法抽象类必须重写抽象方法对于虚方法可根据需求进行重写。 接口支持派生类多实现而抽象类只能被一个类继承继承则是单继承。 以上均是语法层面的不同 抽象类仍然是类具有类的性质可以拥有自己的成员方法对其进行封装只不过相对于普通类其可以声明抽象方法无法直接实例化其派生类必须重写其抽象方法。 而接口是一组对外的行为规范微软的自定义接口的名称结尾总是以able结尾证明接口就是来表述一类“我能做xxx”的事情就像为调用方和实现方签订一个契约实现放实现接口表明我能做xxx事调用方面向接口调用表明我找能做xxxx事的人利用好能极大降低耦合性。
顺便补充一下相同吧 两者均是面向抽象编程中经常使用的方法都是一种抽象形式。 二者都无法直接实例化必须由派生类去实现或继承。 二者都可以包含抽象未实现的抽象方法声明。 接口和类一样都是引用类型的 九、.Net与Mono的关系
这里介绍.NET .NETFramework .NETCore Mono C# CLR等的关系
首先是.NET
.NET是一个开源的开发者平台。其是一个平台类似于Java虚拟机一样可以运行其所支持语言下编写的任何程序其包括了运行时的环境和开发环境。
.NET Core .NET Framework Mono都是基于.NET提供的一系列组件的一些具体实现。
其中.NET Core和Mono 都支持跨平台开发而.NET Framework是针对Windows应用的一种.NET实现。 然后是FCL CIL CLR 首先介绍一下 .NET Standard
.Net Standard是一组通用的API所有的.Net实现平台都会实现它。它是一种标准规范。
一套.NET的实现包含FCL,CLR和可以把其它代码转化为可以在CLR中运行的CIL 的编辑器。FCLFramework Class Library---框架类库是对.Net Standard一套实现。CILCommon Intermediate Language---通用中间语言---有时也称IL就是C#,F#,VB等语言经过编译器生成的中间语言然后CLRCommon Language Runtime---公共语言运行时就是运行CIL的地方。 十、C#的堆和栈
首先交代清楚一些命名问题
栈全称堆栈容易混淆一般叫栈。
堆C中叫堆 在C#中其实叫托管堆。
托管堆托管堆不同于堆它是在堆中开辟的一块连续的内存空间并由CLR公共语言运行库(Common Language Runtime)管理当托管堆中满了会自动回收垃圾。 内存结构
栈栈的结构是后进先出先进后出栈地址从高往底分配内存分配连续
堆堆地址从低到高分配内存在物理地址上不连续 逻辑地址上连续(分页分段管理机制--- 页号段号连续 对应物理块不连续) 类型存放位置
值类型中方法体内部的局部变量存放在栈中类的成员字段存放在堆中
引用类型的对象存放在堆中引用类型的引用存放在栈上 回收问题
栈区存放函数的参数局部变量返回数据等值会自动释放编译器在声明变量时会自动生成释放的代码--超出作用域释放
堆区存放着引用类型的对象由CLR释放 优劣势
栈空间小速度快少一次寻址变量超出作用域编译器自动释放存放局部值类型和引用类型的引用。
堆空间大速度慢由CLR的GC程序释放存放引用类型的对象。 十一、结构体和类有何区别
语法层面的不同 关键字不同 结构体是struct 类是class关键字 结构体不能在声明字段时直接对字段进行初始化类可以。 ----类中声明字段赋值初始化在.Net生成程序集时会将其放入构造函数中所以才可以这么用 结构体和类均提供了默认的无参构造函数但是类一旦声明了构造函数有参无参均可,编辑器就不在默认提供构造函数但是结构体提供的默认构造函数一直都在。 源于上一条的缘故编译器一直会默认提供无参构造函数结构体不能显式地声明无参数的构造函数。 结构体的构造函数必须为所有字段进行赋值,若属性内get提供返回值则可不用对其在构造函数中赋值,若只是{get;set;}则还需要在构造函数中对其进行赋值。 结构体不能被继承继而也不能使用abstract、sealed、virtual、protected等关键字,结构体本身也不允许使用static关键字修饰但其内部字段方法等允许值得一提的是struct支持override用于重写其基类System.ValueType内部的方法。 结构体是值类型类是引用类型。所以结构体声明在局部变量时全部都存放在栈中类只有其引用会在栈上其对象都存放在堆中。 结构体在传值和传参时传递的都是值每次都会新开辟一段空间来存放二者互不影响值类型的特性。而类默认传递的是引用不会新开辟一段空间存放类的对象只是新增了一段空间存其对象的引用两个引用指向堆中同一块对象。 结构体不能定义析构函数---析构函数的名称为~类名组成其不能有参数修饰符也无返回值我们无法控制何时调用析构函数在GC垃圾回收器认为该对象不在使用回收时才会回调析构函数一般用于处理善后工作。
基于这些区别来谈一谈什么时候使用结构什么时候使用类 结构的局部变量存储在栈上空间小速度快效率高适合一些轻量级的对象如点颜色等只包含xyz,rgba等少数字段。而类的对象存储在堆上适合逻辑复杂的重量级对象使用。当存储1000个点等轻量级对象时如果使用类每个对象都会增加诸如同步块索引和类型对象指针的额外空间甚至占的比其本身都要多此时更适合使用结构。 要用双面的态度去看问题若栈的空间有限且结构有非常多的逻辑对象还是该首选类。 如果对象需要多次使用传递且都只需要使用同一份无需拷贝使用结构体会造成较大的浪费值类型的特性可以考虑使用类或者ref进行传递参数,如果就是想传拷贝也可直接使用结构体无需对类手动一遍遍复制。 如果只是以数据为主可以使用结构体如果想表现抽象和多级别的对象层次时类是最好的选择结构不支持继承注定其只是轻量级的类型。 十二、ref参数和out参数是什么有什么区别
ref和out参数的效果近似都是通过关键字找到定义在主函数里面的变量的内存地址并通过方法体内的逻辑对其加以操作改变由于是传递的地址同时也会对调用方的参数同时发生改变。不同点就是out输出参数无法传递进函数内部函数内部必须对其进行初始化。ref参数的值可以传递进函数中可直接在函数内部进行操作。总的来说就是ref有进有出out只出不进。 十三、C#的委托是什么有什么用
C#的委托类似于C或C中的函数的指针。委托就是一种指向某个方法的引用类型变量。
委托对象也就是将方法包装成了对象通过调用委托对象间接调用方法实现事件和回调机制。
委托是引用类型使用方法创建委托对象必须符合委托的原型。
作用 将方法做为参数进行传递可以将一个方法的执行代码注入到另一个方法中。 实现回调机制且比接口更加灵活。 实现任意方法的异步调用。 事件实现的基础。
十四、委托的多播和异步调用 多播委托本质上是委托的一种特殊形式以委托链的形式存在 多播委托绑定的方法有返回值调用委托对象时默认只会收到最后一个方法的值 想要获取每个方法的返回值调用委托GetInvocationList()方法获取委托链然后循环调用DynamicInvoke方法即动态调用每个方法可获取返回值。
多播委托原理 委托对象绑定()多个方法时本质调用Delegate.Combine(原对象,新对象)方法。 Combine方法内部创建新委托链(Delegate[]),再创建新委托对象存入_invocationList字段中。 委托对象移除(-)某个方法时本质调用Delegate.Remove(原对象,删除对象)方法。 Remove方法内部删除匹配的委托(判断_target与_methodPtr)。 如果删除后委托链中只剩下一个委托则返回该委托否则再新创建一个委托对象初始化新委托链。
委托实现方法的异步调用
同步调用方法排队调用前一个方法执行时后一个方法等待它的结束后才能启动。
异步调用方法不排队调用后一个方法不必等待它的结束就可启动异步调用的方法是创建了一个新线程来工作的与后一个方法所在不同的线程各自独立互不影响。
Framework中的任何方法都可以通过委托异步调用。(BeginInvoke)
步骤 为需要异步调用的方法定义一个相应的委托。 创建该委托的引用指向需要异步调用的方法。 使用委托类型的BeginInvoke方法开始异步调用。
a) BeginInvoke中的参数IAsyncCallback 表示异步调用的方法结束时执行的回调方法往往用回调方法来处理异步的结果。
b) BeginInvoke中的参数object 表示在回调方法中需要用到的数据该数据被封装在IAsyncResult的AsyncState属性中。 如方法有返回值则需要调用EndInvoke取得返回值。 十五、请简述GC垃圾回收产生的原因并描述如何避免
GC--Garbage Collector(垃圾回收器
GC 垃圾回收机制为了避免内存的溢出将定期回收没有有效引用的对象的内存。
其工作在.NET提供的CLR,JAVA提供的VM(Virtual Machine虚拟机)上对内存进行管理。工作原理就是以应用程序的root为基础遍历应用程序在Heap(堆)上动态分配的所有对象(静态对象随程序的结束才释放)通过识别它们是否被引用来确定哪些对象是已经死亡的哪些仍需要被使用。已经不在被应用程序的root或者其它对象所引用的对象就是已经死亡的对象也就是所谓的垃圾需要被回收这就是GC的工作原理。.NET和Java都是利用Mark Sweep标记-清扫算法实现的GC。
GC带来的好处 提高了软件的抽象度 ---让软件开发更注重上层功能而非底层具体实现 程序员可以将精力集中在实际的问题上而不用分心来管理内存问题 可以使接口更加清晰减少模块间的耦合 ---- 使用其它模块的功能时无需关心还要回收其它模块的对象。 大大减少了内存人为管理不当所带来的BUG。 使内存管理更加高效。
总的说来就是GC可以使程序员可以从复杂的内存问题中摆脱出来从而提高了软件开发的速度、质量和安全性。 GC的执行时机
GC的基本假定 最近分配内存空间的对象最有可能被释放一般在方法内部声明的对象在方法体结束时其引用销毁后其对应堆上的对象也会因无有效引用而被回收所以搜索最近分配的对象集合有助于尽可能多释放内存空间。 生命周期长的对象释放可能性最小即经过几轮垃圾回收后对象仍然存在搜索其需要大量工作且只能释放一小部分空间。例如一些全局的管理器对象类型对象等等。
C#中的回收器是分代的垃圾回收器Gererational GarbageCollector) 它将分配的对象分为3个类别或代。可用GC.GetGeneration方法返回任意作为参数的对象当前所处的代最近被分配内存的对象被放置于第0代因为第0代很小小到足以放进处理器的二级L2)缓存所以它能够提供对对象的快速存取。经过一轮垃圾回收后仍然保留在第0代中的对象被移进第1代中再经过一轮垃圾内存回收后仍然保留在第1代中的对象则被移进第2代中第2代中包含了生存期较长的对象。
当第0代中没有可以分配的有效内存时就触发了第0代中的一轮垃圾回收它将删除那些不再被引用的对象并将当前正在使用的对象移至第1代。而当第0代垃圾回收后依然不能请求到充足的内存时就启动第1代垃圾回收。如果对各代都进行了垃圾回收后仍没有可用的内存就会引发一个OutOfMemoryException异常。
执行时机 第一代空间不足执行GC 调用了GC.Collect() 系统内存资源不足 C#垃圾回收时如果某个对象已经被回收了后续却又需要调用它会发生什么问题如何解决这个问题你会如何设计垃圾回收机制来解决这个问题
会抛出ObjectDisposedException的异常。
如果对象已经被回收想要再次使用它无非就是重新实例化。
一般来讲 需要从两个维度考虑 空间和时间如果存入一个较大的对象其占用内存很多而且创建也比较耗时如果一直长期不手动释放且保持它的引用就会十分占用内存如果用完就手动释放后如果后续需要频繁使用那就需要频繁创建销毁对运行速度也会造成很大的影响。
既然不能两全其美找到一个全局最优解就只能局部最优比如内存空间不足也就是触发GC的时候我去释放它不触发GC我就不去释放普通的强引用肯定实现不了这种功能C#的一个弱引用机制可以实现此功能。
多介绍一点的话C#的弱引用还具备一个追踪复活机制有定义析构函数的对象第一次回收时会加入到一个队列执行析构函数并不会真回收执行完析构函数后第二次才会真正回收。在执行析构函数时如果又被引用了就会实现复活。 非托管资源需要实现IDisposable接口和Dispose方法 非托管资源对象手动调用Close或者Dispose或使用using()会调用Dispose方法。 析构函数一般用于清理非托管资源 代Generation)引入的原因主要是为了提高性能Performance)以避免收集整个堆Heap。一个基于代的垃圾回收器做出了如下几点假设 1、对象越新生存期越短 2、对象越老生存期越长 3、回收堆的一部分速度快于回收整个堆。 .NET的垃圾收集器将对象分为三代Generation0,Generation1,Generation2。不同的代里面的内容如下 1、G0 小对象(Size85000Byte)新分配的小于85000字节的对象。 2、G1:在GC中幸存下来的G0对象 3、G2:大对象(Size85000Byte);在GC中幸存下来的G1对象
object o new Byte[85000]; //large object
Console.WriteLine(GC.GetGeneration(o)); //output is 2,not 0 这里必须知道CLR要求所有的资源都从托管堆managed heap分配CLR会管理两种类型的堆小对象堆small object heapSOH和大对象堆large object heapLOH其中所有大于85000byte的内存分配都会在LOH上进行。 代收集规则当一个代N被收集以后在这个代里的幸存下来的对象会被标记为N1代的对象。GC对不同代的对象执行不同的检查策略以优化性能。每个GC周期都会检查第0代对象。大约1/10的GC周期检查第0代和第1代对象。大约1/100的GC周期检查所有的对象。 GC的工作流程
标记(Mark) → 计划(Plan) → 清理(Sweep) → 引用更新(Relocate) → 压缩(Compact) Mark所有对象都初始为Dead(无有效引用)找到所有的GC Root遍历所有Root及其子节点将遍历到的节点标记为Live(有效引用)不包含弱引用。 Plan根据Live标记判断是否需要压缩根据特定的算法决策。 Sweep回收没有有效引用的Dead对象并将回收的空间加入到可用内存链表中。 Relocate Compact设置Forwarding指针先更新引用再移动对象进行压缩。
GC Root 全局变量静态变量 栈中局部变量参数变量 寄存器中的变量 均必须是引用类型变量 标记-清除
传统GC无需进行压缩而是维护一个空闲块链表分配时有三种算法 均为O(n) First-fit: 找到大于等于size的块立即返回 Best-fit:遍历整个空闲列表返回大于等于size的最小分块 Worst-fit:遍历整个空闲列表找到最大的分块然后切成两部分一部分size大小并将该部分返回。 可以设置多条链表1字节2字节3字节---100字节这种内存碎片和分配速度慢都可以得到解决。 标记-压缩 先进行一次标记-清除算法搜索两次堆 设置Forwarding指针 搜索1次堆 void set_forwarding_ptr() {scan new_address $heap_start;while (scan $heap_end)if (scan.mark TRUE)scan.forwarding new_address;new_address scan.size;scan scan.size;} 先更新引用更新GC Root进而更新其子节点又搜索一次 adjust_ptr(){for(r : $roots)*r (*r).forwarding;
scan $heap_start;while(scan $heap_end)if(scan.mark TRUE)for(child : children(scan))*child (*child).forwarding;scan scan.size} 先更新后移动是为了防止移动对象覆盖了还未开始移动对象空间的情况。 移动对象进行压缩最后一次搜索堆 move_obj(){scan $free $heap_start;while(scan $heap_end)if(scan.mark TRUE)new_address scan.forwarding;copy_data(new_address, scan, scan.size);new_address.forwarding NULL;new_address.mark FALSE;$free new_address.size;scan scan.size;}
清除时间复杂度O(n) 光压缩就需要3次搜索堆 Two Finger优化算法
两个指针左指针找空闲位置右指针找非空闲位置并放入左指针处要求所有块大小相等限制较大。 三色标记增量清除算法的时间复杂度是多少如何计算这个复杂度
三色标记算法
最大的好处减少了STW的时间可以异步效率高。
三色标记算法不使用STW(即在GC时暂停整个程序)而是使用了并发标记在GC标记时程序仍在运行会有以下两种情况 错标浮动垃圾原来不是垃圾的变为了垃圾 错杀本来已经当做垃圾的又得到了有效引用 白色没有检查或者为检查过没有引用指向它
灰色自身被检查了成员还未检查当前操作节点
黑色自身和成员已经被检查了 错标问题 错标问题影响不大在下次GC时也会随之清除。 错杀会将应该存在的对象回收影响程序正确性需要解决。
var G objE.fieldG; // 1.读
objE.fieldG null; // 2.写
objD.fieldG G; // 3.写
错杀的前提条件有两个 黑色对象指向了白色对象 本来指向这个白色对象的灰色对象断开了对它的连接。
只需解决任意一个即可避免错杀。
Function1:写屏障和 SATB Snapshot At The Beginning原始快照 G1 所谓写屏障有点像AOP就是在写的操作后加入一个处理函数进行增强功能 SATB的思想 尝试保留开始时的对象图即原始快照Snapshot At The BeginningSATB当某个时刻 的GC Roots确定后当时的对象图就已经确定了。 SATB在每次写入时都会保存记录旧值以保证开始的对象图不受到影响。删除旧引用标记保留增加新引用无视。 值得一提的是扫描所有GC Roots 这个操作即初始标记通常是需要STW的否则有可能永远都扫不完因为并发期间可能增加新的GC Roots。 SATB破坏了条件2灰色对象断开了对白色对象的引用 Function2: 写屏障 增量更新 CMS 思想当有新引用插入进来时记录下新的引用对象即增量更新。 记录的新的引用对象会作为灰色节点再进行遍历即可注意集合重新标记的遍历过程可能会一直增加可能需要STW。 破坏了条件1黑色对象增加对白色对象的引用 Function3: 读屏障 ZGC 思想保守方法在前提条件中黑色对象 引用了该 白色对象引用的前提一定会先获取到白色此时读屏障就可以发挥作用进行记录了。 GC时间复杂度的计算 初始化所有节点置位白色 O(n) n为节点数 标记阶段从每个根节点下开始遍历所有节点 O(n) 增量阶段 由于used判重的存在 仍为O(n) 清除阶段清除白色的,可能是直接清除串成链表就是O(m)如果是压缩清除涉及到黑色的移动就还是O(n’) 此时的n为整个堆 GC的工作区域
局部值类型和引用类型的引用存放在栈里先进后出堆栈是从高地址往低地址分配内存。基本上栈用于存储临时的数据(局部值类型和引用)当一个方法执行完毕后立刻自动清除---当变量离开其作用域时进行清楚比堆的回收更简单和高效。
引用类型的对象分配在托管堆(Managed Heap)上是从低地址往高地址分配内存。声明一个引用变量在栈上保存。当使用new等方式创建对象时会把对象的地址存储在这个变量里托管堆中的对象是由GC进行回收的被回收的对象会回调其析构函数进行善后处理。
搞清楚GC的工作原理和范围其对性能的消耗是不能无视的当产生大量GC时势必会影响软件的性能
如何减少GC 减少new等创建对象的次数。 使用公用静态变量 static 或者常量const但要慎用因为静态成员和常量的生命周期是整个应用程序。 多使用StringBuilder代替直接使用String进行拼接字符串string可能会造成内存泄漏---内存泄漏因为疏忽程序未能释放掉不再使用的内存的情况。 解释一下直接利用string的拼接字符串每个string都会生成一个新的内存空间不及时回收会产生大量内存碎片GC回收大量string对象也会对性能造成不小的浪费而使用StringBuilder只创建一次而不会频繁创建。 使用对象池ObjectPool能复用就复用减少频繁的创建和销毁回收 十六、字符串暂存池缓冲池
字符串是引用类型其有一个特性就是字符串的不变性----直接对字符串进行赋新的值不会改变原来字符串在堆中的对象而是新建一个对象再将引用指向新建的字符串对象。
程序中会存在大量的字符串对象如果每次都创建一个字符串对象会比较浪费内存、性能低所以CLR在底层对其进行了优化才有了字符串缓冲池的用法。其会对已经存在缓冲区的字符串对象进行重用。 十七、反射的实现原理
定义
反射就是动态获取 程序集动态获取类型信息 , 动态创建对象, 动态访问成员信息的过程。
作用
在编译时无法了解类型在运行时动态获取类型信息动态创建对象和访问成员。
流程 获取数据类型 动态创建对象 查看类型信息了解本身信息成员信息
反射的实现原理
反射的实现原理就是运行时审查元数据并获取元数据里面关于它的类型信息。元数据是程序集的一部分就是一堆表里面存有了该程序集下的所有类的信息极其成员属性和方法的信息。 十八、程序集的概念 程序集是代码进行编译时的一个逻辑单元把相关的代码和类型进行组合然后生成可移植可执行文件PE文件例如可执行文件.exe和类库文件.dll 任何基于.NET的代码在编译时都至少存在一个程序集 程序集和命名空间没有必然联系一个程序集可能有多个命名空间一个命名空间也可能分布在多个程序集。 二十下列代码在运行中会发生什么问题如何避免
List ls new List(new int[] { 1, 2, 3, 4, 5 }); foreach (int item in ls) { Console.WriteLine(item * item); ls.Remove(item); }
产生运行时错误在 ls.Remove(item)这行因为foreach是只读的。不能一边遍历一边修改。 二十一、简述StringBuilder和string的区别
string是字符串常量。---内部是常量字符数组不可修改
StringBuffer是字符串变量 线程安全。---内部是普通字符数组可以更改比Builder多上了锁,线程安全
StringBuilder是字符串变量线程不安全。---内部是普通字符数组可以更改
String类型是个不可变的对象当每次对String进行改变时都需要生成一个新的String对象然后将指针指向一个新的对象如果在一个循环里面不断的改变一个对象就要不断的生成新的对象所以效率很低建议在不断更改String对象的地方不要使用String类型。
StringBuilder对象在做字符串连接操作时是在原来的字符串变量上进行修改只要不超过初始定义的长度就不会新增若超过则增大为原来两倍用来存取。一般情况下更建议使用StringBuilder性能会更好。 二十三、C#中四种访问修饰符是哪些各有什么区别
public对任何类和成员都公开无限制访问
private仅对本类公开
protected对该类和其派生类公开
internal只能在包含该类的程序集中访问该类
组合修饰符
protected protected internal内部保护只能被本程序集内的所有类和这些类的派生类所存取 二十四、Heap与Stack有何区别 heap是堆stack是栈其都是程序运行期间在内存中开辟的两块区域。 stack存取速度快效率高但空间有限heap的空间较大但存取速度较慢。 stack的空间由操作系统自动分配和释放heap的空间是手动申请和释放的在C# Java等语言中由GC自动回收无需手动释放heap常用new关键字来分配。 栈中一般存有函数的局部值类型变量和引用类型对象的引用堆中一般存有引用类型的对象。 二十五、值类型和引用类型有何区别 局部变量下的值类型的数据存储在内存的栈中引用类型的数据存储在内存的堆中而栈中只存放堆中对象的地址也就是引用。 值类型相较引用类型一般少了一步寻址的操作且如果在栈中读取速度相较堆中比较块所以一般值类型的操作速度比引用类型的要快。 值类型的变量直接存放实际的数据而引用类型的变量存放的则是数据的地址即对象的引用。 值类型继承自System.ValueType引用类型继承自System.Object 局部值类型变量在栈中的内存分配是自动释放而引用类型的对象在堆中由CLR提供的GC来释放 值类型没有同步块索引和类型对象指针等额外空间所占空间即为数据的实际大小而引用类型不仅有额外的空间消耗还有对齐等影响空间相较值类型利用率较低。 二十六、什么是里氏替换元则
里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。简单来说就是子类对象可以赋值给基类对象隐式转换而基类对象不可以直接赋值给子类对象需要显示转换。
详细限制子类要拥有父类的所有功能。子类在重写父类方法时尽量选择扩展重写防止改变了功能。 二十八、概述序列化
所谓的序列化就是把一个对象信息转化为一个可以持久存储的数据形式经过转化后就可以方便的保存和传输了因此序列化主要用于平台之间的通讯。由序列化我们可以反推出所谓的反序列化就是将持久存储的数据还原为对象。
举例JSON XML 二进制文件的转换 都是序列化和反序列化的体现在不同语言和平台下都能进行解析和转换。
根据Unity的官方定义序列化就是将数据结构或对象状态转换成可供Unity保存和随后重建的自动化处理过程。
Unity中有很多自动序列化的引擎功能AB包场景ScriptableObejct都会。 一些Unity中对类和变量序列化的关键字
SerializeField : 表示变量可被序列化。众所周知公有变量可以在检视面板中看到并编辑而私有和保护变量不行。SerializeField与privateprotected结合使用可以达到让脚本的变量在检视面板里可视化编辑同时保持它的私有性的目的。
HideInInspector : 将原本显示在检视面板上的序列化值隐藏起来。
NonSerialized :通过此方法可以将一个公有变量不序列化并且不显示在检视面板中。
Serializable用在类的前面表示该类可被序列化。 二十九、概述c#中代理和事件
委托
代理就是委托类似于C/C的函数指针其实就是指向某个方法的引用。委托的使用实际就是将方法封装成一个委托对象通过调用委托对象间接调用方法实现回调机制。
委托的作用 将方法做为参数进行传递可以将一个方法的执行代码注入到另一个方法中。 实现回调且比接口更加灵活。 实现任意方法的异步调用。---本质创建新线程 事件实现的基础。
事件
事件就是当某一对象事件源达到某种条件或者发生某种改变时将消息及时通知到注册了事件的相关对象。
注册了事件的对象都是观察者和订阅者事件源就是被观察者和发布者。
作用
和委托基本一致
着重区别
委托就是一个普通的类可以实例化可以被外部调用可以在类的外部触发。而事件就是一种特殊的委托其在委托delegate关键字前加event相当于为委托施加了保护将委托私有化外部不允许直接修改委托实例不能用只允许在外部进行注册和注销而且也只能在定义事件的类的内部进行触发不允许在外部触发。
总的来讲event关键字有助于提高类的封装性物理隔绝代码耦合迫使类设计更追求高内聚。 三十一、介绍一下C#中的泛型模板
泛型是一种可以允许延迟编写类或方法中的变量的数据类型的语法。说白了的话泛型是一种延迟声明即定义的时候没有指定具体的参数类型把参数类型的声明推迟到了调用的时候才指定参数类型。 延迟思想在程序架构设计的时候很受欢迎反射等技术都体现了延迟的思想。
使用泛型是一种增强程序功能的技术具体表现在以下几个方面 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。 您可以创建泛型集合类常用的就是List集合。 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。 您可以对泛型类进行约束以访问特定数据类型的方法。 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
一般使用Object也能实现类似泛型的功能但需要显示的转换容易出错且可能发生拆装箱的情况效率不如使用泛型进行隐式的转换。 三十二、迭代器
迭代器是一种行为模式在.NET中迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装
迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是arraylistlinked list或者是其他什么序列结构。抽象了序列的类型。
在.NET中迭代器主要实现了MoveNext方法和Current属性来进行对序列的遍历获取。诸如foreach就是在迭代器的基础上实现了对序列的循环遍历不停的MoveNext然后Current取值一直到迭代器的末尾。 三十三、c#和unity中用的c#的差距
c#是由微软开发的属于所谓的面向对象编程语言。它是用来使用. net framework开发应用程序的这个平台提供了广泛的类库、编程接口和实用程序。
然而Unity将该语言与.net框架的开源变种Mono框架结合使用这意味着c#应用程序也可以在非microsoft系统上运行(也称为Mono项目)。
原始C#是微软在.NET平台下开发的专为PC端开发软件的一门语言
Unity使用的是mono版的C#支持跨平台语法层面几乎没有区别
三十四、Dictionary底层原理 先通过key找到hash_key % length去buckets中找到entries的索引这个索引就是这个key值的头结点在利用链表进行增删改查即可。
详细的源码解析可见博主的另一篇博客C# Dictionary源码解析_c# 源码_窗外听轩雨的博客-CSDN博客 三十五、什么是虚函数
在基类中由Virtual修饰的函数在子类中Override进行重写这就是虚函数。 重写后的函数仍是虚函数这意味着它可以继续被子类重写 Virtual虚函数编辑器下的调用流程 当调用一个函数时系统会优先检查声明类的函数若其不为虚函数则直接执行 若其为虚函数则转去检查 实例对象类的函数如果实例类对应函数用Override修饰则直接调用实例对象的相关方法 若实例类没有用Override修饰则继续向上查找其父类直到找到Override或者回到了声明类本身执行相关方法。 Virtual是实现多态性的重要手段一般利用接口或抽象类定义抽象方法可以将方法的实现延迟到子类中进行。Virtual的定义也可以让子类重写父类的方法进而实现多态 Virtual可以在函数中实现一部分的共性让子类重写时保留并加入自己的特性部分。 Virtual不强制要求重写当想要某些函数子类可以选择性的实现也可以利用虚函数实现比如状态机并不是所有状态都需要进入正在离开的逻辑就可以用Virtual修饰它们。 三十六、谈一谈重载和重写的区别 重载发生在一类内部同一个类中同名但参数列表不同的函数在调用时根据传参情况决定调用函数的过程。 重写发生在具体父子关系的不同类中父类可以通过声明abstract或virtual方法将方法的具体逻辑延迟到子类中实现这种延迟思想对面向对象的设计十分重要。
三十七、面向组件式和继承式比较而言分析一下优缺点。
传统的OO思想主要是运用了抽象继承多态来实现同一类对象所衍生出的不同功能的子对象。对象之间继承存在层级关系。
而面向组件的ECS式变成则粒度更小所有Entity实体都是平级不存在继承关系Component组件也是一种Entity多个功能组件排列组合就能实现多种多样的实体需要注意的是Entity只负责属性的定义System负责定义行为。
面向对象具有里程碑意义是现在最主流的思想已经站在了巨人的肩膀上拥有很多指导原则设计模式等指导程序员写出优美易于维护的项目。且采用继承多态层次清晰但如果使用不当很容易造成项目难以重构牵一发而动全身的状况。
面向组件开发定义的一套规范让每个独立的功能作为一个组件使用时需要将其组合起来这种组件式的思想做到了很好的高内聚灵活性很强但带来高灵活性的同时由于组件可以随意组合其复杂度将会大大提高难以理解。 三十八、队列在实际开发中的应用 网络通信消息队列比如Actor消息队列 寻路问题迷宫问题一类例如普通无差别4个方向寻路也就是BFS需要用到队列。如果是A*这种启发式寻路就需要用到优先队列当然这跟队列就没关系了这是个二叉堆。 符合先来先做的任务队列这个就很广泛了任务可以五花八门比比如就诊医院排队一类的但主要是先进先出这一条。 三十九、栈在实际开发中的应用 语法检查利用栈逆序输出的特点判断成对出现符号的合法性是很方便的。 撤销和恢复典型的命令模式中就需要用到两个栈来进行命令的执行撤销和恢复操作。 UI栈的使用多重界面不断深入后返回时的顺序是后进先显示正符合栈的性质UI框架一般都会为有这些性质的UI界面创建栈。 替代递归递归的使用能使得一个很复杂的功能只需要短短几行就能实现但递归需要不断为函数开辟空间效率低下递归的本质就是将函数压栈出栈的过程可以用栈代替递归提高代码执行效率。 四十、Volatile关键字的作用
在C#语言编写的程序中有时为了提高程序的运行效率编译器会自动对其进行优化把经常被访问的变量缓存起来程序在读取这个变量时有可能会直接从缓存例如寄存器中来读取这个值而不是去内存中读取。这样做的一个好处是提高了程序的运行效率但当遇到多线程编程时变量的值可能因为别的线程而改变了而缓存中的值不会改变从而造成应用程序读取的值和实际的值不一致。
volatile是一个类型修饰符它用来修饰被不同线程访问和修改的变量。被volatile类型定义的变量系统每次用到它时都是直接从对应的内存当中提取而不会利用缓存。 在使用了volatile修饰成员变量后所有线程在任何时候所看到的变量都是相同的。
四十一、负数为什么要补码
计算机中加法器不会进行减法而是进行模运算在4位计算机0-15中
5-3 其实就是计算 5 13 即减一个数是加上其补数
而-3的的补码正好就是13 所以补码就相当于补数
文章转载自: http://www.morning.bktzr.cn.gov.cn.bktzr.cn http://www.morning.tmbfz.cn.gov.cn.tmbfz.cn http://www.morning.xbmwm.cn.gov.cn.xbmwm.cn http://www.morning.zlxkp.cn.gov.cn.zlxkp.cn http://www.morning.xknsn.cn.gov.cn.xknsn.cn http://www.morning.lkrmp.cn.gov.cn.lkrmp.cn http://www.morning.ryztl.cn.gov.cn.ryztl.cn http://www.morning.bpmtl.cn.gov.cn.bpmtl.cn http://www.morning.bmrqz.cn.gov.cn.bmrqz.cn http://www.morning.lhptg.cn.gov.cn.lhptg.cn http://www.morning.bxch.cn.gov.cn.bxch.cn http://www.morning.jsmyw.cn.gov.cn.jsmyw.cn http://www.morning.fwkjp.cn.gov.cn.fwkjp.cn http://www.morning.sfzwm.cn.gov.cn.sfzwm.cn http://www.morning.pwwjs.cn.gov.cn.pwwjs.cn http://www.morning.hpggl.cn.gov.cn.hpggl.cn http://www.morning.fhntj.cn.gov.cn.fhntj.cn http://www.morning.rxnxl.cn.gov.cn.rxnxl.cn http://www.morning.khclr.cn.gov.cn.khclr.cn http://www.morning.haolipu.com.gov.cn.haolipu.com http://www.morning.yhjrc.cn.gov.cn.yhjrc.cn http://www.morning.sjbpg.cn.gov.cn.sjbpg.cn http://www.morning.mmtjk.cn.gov.cn.mmtjk.cn http://www.morning.qkrzn.cn.gov.cn.qkrzn.cn http://www.morning.rxxdk.cn.gov.cn.rxxdk.cn http://www.morning.rqlzz.cn.gov.cn.rqlzz.cn http://www.morning.huarma.com.gov.cn.huarma.com http://www.morning.bzgpj.cn.gov.cn.bzgpj.cn http://www.morning.qwmsq.cn.gov.cn.qwmsq.cn http://www.morning.wlqbr.cn.gov.cn.wlqbr.cn http://www.morning.jhgxh.cn.gov.cn.jhgxh.cn http://www.morning.dkbgg.cn.gov.cn.dkbgg.cn http://www.morning.yrskc.cn.gov.cn.yrskc.cn http://www.morning.ylyzk.cn.gov.cn.ylyzk.cn http://www.morning.zglrl.cn.gov.cn.zglrl.cn http://www.morning.mqwdh.cn.gov.cn.mqwdh.cn http://www.morning.snccl.cn.gov.cn.snccl.cn http://www.morning.kjdxh.cn.gov.cn.kjdxh.cn http://www.morning.bwttj.cn.gov.cn.bwttj.cn http://www.morning.synlt.cn.gov.cn.synlt.cn http://www.morning.jtkfm.cn.gov.cn.jtkfm.cn http://www.morning.pdghl.cn.gov.cn.pdghl.cn http://www.morning.xykst.cn.gov.cn.xykst.cn http://www.morning.mdlqf.cn.gov.cn.mdlqf.cn http://www.morning.jgnjl.cn.gov.cn.jgnjl.cn http://www.morning.knmp.cn.gov.cn.knmp.cn http://www.morning.txfzt.cn.gov.cn.txfzt.cn http://www.morning.hlzpb.cn.gov.cn.hlzpb.cn http://www.morning.yfmxn.cn.gov.cn.yfmxn.cn http://www.morning.lgwjh.cn.gov.cn.lgwjh.cn http://www.morning.bzlgb.cn.gov.cn.bzlgb.cn http://www.morning.lfdrq.cn.gov.cn.lfdrq.cn http://www.morning.c7629.cn.gov.cn.c7629.cn http://www.morning.51meihou.cn.gov.cn.51meihou.cn http://www.morning.mqfkd.cn.gov.cn.mqfkd.cn http://www.morning.nchsz.cn.gov.cn.nchsz.cn http://www.morning.kpygy.cn.gov.cn.kpygy.cn http://www.morning.frqtc.cn.gov.cn.frqtc.cn http://www.morning.mflhr.cn.gov.cn.mflhr.cn http://www.morning.xwgbr.cn.gov.cn.xwgbr.cn http://www.morning.xrsqb.cn.gov.cn.xrsqb.cn http://www.morning.pbsqr.cn.gov.cn.pbsqr.cn http://www.morning.pghry.cn.gov.cn.pghry.cn http://www.morning.lpppg.cn.gov.cn.lpppg.cn http://www.morning.bqmhm.cn.gov.cn.bqmhm.cn http://www.morning.jpjpb.cn.gov.cn.jpjpb.cn http://www.morning.rzmzm.cn.gov.cn.rzmzm.cn http://www.morning.lnmby.cn.gov.cn.lnmby.cn http://www.morning.mldrd.cn.gov.cn.mldrd.cn http://www.morning.ydrfl.cn.gov.cn.ydrfl.cn http://www.morning.qbpqw.cn.gov.cn.qbpqw.cn http://www.morning.shawls.com.cn.gov.cn.shawls.com.cn http://www.morning.pzbjy.cn.gov.cn.pzbjy.cn http://www.morning.lqjpb.cn.gov.cn.lqjpb.cn http://www.morning.qnbgh.cn.gov.cn.qnbgh.cn http://www.morning.pswqx.cn.gov.cn.pswqx.cn http://www.morning.jqwpw.cn.gov.cn.jqwpw.cn http://www.morning.qyxnf.cn.gov.cn.qyxnf.cn http://www.morning.yrjkz.cn.gov.cn.yrjkz.cn http://www.morning.rpljf.cn.gov.cn.rpljf.cn