Unity C# 游戏客户端面试复习

为了游戏客户端而自己准备的面试复习资料,若有错误欢迎指正

C# 装箱与拆箱操作

装箱:值类型=>引用类型
拆箱:引用类型=>值类型

  • .Net中,数据类型划分为值类型与引用类型,由此内存分配划分为两种方式:
  • 栈(值类型一般在栈中分配,但并不是所有值类型都在栈中)
  • 堆(引用类型在堆中分配,托管堆用于垃圾回收GC)
  • 装箱过程使用的是值类型的一个副本,因此对原有值类型的改变不会影响装箱后的对象,拆箱过程会拷贝值类型,同样原有引用类型的改变不会影响拆箱后的值类型

装箱步骤

  • 首先从托管堆中为新生成的引用对象分配内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex),然后将值类型的数据拷贝到刚刚分配的内存中
  • 返回托管堆中新分配的对象地址,其中指向对象的引用其中分配内存与拷贝数据均比较花费时间

拆箱步骤

  • 首先获取托管堆中属于值类型部分字段的地址,进行严格意义上的拆箱
  • 将引用对象中的值拷贝到位于线程堆栈上的值类型实例中
  • 严格意义上的拆箱不影响性能,但是随后的数据拷贝过程会影响性能

装箱、拆箱的使用场景

  1. 一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。
  2. 非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。
  3. 字符串转成值类型的时候会出现拆箱(很普遍)

装箱/拆箱 避免方法:使用重载函数或是泛型来避免

C# 垃圾回收主动触发模式

Unity的自动内存管理

  • Unity 可以访问两个内存池:栈(stack)和堆(heap)(also known as the managed heap)。栈用于短期存储的小数据,堆用于较长时间存储的较大的数据块。
  • 当创建变量的时候,Unity 会从栈或堆中请求一个内存块。
  • 只要变量值作用域内(in scope(仍可被代码访问) ),分配给它的内存仍然会保留。我们说的内存被分配( allocated )了。我们可以这样描述,保存在栈内存的叫做栈对象(object on the stack),保存在堆内存的叫做堆对象(object on the heap)。
  • 当变量不在作用域范围内了,这块内存就不再需要了,可以被创建它的那个内存池回收。当内存被回收到池,我们说内存被重新分配(deallocated)了。只要变量的引用超出了作用域,栈内存就会被重新分配,而堆的内存不同,这种情况下内存不会重新分配即使变量的引用超出了作用域。
  • 垃圾回收器标识和回收没有使用的堆内存。垃圾回收器定期清理堆内存。

栈内存:

  • 栈工作类似栈数据类型:这种情况下的内存更像一个简单的集合,元素按严格的顺序添加和移除。这种简单性和严格性使它如此快速:当变量存储在栈时,它的内存只从栈顶分配。当一个栈变量离开作用域,存储变量的内存会立即被栈重用。
  • 堆内存:堆分配内存比栈分配内存要复杂得多。这是因为,堆可以用来存储长期数据和短期数据和不同类型和大小的数据。分配和释放不总是按可预见的顺序,并且可能需要非常不同大小的内存块。当创建一个堆变量的时候,会按以下的步骤:
    • 首先,Unity 会检查堆是否有足够的剩余内存。如果堆有足够的剩余内存,就会为变量分配内存。
    • 如果堆内有足够的剩余内存,Unity 触发垃圾回收尝试释放不使用的堆内存。这可能是一个很慢的操作。如果这时堆有足够的剩余内存,则会为变量分配内存。
    • 如果垃圾回收后,堆剩余内存仍然不够,Unity 会在堆中增加内存。这可能是一个很慢的操作。然后为变量分配内存。
    • 堆分配可能会很缓慢,尤其是需要运行垃圾回收器或扩展堆内存的时候。
    • 当一个堆变量离开作用域,存储该变量的内存不会被立即释放。不使用的堆内存只有在垃圾回收器运行时候才会释放。
  • 每次垃圾回收器运行,会发生以下的事情:
    • 垃圾回收器检查堆中每个对象
    • 垃圾回收器搜索所有当前对象的引用以确定堆对象是否在作用域。
    • 任何不在作用域的对象都打一个删除的标记。
    • 分配给打了删除标记的对象的内存会被堆回收。
  • 垃圾回收什么时候发生?
    • 请求堆内存分配的时候,剩余内存不能满足时,垃圾回收器会运行。
    • 垃圾回收器不时的自动运行(不同的平台频率不同)。
    • 垃圾回收可以手动运行gc接口。
  • 垃圾回收可能是一个频繁的操作。请求对内存分配,内存不足时候会触发垃圾回收器,这意味频繁的堆内存申请和释放可能导致频繁的垃圾回收。

减少垃圾回收的几种方法:

  • 使用缓存,避免在频繁调用的函数中申请内存
  • 对象池技术
  • 字符串String是引用类型,容易产生垃圾
  • 减少装箱/拆箱操作

堆栈内存的黄金规则

  • 引用类型永远存储在堆里
  • 值类型和指针类型永远存储在他们声明时所在的堆或栈中:如果一个值类型不是在方法中定义的,而是在一个引用类型里,那么此值类型将会被放在这个引用类型里并存储在堆上。
  • 结构体struct是值类型,需要小心地去使用它,因此对于这种情况,考虑使用ref关键字,只是去传递原始值类型的引用。
  • 此部分内容参考

面向对象(多态与虚函数表)

面向对象的三个特征:封装,继承与多态

  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。

    • 重载(overload): 在同一个作用域(一般指一个类)的两个或多个方法函数名相同,参数列表不同的方法叫做重载,它们有三个特点(俗称两必须一可以):

      • 方法名必须相同
      • 参数列表必须不相同
      • 返回值类型可以不相同
    • 重写(override):子类中为满足自己的需要来重复定义某个方法的不同实现,需要用 override 关键字,被重写的方法必须是虚方法,用的是 virtual 关键字。它的特点是(三个相同):
      • 相同的方法名
      • 相同的参数列表
      • 相同的返回值。
    • 虚方法:即为基类中定义的允许在派生类中重写的方法,使用virtual关键字定义。等同于C++中的虚函数。
    • 抽象方法:在基类中定义的并且必须在派生类中重写的方法,使用 abstract 关键字定义。在C++中等同于纯虚函数。抽象方法只能在抽象类中定义,如果不在抽象类中定义,则会报错,抽象类无法实例化。

C# 多态实现

在C#中,类都是引用类型,因此托管堆中所有对象都要有的两个额外的成员——类型对象指针(Type Object Point)和同步块索引(sync Block Index)。当执行某个方法时,如果该方法是非虚实例方法,JIT编译器会找到发出调用的那个变量的类型对应的类型对象(比如cat继承自Animal类型对象)。然后调用该类型对象中的指定方法(比如Sleep),如果该类型对象没有Sleep方法,JIT编译器会回溯类的基类(一直到Object)中查找Sleep方法。

C#接口与抽象类

  • 抽象类:抽象方法只能出现在抽象类中,但是抽象类中可以包含普通方法(普通方法可以由非抽象类的子类调用)在父类中定义的抽象方法不能实现。抽象类不能实例化。抽象类与抽象方法需要添加abstract关键字。子类实现父类的抽象方法时,需要添加override关键字。(不能继承多个抽象类,C++允许多重继承)
  • 接口:接口的格式可以记成用interface替换Class来定义(interface接口默认的访问修饰符是internal)一旦某个类实现了接口,就必须实现接口中定义的全部成员。不能直接实例化接口,可以使用接口实现多态,可以实现多个接口。

游戏引擎由哪几个部分组成

游戏引擎主要由

  • 渲染引擎:将一个场景中所有正确的对象和效果显示到显示器。渲染引擎在引擎的所有部件当中是最复杂的,它的强大与否直接决定着最终的输出质量。
  • 物理引擎:主要用来模拟物体的重力加速度以及其他与物理相关的元素。使游戏中物体按照现实生活中的物理现象,可以让游戏效果更加逼真。
  • 碰撞检测系统:检查游戏中物体的边缘和碰撞,当两个3D物体撞在一起的时候,这种技术可以防止它们相互穿过,这就确保了当你撞在墙上的时候,不会穿墙而过,也不会把墙撞倒,因为碰撞探测会根据你和墙之间的特性确定两者的位置和相互的作用关系。
  • 网络引擎:支持多人在线同时游戏必不可少的部分。
  • 音效引擎:处理声音和音乐。
  • 脚本引擎:编译运行脚本等。
  • 动画及场景管理系统:处理游戏中物体的各种动画。
  • 输入/输出系统:用来处理文件操作、键盘、鼠标相应等。
  • 人工智能系统:人工只能AI设计。
  • 数据库系统:用来存储游戏中的数据。
  • UI系统:创建窗口目录等

等模块组成,通常将这些子系统定义为单例类模式。

C# 关键字

  • Sealed: sealed修饰说明该类将不能被继承或重写
  • Abstract: 标识一个可以扩展但不能被实体化的、必须被实现的类。
  • Private: 仅仅对该类公开;
  • Public: 对任何类和成员都公开,无限制访问;
  • Protected: 限定的是只有在继承的子类中可访问,可以跨程序集
  • Internal: 限定的是只有在同一程序集中可访问,可以跨类
  • Virtual:修饰属性或方法(不同于C++的virtual因为C#不允许多重继承)

设计模式

一些基本设计模式的写法

  • 单例模式:饱汉,饿汉(双重锁保证线程安全)Instance
  • 订阅/发布模式

几种设计原则

  • 单一职责原则 (The Single Responsiblity Principle,简称SRP):一个类,最好只做一件事,只有一个引起它的变化.
  • 开放-封闭原则 (The Open-Close Principle,简称OCP):对于扩展是开放的,对于更改是封闭的
  • Liskov 替换原则(The Liskov Substitution Principle,简称LSP):子类必须能够替换其基类
  • 依赖倒置原则(The Dependency Inversion Pricinple,简称DIP):依赖于抽象
  • 接口隔离原则 (The Interface Segregation Principle,简称ISP):使用多个小的专门的接口,而不要使用一个大的总接口。

游戏资源管理MipMap,LightMap,LOD

  • MipMap: 类似瓦片地图,当渲染近处的物体时,使用大的图片,渲染远处的物体时使用小图片。
  • LightMap: 光照贴图, 描述了场景光照数据的纹理.预先渲染好后续加载即可,速度很快。
  • LOD:Level of Detail(细节层次).类似MipMap,物体模型多边形数目随着物体或者模型远离观察者而逐步降低。LOD主要针对三维模型,MipMap针对二维贴图。

UGUI锚点与中心点

  • Pivot中心轴(x,y),范围在(0,1)之间
  • Anchor(Min,Max)四个数组成一个框

内存五大区

  • 堆区:引用类型
  • 栈区:值类型
  • 程序代码区:自由存储放置代码逻辑
  • 全局静态区:全局静态变量
  • 常量存储区:常量区

委托

C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针(但是之间还是有很大区别的)。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。

排序算法


详情参考:
十大经典排序算法(动图演示)
排序算法时间复杂度、空间复杂度、稳定性比较

String、StringBuilder

String‘作为引用类型,频繁操作每次都会创建一个新的字符串对象,系统开销可能会比较昂贵。使用StringBuilder因此可以提升性能。

数据结构中的堆

在数据结构中,堆就是用数组实现的二叉树,所以不存在父指针或者子指针,堆根据“堆属性“来进行排序,堆属性决定了树中节点的位置。
最大堆与最小堆

  • 区别在于节点的排序方式上。
  • 堆常常被当作优先队列来使用,对于堆array以数组的方式存储,如果i是节点的索引,则:
    • parent(i) = floor((i - 1)/2) 向下取整,不大于x的最大整数
    • left(i) = 2i + 1
    • right(i) = 2i + 2
  • 在堆中,在当前层级所有的节点都已经填满之前不允许开是下一层的填充,如果一个堆有 n 个节点,那么它的高度是 h = floor(log2(n))。这是因为我们总是要将这一层完全填满以后才会填充新的一层。
  • 堆的添加节点与删除节点操作是怎么样的?需要自适应堆。

数据结构(树)

  • 基本算法

    • 前序遍历(递归写法,非递归写法使用栈)
    • 中序遍历(递归写法,非递归写法使用栈)
    • 后序遍历(递归写法,非递归写法使用栈)
    • 层序遍历(非递归写法使用队列)
  • 树的深度的计算方法:递归深度(一棵树的深度等于max(左子树深度,右子树深度)+1)、层序遍历
  • 根据遍历结果重构树的方法
  • 获取树的左视图,右视图(层次遍历)

Shader

《Render-Time Rendering》中将其简要划分为三个阶段:

  • 应用阶段(Application Stage):以应用为主导,由CPU负责实现,主要完成的任务包括:

    • 场景数据准备:例如摄像机的位置,视锥体,模型,光源等(把数据加载到显存)
    • 粗粒度剔除(culling),剔除不可见的模型
    • 设置每个模型的渲染状态(例如使用哪些材质纹理或者使用什么shader之类的)(设置渲染状态然后调用Draw Call命令)
  • 几何阶段(Geometry Stage):几何阶段主要用于处理所有和几何绘制相关的事情,如决定需要绘制的图元是什么,如何绘制,在哪里绘制,此部分为GPU主导。将应用阶段中的顶点数据进行坐标变换,变换到屏幕坐标系下,因此该阶段最后输出屏幕空间的二维顶点坐标,每个顶点对应深度值,着色等信息。
  • 光栅化阶段(Rasterizer Stage):根据几何阶段的结果,进行光照等颜色的计算,并进行插值,此阶段也是GPU主导,它需要对上一阶段得到的逐顶点数据进行插值,最后进行逐像素处理。

顶点变换中涉及到的空间坐标系

Unity外部资源加载方法

  • Resources文件夹,(unity将保留文件夹全部打包,最大2G),

    • 只读,无法动态修改,所以动态更新资源不要放在此处,打包会加密压缩
    • 通过Resources.Load读取
  • StreammingAssets: 与Resources十分像,但是打包不会保护加密与压缩,主要存放一些二进制文件,可通过WWW获取
  • AssetBundle:资源压缩包,方便发布与版本升级,AssetBundle就是把prefab或者二进制文件封装成AssetBundle文件

动画模式

  • Unity动画状态机:Animator
  • 3D模型动画的基本原理是让模型中各顶点的位置随时间变化。主要种类有Morph(变形)动画,关节动画和骨骼蒙皮动画(SkinnedMesh)(不同骨骼对不同mesh有一个权重影响最后计算出总的结果)。从动画数据的角度来说,三者一般都采用关键帧技术,即只给出关键帧的数据,其他帧的数据使用插值得到。

C#闭包

变量域的问题,通过新增一个临时变量保存下来即可

态合批与静态合批

  • 动态合批与静态合批其本质是对将多次绘制请求,在允许的条件下进行合并处理,减少cpu对gpu绘制请求的次数,达到提高性能的目的。静态合批是将静态(不移动)GameObjects组合成大网格,然后进行绘制。对需要静态合批物体的Static打钩即可,unity会自动合并被标记为static的对象,前提它们共享相同的材质,并且不移动,被标记为static的物体不能在游戏中移动,旋转或缩放。
  • 动态合批是将一些足够小的网格,在GPU上转换它们的顶点,将许多相似的顶点组合在一起,并一次性绘制它们。

计算机网络

OSI网络七层架构

  • 应用层(HTTP,FTP)
  • 表示层
  • 会话层
  • 传输层(TCP/UDP)
  • 网络层
  • 数据链路层
  • 物理层

热更新

  • 热更新:

    • 广义定义:无需关闭服务器,不停机状态下修复漏洞,更新资源等,重点是更新逻辑代码。
    • 狭义定义( iOS热更新):无需将代码重新打包提交至AppStore,即可更新客户端的执行代码,即不用下载app而自动更新程序。
    • 现状:苹果禁止了C#的部分反射操作,禁止JIT(即时编译,程序运行时创建并运行新代码),不允许逻辑热更新,只允许使用AssetBundle进行资源热更新。
  • Android 应用的热更新
    • 将执行代码预编译为assembly dll。
    • 将代码作为TextAsset打包进Assetbundle。
    • 运行时,使用Reflection机制实现代码的功能。
    • 更新相应的Assetbundle即可实现热更新。

Unity texture,RenderTexture

RenderTexture是Unity定义的一种特殊的Texture类型,连接着一个FBO (FrameBufferObject)即帧缓冲。可以理解FrameBufferObject是一个集合,集合了FrameBuffer,通过快速刷新Framebuffer实现动态效果,最典型的FBO就是Unity的Main Camera,它是默认的FBO,是gpu里渲染结果的目的地.但是现代gpu通常可以创建很多其他的FBO(Unity中可以创建多个Camera),这些FBO不连接窗口区域,这种我们创建的FBO的存在目的就是允许我们将渲染结果保存在gpu的一块存储区域,待之后使用,这种用法叫做离屏渲染,这是一个非常有用的东西。Camera 输出的FBO,可以嵌在另一个FBO中,Unity中使用RenderTexture来接收FBO(可视化FBO),Game窗口是一个特殊的RenderTexture,它允许多个FBO叠加渲染,当Camera的RenderTarget都设置为null时表示输出到game窗口(没有摄像机的RenderTaget为null会显示没有摄像机进行渲染),设置不为null表示输出到某个RT。

深度缓冲

深度缓冲区与帧缓冲区相对应,用于记录上面每个像素的深度值,通过深度缓冲区,我们可以进行深度测试,从而确定像素的遮挡关系,保证渲染正确。深度其实就是该象素点在3d世界中距离摄象机的距离(绘制坐标),深度缓存中存储着每个象素点(绘制在屏幕上的)的深度值!深度值(Z值)越大,则离摄像机越远。在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。

Unity脚本执行生命周期

C#各类型所占字节大小

类型 字节大小
bool 1字节
byte 1字节
sbyte 1字节
char 2字节
short 2字节
ushort 2字节
uint 4字节
int 4字节
float 4字节
ulong 8字节
long 8字节
double 8字节

HashTable的实现与Dictionary的区别:(C#中没有HashMap)

  • 哈希表(HashTable)表示键/值对的集合。在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key-value的键值对,其中key通常可用来快速查找,同时key是区分大小写;value用于存储对应于key的值。Hashtable中key-value键值对均为object类型,所以Hashtable可以支持任何类型的keyvalue键值对,任何非 null 对象都可以用作键或值。
  • HashSet类主要是设计用来做高性能集运算的,例如对两个集合求交集、并集、差集等。集合中包含一组不重复出现且无特性顺序的元素,HashSet拒绝接受重复的对象。
  • Dictionary: Dictionary表示键和值的集合。Dictionary<string, string>是一个泛型,本身有集合的功能有时候可以把它看成数组。其结构是这样的:Dictionary<[key], [value]>。他的特点是存入对象是需要与[key]值一一对应的存入该泛型。通过某一个一定的[key]去找到对应的值。

区别

  • HashTable不支持泛型,而Dictionary支持泛型。
  • Hashtable 的元素属于 Object 类型,所以在存储或检索值类型时通常发生装箱和拆箱的操作,所以你可能需要进行一些类型转换的操作,而且对于int,float这些值类型还需要进行装箱等操作,非常耗时。
  • 单线程程序中推荐使用 Dictionary, 有泛型优势, 且读取速度较快, 容量利用更充分。多线程程序中推荐使用 Hashtable, 默认的 Hashtable 允许单线程写入, 多线程读取, 对 Hashtable 进一步调用 Synchronized() 方法可以获得完全线程安全的类型. 而 Dictionary 非线程安全, 必须人为使用 lock 语句进行保护, 效率大减。
  • 在通过代码测试的时候发现key是整数型Dictionary的效率比Hashtable快(因为这个时候Hashtable有装箱拆箱),但如果key是字符串型,Dictionary的效率没有Hashtable快。

哈希冲突

哈希表(Hash Table,也叫散列表),是根据关键码值 (Key-Value) 而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。哈希表的实现主要需要解决两个问题,哈希函数和冲突解决。

哈希函数也叫散列函数,它对不同的输出值得到一个固定长度的消息摘要。理想的哈希函数对于不同的输入应该产生不同的结构,同时散列结果应当具有同一性(输出值尽量均匀)和雪崩效应(微小的输入值变化使得输出值发生巨大的变化)。

现实中的哈希函数不是完美的,当两个不同的输入值对应一个输出值时,就会产生“碰撞”,这个时候便需要解决冲突。常见的冲突解决方法有开放定址法,链地址法,建立公共溢出区等。实际的哈希表实现中,使用最多的是链地址法

在这里散列函数的作用就是讲key值映射成数组的索引下标。关于散列函数的设计方法有很多,如:直接寻址法、数字分析法、随机数法等等。但即使是再优秀的设计方法也不能避免散列冲突。在散列表中散列函数不应设计太复杂。

散列函数具有确定性和不确定性。

  • 确定性:哈希的散列值不同,那么哈希的原始输入也就不同。即:key1=key2,那么hash(key1)=hash(key2)。
  • 不确定性:同一个散列值很有可能对应多个不同的原始输入。即:key1≠key2,hash(key1)=hash(key2)。
  • 散列冲突,即key1≠key2,hash(key1)=hash(key2)的情况。散列冲突是不可避免的,如果我们key的长度为100,而数组的索引数量只有50,那么再优秀的算法也无法避免散列冲突。

散列表的负载因子 = 填入表中的元素个数/散列表的长度

  • 开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。比如,我们可以使用线性探测法。当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,如果遍历到尾部都没有找到空闲的位置,那么我们就再从表头开始找,直到找到为止。
  • 链表法(拉链法)。简单来讲就是在冲突的位置拉一条链表来存储数据。链表法是一种比较常用的散列冲突解决办法,Redis使用的就是链表法来解决散列冲突。链表法的原理是:如果遇到冲突,他就会在原地址新建一个空间,然后以链表结点的形式插入到该空间。当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可。

问答题

HashTable 为什么用String做键值?
答:Dic是类型安全的,这有助于我们写出更健壮更具可读性的代码,而且省却我们强制转化的麻烦。Dic是泛行的,当K或V是值类型时,其速度远远超过Hashtable。处理对象时不需要进行显式或是隐式转型,进行值类型处理时不需要装箱。所以使用方便一些,而且效率会高一些(编码效率、运行效率),还不太容易出错。(即用String类型(引用类型)做键值会出现装箱、拆箱等操作降低性能)(也就是说类似于List与ArrayList这种,后者会有装箱操作,而List泛型则不会有这种情况发生)

给一个随机函数,如果其结果足够随机(0-1)之间,那么给一个(0-10)范围的数,如何保证其概率都为0.1?
答:洗牌算法.(给定一个数组,如何打乱其顺序且保证概率相等)Leetcode详解

随机数生成器(随机数如何生成):给定一个随机数生成器,这个生成器能均匀生成1到5(1,5)的随机数,如何使用这个生成器生成均匀分布的1到7(1,7)的数?
答:
方法一:生成两个(1,5)的随机数,这样一共是25种情况,注意这两个数是有顺序的,从这25种情况中,取前21种,每三种代表(1,7)中的一个数字,如果取到的是这21种以外的情况,丢掉重新取。
方法二:生成三个(1,5)的随机数,分别表示一个二进制位,其中1和2映射为0,3跳过,4和5映射为1。这样产生的三位二进制数,即1-8这8个数字都是等概率的。如果产生的是8,那么丢弃即可。
方法三:生成两个(1,5)的随机数,产生一个两位的五进制数,5 * (random5() – 1) + random5()。这个公式能够等概率产生1-25,即第一个随机数代表:0,5,10,15,20,地位代表1,2,3,4,5。这样对这个数字(1-25的数字),采用方法一的方法,只用1-21,分7分,代表1-7,22-25这4个数字扔掉。

位运算

异或运算交换两个数:
a = a^b;
b = a^b;
a = a^b;

求一个队列中只出现了一次的数(只有一个):
对数组中每个元素都异或一次

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分组异或:先对所有数字进行一次异或,得到两个出现一次的数字的异或值。在异或结果中找到任意为 1的位。根据这一位对所有的数字进行分组。在每个组内进行异或操作,得到两个数字。

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
设计有限状态自动机,统计所有数字的各二进制中1的个数,并对3求余,结果为只出现一次的数字。

三渲二

三维图像渲染出二维原画效果

C#中进程Sleep的理解

操作系统中,CPU竞争有很多策略,Unix用的是时间片,Windows则使用的是抢占式的策略。在时间片算法中,所有的进程排成一个队列。操作系统按照他们的顺序,给每个进程分配一段时间,即该进程允许运行的时间。如果在 时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程 序所要做的就是维护一张就绪进程列表,,当进程用完它的时间片后,它被移到队列的末尾。

所谓抢占式操作系统,就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU 。因此可以看出,在抢占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出 CPU 。在抢占式操作系统中,假设有若干进程,操作系统会根据他们的优先级、饥饿时间(已经多长时间没有使用过 CPU 了),给他们算出一 个总的优先级来。操作系统就会把 CPU 交给总优先级最高的这个进程。当进程执行完毕或者自己主动挂起后,操作系统就会重新计算一次所有进程的总优先级,然后再挑一个优先级最高的把 CPU 控制权交给他。

所以Thread.Sleep就是用于告诉操作系统,在未来多少毫秒内不参与CPU竞争。Thread.Sleep(0)的作用就是触发操作系统立刻重新进行一次CPU竞争。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.Sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里。

C# 编译器的链接执行顺序是什么

托管环境的共同特点是:编译器不直接编译成机器码,而是中间代码。在那之后,在运行时JIT(Just In Time)编译器将MSIL中间代码翻译成机器码,这意味着我们的代码在真正使用的时候才被解析,这允许在CLR(公共语言运行时)预编译和优化我们的代码,实现程序性能的提高,但增加了程序的启动时间

死锁

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

处理死锁的基本方法:

  • 死锁预防:通过设置某些限制条件,去破坏死锁的四个条件中的一个或几个条件,来预防发生死锁。但由于所施加的限制条件往往太严格,因而导致系统资源利用率和系统吞吐量降低。
  • 死锁避免:允许前三个必要条件,但通过明智的选择,确保永远不会到达死锁点,因此死锁避免比死锁预防允许更多的并发。
  • 死锁检测:不须实现采取任何限制性措施,而是允许系统在运行过程发生死锁,但可通过系统设置的检测机构及时检测出死锁的发生,并精确地确定于死锁相关的进程和资源,然后采取适当的措施,从系统中将已发生的死锁清除掉。
  • 死锁解除:与死锁检测相配套的一种措施。当检测到系统中已发生死锁,需将进程从死锁状态中解脱出来。常用方法:撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程。死锁检测盒解除有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。

Unity C# 游戏客户端面试复习相关推荐

  1. 【游戏客户端面试题干货】--2021年最新游戏客户端面试干货(lua篇)

      [游戏客户端面试题干货]-- 2021年度最新游戏客户端面试干货(lua篇)     大家好,我是Lampard~~   经过春招一番艰苦奋战之后,我终于是进入了心仪的公司.   今天给大家分享一 ...

  2. 【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货(操作系统篇)

    [游戏客户端面试题干货]-- 2021年度最新游戏客户端面试干货(操作系统篇)   大家好,我是Lampard~~   经过一番艰苦奋战之后,我终于是进入了心仪的公司.   今天给大家分享一下我在之前 ...

  3. 【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货( 计算机网络篇 )

    [游戏客户端面试题干货]-- 2021年度最新游戏客户端面试干货( 计算机网络篇 )   大家好,我是Lampard~~   经过一番艰苦奋战之后,我终于是进入了心仪的公司.   今天给大家分享一下我 ...

  4. 【游戏客户端面试题干货】-- 2021年度最新游戏客户端面试干货( C++篇 )

    [游戏客户端面试题干货]-- 2021年度最新游戏客户端面试干货( C++篇 )   大家好,我是Lampard~~   经过春招一番艰苦奋战之后,我终于是进入了心仪的公司.   今天给大家分享一下我 ...

  5. 【游戏客户端面试题干货】--2020年最新游戏客户端面试干货(c#篇)

     [游戏客户端面试题干货]--2020年度最新游戏客户端面试干货( C#篇 )   大家好,我是Lampard~~   经过春招一番艰苦奋战之后,我终于是进入了心仪的公司.   今天给大家分享一下我在 ...

  6. 游戏客户端面试(Unity)

    推荐阅读: 我的CSDN 我的博客园 QQ群:704621321 我的个人博客 一.最开始的两家公司笔试面试题目 一家公司是学校聘请研究教育方面VR课件的公司,面试没几天,就收到了面试通过的消息,后面 ...

  7. 2019腾讯游戏客户端面试

    第一次面试腾讯,个人感觉,每个问题都会问的比较深入,以及原理性的理解. 一面是电话面试,一个还比较年轻的面试官跟我开视频面试的,面试时间一小时.下面是一些问到的内容: 1.针对简历,问一下你之前的项目 ...

  8. 《Unity 3D游戏客户端基础框架》protobuf 导excel表格数据

    前言: 之前使用NPOI插件编写的导表工具,其实就是直接将数据进行序列化,解析时还需要进行反序列化,步骤比较繁复,最近看到Google的一个开源的项目protobuf,不仅可以用于进行excel表格数 ...

  9. 【游戏客户端与服务器面试题】-- 2022年最新游戏客户端与服务器面试(lua篇持续更新)

    [游戏客户端与服务器面试题干货]-- 2022年度最新游戏客户端面试干货(lua篇) 文章目录 一.Lua的8种数据类型 (1) nil 类型 (2) boolean类型 (3) number类型 1 ...

最新文章

  1. 方法 手写promise_高级前端养成37js专精06之手写promise(上)
  2. mongodb json_在MongoDB和Spring Batch中将XML转换为JSON和原始使用
  3. attributeerror: __enter___python魔法方法之__setattr__()
  4. 我的世界服务器名称被占用,为什么我的世界服务器说此用户名已被注册我都换了很多用户了都没用 爱问知识人...
  5. dspq值多少最好_蜂蜜纯度42的意思?蜂蜜纯度多少度好?
  6. [笔记]深入解析Windows操作系统《二》系统架构
  7. 从SVN下载项目到本地的eclipse 工作空间
  8. u盘启动蓝屏 索尼vaio_索尼笔记本电脑安装系统后出现蓝屏怎么处理
  9. 网络测量工具及其使用
  10. aix linux ftp服务器,AIX主机FTP到LINUX服务器其中的磕碰记录
  11. Unity环境光 Environment面板
  12. 联想服务器安装win10系统安装教程,联想笔记本安装win10系统图文教程
  13. 七日杀局域网找不到服务器,7日杀局域网的联机教程步骤图
  14. c语言求三角形周长代码,C语言求三角形面积和周长
  15. ARM7——LPC2xxx小总结
  16. 第一次玩switch,需不需要再买一个任天堂Pro手柄
  17. 1024,一封写给CSDN家园Python初学者的信 Python初级、中级、高级学习路线
  18. 苹果cmsv10仿爱美剧网自适应美化模板免费模板
  19. 推荐几个2023年比较好用的youtube转换器
  20. 【ArcGIS】server授权文件ecp超期的处理

热门文章

  1. eSIM时代,运营商的末日还是新生
  2. java中try catch的中断规则
  3. 小众支持ps手机预览设计稿的软件
  4. 阿布扎比王储支持区块链在航空业的应用
  5. Linux中重定向输入和输出
  6. border.css
  7. 中国人要在太空安家!今天先把大厅送了上去
  8. Windows个人电脑的自我防护(包括nmap的扫描端口和cmd的跃点追踪)
  9. 2022-01-26 Android app java 获取设备制造商的方法:Build.MANUFACTURER,实际上是读ro.product.manufacturer的值。
  10. ASP.net 网站项目:Fckeditor使用Step-By-Step