TIMI面试准备

该博客涵盖面试常涉及的技术问题。包括平面点与扇形位置判断、进程调度算法、快排思路等算法知识,C++虚函数、函数重载重写等语言特性,vector、map等容器底层实现,还有网络编程、渲染管线、设计模式等多方面信息技术内容。

文章目录

1.平面内如何判断一个点在扇形内部

可以通过计算该点与扇形的圆心之间的距离以及与扇形的圆心连线与扇形的两条边界线之间的夹角来实现

2.系统中进程调度有哪些算法

  1. 先来先服务
  2. 最短作业优先
  3. 高响应比优先调度
  4. 时间片轮转调度
  5. 最高优先级
  6. 多级反馈队列
  7. 最短剩余时间
  8. 最大吞吐量

3.快排思路

随机选择一个基准元素(一般选择中间元素mid),然后通过基准元素把待排数组分为左右两边,然后递归继续深入排序即可;

4.C+里的虚函数?实现原理?

虚函数是一种用于实现多态性的机制。它允许在基类和派生类之间进行动态绑定,使得在调用同一个函数时能够根据对象的实际类型来执行相应的代码。

在C++中,虚函数的实现原理涉及到两个关键概念:虚函数表(vtable)和虚函数指针(vptr)。当一个类中声明了一个虚函数时,编译器会为该类创建一个虚函数表(vtable)。虚函数表是一个存储了虚函数地址的表格,其中每个条目对应于一个特定的虚函数。它通常是一个类的静态数据成员,所有的对象共享同一个虚函数表。

在每个对象的内存布局中,编译器会插入一个指向虚函数表的指针,称为虚函数指针(vptr)。这个指针指向对象所属类的虚函数表。通过虚函数指针和虚函数表的结合,可以实现动态绑定。

当调用一个虚函数时,编译器会使用虚函数指针找到对象所属类的虚函数表,并根据函数在虚函数表中的索引位置来调用正确的虚函数。这个过程称为虚函数的动态分派。

总结起来,虚函数的实现原理涉及到虚函数表和虚函数指针。每个类拥有自己的虚函数表,对象通过虚函数指针来访问所属类的虚函数表,从而实现了根据对象实际类型进行动态分派的能力。这样,即使通过基类指针或引用调用虚函数,也能够正确地执行派生类中的对应函数。

5.虚函数表会在每个类对象中都存一份吗?

是的,在每个类对象中都会存储一份虚函数表。每个对象都有自己的内存布局,其中包含一个指向所属类的虚函数表的虚函数指针(vptr)。
虚函数表是静态数据成员,所有的对象共享同一个虚函数表的地址。但是,虚函数表本身的内容在每个对象中都会有一份拷贝。
这是因为每个对象都可能有自己特定的派生类信息,包括可能重写或添加新的虚函数。通过为每个对象存储一份虚函数表,可以保证在调用虚函数时能够根据对象的实际类型来执行正确的函数。

需要注意的是,由于每个对象都有自己的虚函数表,所以不同的对象可能会有不同的虚函数表指针值,即它们指向不同的虚函数表。这样就能够实现多态性,在运行时动态地绑定到正确的虚函数表和对应的函数上。

6.函数重载和函数重写?

用于实现多态性和代码复用。

  1. 函数重载(Function Overloading)指的是在同一个类中定义多个相同名称但参数列表不同的函数。这些函数可以有不同的返回类型、参数类型、参数个数或参数顺序。编译器会根据调用时提供的参数信息来确定调用哪个重载函数,例如:
class MyClass {
public:
    void doSomething(int x);
    void doSomething(double x);
};

  1. 函数重写(Function Overriding),也称为虚函数重写(Virtual Function Overriding),指的是在派生类中重新定义(覆盖)基类中已有的虚函数。被重写的函数必须具有相同的名称、返回类型和参数列表。通过使用 virtual 关键字声明基类中的函数,并在派生类中使用相同的函数定义,可以实现函数重写。
class Base {
public:
    virtual void show();
};

class Derived : public Base {
public:
    void show(); // 重写基类的虚函数
};

函数重载通过不同参数来实现多态性和代码复用,而函数重写通过继承和虚函数的机制实现动态绑定和多态性。

7.vector的底层实现?

vector 是一个动态数组容器,它提供了可变大小的数组功能。std::vector 的底层实现通常是使用连续的内存块来存储元素,并通过指针进行访问。它动态分配内存以适应元素数量的变化,并支持高效的随机访问和迭代。

具体来说,std::vector 使用以下三个成员变量来管理内存和元素:

指向数组起始位置的指针(如 T* data):表示动态数组的起始地址。
容量大小(如 size_t capacity):表示当前分配内存块的总容量,即可存储的元素个数。
当前元素个数(如 size_t size):表示当前存储在 std::vector 中的元素个数

8.vector的扩容机制?

固定扩容:每次扩容在原capacity基础上加上固定容量,空间利用率高,但时间复杂度高;
加倍扩容:每次扩容是原capacity翻倍,因预留空间多,所以扩容次数减少,导致时间复杂度低,空间复杂度高;

9.map是啥?底层实现?

map 是 C++ 标准库中的关联容器之一,它提供了键值对的存储和检索功能。 std::map 中的每个元素都由一个键(key)和一个值(value)组成,通过键可以快速地查找对应的值。

底层实现原理是红黑树或平衡二叉树来存储键值对,这种数据结构可以保持元素按键的有序性,并支持高效的插入、删除和查找操作

10.红黑树特性有哪些?

  1. 每个节点都有一个颜色属性,红色或黑色。
  2. 根节点是黑色的。
  3. 所有叶子节点(NIL 节点)都是黑色的。
  4. 如果一个节点是红色的,那么其子节点必定是黑色的。
  5. 对于任意节点,从该节点到其后代的所有叶子节点的简单路径上,均包含相同数量的黑色节点

11.vector和list的区别?

  1. vector底层实现是数组,list是双向链表
  2. vector是顺序存放的,支持随机访问,list则不行;
  3. vector在中间节点进行插入删除会导致内存拷贝,list不会;
  4. vector一次性分配内存,不够时才进行翻倍扩容;list每次插入新节点都会进行内存申请;
  5. vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好;

12.new和malloc的区别?

  1. new是c++运算符,malloc是c语言标准库函数。
  2. malloc分配失败返回空指针,new分配失败则默认抛出异常。
  3. malloc返回void类型指针,new返回具体类型指针。
  4. new可利用c++对象的构造函数来执行对象的初始化操作,malloc不会调用对象构造函数;
  5. new表达式可根据对象类型自动计算所需内存大小,malloc需要显式指定所需内存大小;

13.c++里堆和栈的区别?

  1. 栈的内存分配和释放是自动的,由编译器进行管理,堆上的内存分配和释放是手动的,需要程序员显式的请求和释放;
  2. 栈上分配和释放速度比堆上块
  3. 栈大小通常较小,由编译器预先分配的固定空间,而堆的大小通常较大,受限于操作系统的虚拟内存大小;
  4. 栈上的变量具有局部作用,其生命周期随着函数的执行而自动开始和结束。而在堆上分配的内存可以在多个函数之间共享, 且在程序员手动释放之前一直存在;
  5. 栈上的内存可直接访问且速度快,而堆上的内存须通过指针进行访问,速度慢;

14.可以手动在栈上申请内存吗?

在大多数情况下,栈上的内存分配是由编译器自动管理的,无法手动控制分配的大小。但你可以间接地利用栈来实现对内存的手动操作。一种常见的方法是使用指针,通过指针来引用栈上的对象或数据。

例如,你可以声明一个指向某个类型对象的指针,并将其初始化为指向栈上的内存块,这样你就可以通过指针来手动操作该内存块,包括读取和修改其中的数据。但这种方式存在一些潜在的风险,因为栈上的内存是有限的,如果不小心操作超出了栈的范围,可能会导致栈溢出或其他未定义的行为。

15.#define和typedef?

#definetypedef
创建符号常量或宏定义用于给已有类型取别名
只是简单的字符串替换,没有类型检查有对应数据类型,要进行判断
在编译的预处理阶段起作用在编译、运行时候起作用
不分配内存,给出的是立即数,有多少次使用就有多少次替换在静态存储区中分配空间,在程序运行过程中内存只有一个拷贝

16.给定10000个数求最大3个?

使用排序算法(快速排序、堆排序等)

17.堆排序?

思想:将待排序的数组构建成一个最大堆(Max Heap),然后每次从堆顶取出最大元素,再重新调整堆,重复这个过程直到找到最大的三个数。

18.shared_ptr?

它是一种引用计数智能指针 允许多个指针同时拥有对同一对象的所有权,通过引用计数的方式来管理对象的生命周期。当最后一个 shared_ptr 被销毁时,对象会自动被释放;

内部维护了一个引用计数器,记录有多少个shared_ptr指向同一个对象。
每当一个新的shared_ptr指向对象时,引用计数加一;当shared_ptr被销毁或重置时,引用计数减一。当引用计数为零时,即没有任何shared_ptr 指向对象,对象将被销毁;

循环引用指的是两个或多个对象相互持有对方的 shared_ptr,导致引用计数永远不会变为零,对象得不到释放,造成内存泄漏。
可以通过使用 weak_ptr 来打破循环引用,weak_ptr 是另一种智能指针类型,它允许引用但不共享对象的所有权,避免了循环引用导致的内存泄漏问题。

19.迪杰斯特拉算法?

解决图中单源最短路径问题的一种经典算法。是一种贪婪算法,通过维护一个距离表来记录源节点到其他节点的当前最短路径长度,并逐步扩展最短路径集合,直到找到源节点到所有节点的最短路径。
具体步骤如下

  1. 创建一个距离表和一个已访问节点集合。距离表用于记录源节点到其他节点的当前最短路径长度(初始值为正无穷大),已访问节点集合用于记录已经找到最短路径的节点。
  2. 将源节点的距离设置为0,表示从源节点到自身的距离为0。
  3. 从未访问节点中选择一个距离最小的节点,将其加入已访问节点集合,并更新距离表:
  4. 遍历所有与该节点相连的未访问节点,如果经过该节点到达这些节点的路径比距离表中记录的路径更短,则更新距离表中的路径长度。
  5. 重复步骤3,直到所有节点都被加入已访问节点集合,或者找到特定目标节点的最短路径为止。
  6. 最终,距离表记录的就是源节点到每个节点的最短路径长度,通过反向递推可以得到最短路径。

20.进程与线程的区别?

进程线程
定义进程是程序一个执行实例,内存中分配了独立的资源和空间线程是进程的执行单元,在进程的上下文中执行,共享进程的资源
资源消耗每个进程都有独立的地址空间和系统资源,进程间切换要保存和恢复大量上下文数据,资源消耗大每个线程有自己的栈空间和程序计数器,线程间切换消耗的资源相对较小
并发性不同进程间独立运行,彼此相互隔离,不能直接共享变量或资源,进程间通信复杂线程间通信相对简单,可通过共享内存和同步机制进行通信
安全性进程崩溃不会影响其他进程稳定性,有更高的隔离和安全性一个线程错误可能导致整个进程的崩溃,线程之间需要适当的同步和互斥机制保证数据一致性和安全性

21.多线程访问同一资源时的操作?

多个线程同时访问和修改同一个共享资源时,要考虑线程安全性,以避免数据竞争和不一致的结果;
互斥锁

  1. 在访问共享资源之前,线程可以尝试获取一个互斥锁。如果成功获取到锁,就可以安全地访问共享资源;否则,线程将被阻塞等待锁释放。
  2. 在访问共享资源之后,线程需要释放锁,以便其他线程可以获取它并访问相应的资源。
  3. 互斥锁保证了每次只有一个线程可以进入临界区(访问共享资源的代码段),从而防止数据竞争。

读写锁

  1. 读写锁允许多个线程同时读取共享资源,但只允许一个线程进行写操作。
  2. 读取操作不会改变共享资源的状态,因此多个线程可以同时读取,提高了并发性能
  3. 写操作改变共享资源的状态,需要独占地访问,禁止其他线程的读操作和写操作

原子操作

条件变量

22.渲染管线了解过吗?

它描述了从输入图元(如三角形)开始到最终输出图像的整个渲染过程。以下是经典的图形渲染管线的阶段:

  1. 顶点处理(Vertex Processing):
    该阶段处理输入的顶点数据,如位置、法线、纹理坐标等。
    执行顶点变换和投影操作,将顶点从模型空间转换到裁剪空间或屏幕空间,为后续的光栅化阶段做准备。
  2. 几何处理(Geometry Processing):
    这个阶段处理由顶点处理阶段发出的几何图元(如三角形)。
    执行光栅化操作,将图元拆分成像素片段,计算片段的位置和属性。
  3. 光栅化(Rasterization):
    在该阶段,将几何图元转换为像素片段,并为每个片段计算其在屏幕上的位置和属性。
    这些属性可能包括深度值(用于深度测试)、法线、纹理坐标、颜色等。
  4. 片段处理(Fragment Processing):
    片段处理阶段对每个像素片段执行各种操作,包括光照计算、纹理采样、透明度混合等。
    这些操作可以通过编写着色器程序来实现,根据片段的特性对其进行处理。
  5. 像素操作(Pixel Operations):
    在最后一个阶段,对最终的片段进行像素操作,例如混合、遮罩、深度测试等。
    最终生成的像素颜色将被存储在帧缓冲区,最后呈现在屏幕上。

23.UE里的动画蓝图了解过吗?

动画蓝图是UE中用于创建和控制角色动画的可视化工具。它使开发人员能够在UE的蓝图编辑器中创建复杂的角色动画逻辑,而无需编写代码。
下面是蓝图相关概念和功能:

  1. 蓝图编辑器
  2. 动画状态机
  3. 动画蓝图图表
  4. 蓝图变量
  5. 骨骼动画

24.游戏里的AI用来做什么?

主要用于模拟和控制非玩家角色(NPC),增强游戏交互性

25.行为树有哪些比较重要的节点吗?

行为树由一系列节点组成,各个节点代表不同的行为或决策

  1. 序列节点(Sequence):序列节点按顺序执行其子节点,直到其中一个子节点失败,或者所有子节点都成功。它用于顺序执行一系列相关的子行为。

  2. 选择节点(Selector):选择节点按顺序执行其子节点,直到其中一个子节点成功,或者所有子节点都失败。它用于选择和执行多个可能的行为中的一个。

  3. 并行节点(Parallel):并行节点可以同时执行所有子节点,并根据一定的条件(如成功、失败、超时等)来决定最终的行为。它用于在同一时间执行多个子行为,例如攻击和躲避同时进行。

  4. 条件节点(Condition):条件节点用于检查某个条件的状态,并根据条件的真假来判断下一步的行为。如果条件成立,进入一个子节点;如果条件不成立,跳过该子节点。

  5. 行为节点(Action):行为节点执行具体的操作或行为,如移动、攻击、使用技能等。它是行为树中实际执行任务的节点。

  6. 循环节点(Loop):循环节点重复执行其子节点一定数量的次数,或者一直执行直到某个条件满足。它用于实现循环性的行为,如巡逻或重复尝试某个操作。

  7. 装饰器节点(Decorator):装饰器节点用于修改其子节点的行为或状态。例如,可以添加一个装饰器节点来设定一个子节点的时间限制或重试次数。

26.射击游戏里子弹发射、击中敌人扣血等机制怎么实现?

  1. 子弹发射:
    创建子弹对象或实体,并设置其初始位置、速度和方向。
    更新子弹的位置,通常是每帧根据速度向前移动,可以考虑使用物理引擎或简单的移动逻辑来处理。
    检测子弹与敌人或碰撞体之间的碰撞,可以使用碰撞检测系统或射线投射进行检测。
    如果子弹与敌人碰撞,触发击中敌人的逻辑。
  2. 击中敌人
    当子弹与敌人碰撞时,获取碰撞信息,并确定被击中的敌人。
    根据击中的位置和子弹的威力计算伤害值。
    从被击中的敌人的血量中减去伤害值,并更新敌人的血量显示。
    如果敌人的血量降为零或以下,触发敌人死亡的逻辑,并播放相应的死亡动画或效果。
    如果敌人的血量仍然大于零,可以触发被击中效果,如闪烁、受击动画等。

27.怎么获取子弹和敌人碰撞这一事件?

  1. 设置碰撞体
    在子弹和敌人的对象或实体中,添加碰撞体组件(Collider),例如盒形碰撞体(Box Collider)或球体碰撞体(Sphere Collider)。
    碰撞体应该和子弹或敌人的外观对应,并设置正确的碰撞体尺寸和位置。
  2. 碰撞事件监听
    在子弹或敌人的脚本中,注册碰撞事件监听器(Collision Listener)。
    根据具体的游戏引擎和编程语言,可以使用回调函数、事件系统或消息机制进行监听。
  3. 处理碰撞事件
    当碰撞事件发生时,碰撞事件监听器会相应地触发回调或事件。
    在回调或事件处理函数中,根据需要获取碰撞的详细信息,如碰撞点、碰撞物体的标识等。
    判断发生碰撞的物体是否符合预期,即子弹和敌人之间的碰撞关系。
  4. 处理子弹和敌人碰撞逻辑
    如果发生了子弹和敌人的碰撞,根据游戏逻辑进行相应的处理,如计算伤害、扣血等。
    更新敌人的血量和显示,播放被击中的效果、声音等。

28.了解碰撞的底层实现吗?

游戏引擎中,碰撞的底层原理主要涉及两个方面:碰撞检测和碰撞响应。

29.UE的反射机制?

它允许开发者在运行时获取和操纵引擎和游戏的类、对象、成员和函数等信息

  1. 类信息获取
  2. 动态创建对象
  3. 属性访问和修改
  4. 函数调用和消息传递
  5. 蓝图通信和扩展

30.unity有用过吗?

Unity使用C#作为主要的脚本编程语言,开发者可以使用C#编写游戏逻辑和行为

31.dynamic_cast底层,遇到错误的情况

它主要用于处理多态类型的转换,即基类指针或引用转换为派生类指针或引用。底层实现会依赖于C++的RTTI(Run-Time Type Information,运行时类型信息)机制。
在底层实现中,dynamic_cast可能会进行以下步骤:

  1. 多态类型检查:dynamic_cast首先会检查指针或引用所指向的对象是否具有多态类型,即是否包含虚函数。

  2. 类型信息比较:如果对象具有多态类型,dynamic_cast会比较源类型和目标类型的类型信息。这些类型信息通常由指向虚函数表(vtable)的指针表示。

  3. 类型转换:如果类型信息比较成功,并且目标类型是源类型的公有派生类或相同类,dynamic_cast将会返回指向目标类型的指针或引用。否则,转换失败。

遇到类型转换失败的情况,对于指针返回nullptr,对于引用抛出异常;

32.new重载,placement new

new是一个用于动态分配内存和初始化对象的运算符。除了常规的new运算符,C++还提供了new的重载形式和placement new操作符。

  1. 重载的new
    可以通过重载new运算符来自定义对象的内存分配和初始化过程。通过在类中定义相应的静态成员函数operator new和operator delete,可以重载对象的内存管理行为。例如,可以实现自定义的内存池管理来提高内存分配的效率。
  2. placement new
    是一个在给定的内存地址上构造对象的操作符。它允许我们显式地在预先分配的内存区域上构造对象,而不是使用默认的堆内存分配。通过使用placement new,我们可以控制对象的内存位置,适用于某些特定的场景,例如在共享内存或特定内存池中构造对象。

33.TCP和UDP,可以同一端口吗?

可以的。TCP 和 UDP 传输协议在内核中是由两个完全独立的软件模块实现的。当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。
因此, TCP/UDP 各自的端口号也相互独立,互不影响

34.多个程序有相同动态链接库,运行时内存中有几个?

对于相同的动态链接库,只会在内存中存在一份拷贝。这是由操作系统的动态链接机制来保证的。
当多个程序都需要使用同一个动态链接库时,它们在启动时会去加载该库并将其映射到进程的虚拟地址空间中。实际上,它们加载的是同一份物理文件,并且在内存中只存在一份拷贝,不同的进程通过共享这份拷贝来实现对该库的访问。

35.几何题:长方体蛋糕任意位置有一个长方体空洞,切一刀切成相等两部分

  1. 计算蛋糕的总体积
  2. 计算目标体积,即 V / 2
  3. 遍历所有可能的切割面。可以通过三个嵌套的循环,分别遍历切割面的位置,即切割面与长方体的边界平行
  4. 对于每个切割面位置,计算切割后的两个部分的体积
  5. 如果存在切割面使得 V1 = V2 = V / 2,则找到了满足条件的切割方案。

36.栈溢出情况,怎么定位解决,线程栈一般多大

栈溢出是当程序使用栈空间超出其分配的范围时发生的错误。栈主要用于存储函数调用的局部变量、参数和函数调用的上下文信息。发生栈溢出可能是由于递归调用层数过深、过多的局部变量或过大的数据结构等原因导致。

  1. 确定栈溢出的原因:首先需要确认程序的行为,通过观察和调试找出出现栈溢出的具体位置和原因。一般来说,栈溢出的错误信息会包含调用栈的相关信息,从而帮助定位问题的位置。

  2. 检查递归调用:如果程序中存在递归调用,检查递归的终止条件是否正确,确认递归调用的深度是否过深导致栈溢出。

  3. 检查局部变量和数据结构大小:如果函数中有大量的局部变量或者使用了大的数据结构(如大数组),考虑减少局部变量的数量或者使用堆分配的内存来存储数据。

  4. 调整线程栈的大小:线程栈的大小是系统相关的,一般默认大小为 1MB 到几 MB。如果程序中需要使用大量的函数调用或者有较深的递归调用,可能需要增加线程栈的大小。可以通过调整编译器或系统的参数来修改线程栈的大小。

  5. 使用动态内存分配:如果程序的栈空间有限,并且需要大量的局部变量或者递归调用,可以考虑使用动态内存分配,通过堆分配的方式来存储数据。

一般情况下,线程栈的大小是与系统有关的,默认情况下通常是几 MB。

37.网络同步,帧同步状态同步优缺点应用场景,FPS用帧同步还是状态同步,帧同步如何确保客户端一致

网络同步是指在多人在线游戏中,确保各个客户端之间的游戏状态保持一致。常见的网络同步方式有帧同步和状态同步。

帧同步(Frame Synchronization)是指所有客户端以相同的帧率运行,并且在每一帧中进行游戏状态的更新和同步。在帧同步中,所有客户端按照相同的顺序执行相同的输入和游戏逻辑,从而保持游戏状态的一致性。

状态同步(State Synchronization)是指将游戏中的状态信息实时同步给其他客户端。在状态同步中,每个客户端根据自身的输入和游戏逻辑更新本地状态,并将状态信息发送给其他客户端,其他客户端接收到状态信息后进行状态更新,从而保持游戏状态的一致性。

帧同步和状态同步各自具有一些优点和缺点,适用不同的应用场景:

帧同步的优点:
游戏状态一致性强:所有客户端按相同顺序和帧率运行,能够实现高度一致的游戏状态。
灵活性较高:因为所有客户端执行相同的逻辑,可以实现复杂的游戏机制和交互。
响应速度快:因为客户端不需要等待其他客户端的状态信息,可以更快地进行本地更新。

帧同步的缺点:
带宽占用较大:需要传输完整的输入信息给其他客户端,带宽开销较高。
容易受网络延迟影响:如果某个客户端的网络延迟较高,会导致其他客户端的等待时间增加。
对延迟和抖动较敏感:如果网络延迟和抖动较大,可能会导致游戏画面的不流畅和卡顿。

状态同步的优点:
带宽占用较小:只需要传输状态更新信息,带宽开销相对较小。
容忍网络延迟和抖动:每个客户端根据自身的状态进行更新,不需要等待其他客户端的同步信息。
更适合大规模多人游戏:当玩家数量较多时,帧同步的带宽开销会随着玩家数量的增加而增加,而状态同步的开销相对稳定。

状态同步的缺点:
游戏状态相对不一致:由于每个客户端都有自己的更新逻辑,可能会导致细微的不一致现象。
无法处理复杂的交互:某些复杂的游戏交互可能无法通过仅同步状态信息来实现。
对于FPS(第一人称射击)游戏,常见的做法是使用状态同步来处理玩家之间的交互和碰撞检测。因为FPS游戏对于实时性要求较高,使用状态同步可以降低网络延迟并提高反应速度。

为了确保客户端的一致性,在帧同步中,通常会使用一些技术手段来确保客户端的状态一致:

检测和纠正误差:通过使用时间戳、预测和插值等技术,对客户端的输入和状态进行校正,减小由于网络延迟引起的误差。
客户端验证:服务器会对客户端的状态进行验证,防止作弊和非法操作,保证客户端的状态更新合法。

38.透视挂怎么解决

  1. 安全防护:游戏开发者可以采用各种安全措施来防止外部程序的运行和游戏数据的非法修改。例如使用加密技术保护游戏数据,对游戏程序进行代码签名和反作弊机制的添加等。

  2. 实时监测:开发者可以设计和部署监测机制,对游戏进行实时监测,发现和封禁使用透视挂的玩家。这可以通过自动化的反作弊系统或人工审核来实现。

  3. 举报机制:游戏开发者可以设置举报机制,让玩家可以轻松地举报使用透视挂的玩家。开发者在收到举报后可以进行调查,并根据调查结果采取相应的措施。

  4. 更新和修补漏洞:游戏开发者需要定期更新游戏程序,修补已知的漏洞和安全问题,确保游戏的稳定性和安全性

39.TCP和UDP,UDP可靠机制,UDP保证有序,UDP减少丢包率(多传去重)

  1. TCP 是一种可靠的、面向连接的协议。它提供了错误检测、流量控制、拥塞控制和序列号等机制,以确保数据的可靠传输。TCP 在发送数据之前会建立连接,数据按顺序传输,并且在接收端根据序列号进行重组和排序,确保数据的有序性。如果数据在传输过程中丢失或出现错误,TCP 会自动重传丢失的数据,确保数据的完整性和准确性。

  2. 相比之下,UDP 是一种不可靠的、无连接的协议。与 TCP 不同,UDP 不提供流量控制、拥塞控制、重传机制或数据的有序性保证。UDP 发送数据时不需要先建立连接,数据包之间相互独立,它在传输速度上更快,同时也会导致数据的丢失或乱序。

  3. 为了保证 UDP 数据的可靠性和有序性,应用程序可以通过在应用层实现额外的机制。例如,可以使用应答和重传机制来确认数据的到达和重传丢失的数据。另外,为了减少数据丢失,可以对重要的数据进行冗余传输(多传),以增加数据到达的概率。而在接收端,可以通过接收数据的顺序和时间戳等信息来进行数据的排序和组合,以保证数据的有序性。

40.TCP粘包

TCP 粘包指的是在 TCP 传输过程中,发送方发送的多个小数据包被接收方一次性接收并解析为一个大数据包,或者发送方发送的一个大数据包被接收方拆分为多个小数据包接收。
TCP 协议是面向字节流的,它并没有消息边界的概念,因此在发送方将数据拆分成小的数据段发送,在网络传输过程中这些数据段可能会合并成更大的数据块,或者大的数据块被拆分成更小的数据段。这就导致了 TCP 粘包的现象。
解决方案:

  1. 消息边界标识:在数据包中添加特定的消息边界标识,接收方根据这个标识来划分数据包;
  2. 定长包:发送方将数据按照固定长度进行拆分和发送,接收方根据固定长度进行解析和读取;
  3. 增加协议层:在应用层上增加协议,通过协议头来描述数据长度,接收方根据协议投中的长度信息来拆分数据包;

41.通过屏幕上的一个点确定世界坐标

需要知道屏幕的坐标系和世界坐标系之间的转换关系。一般而言,此类转换涉及到视图矩阵、投影矩阵和屏幕的宽度和高度。

  1. 将屏幕坐标系的点转换为标准化设备坐标系(NDC,Normalized Device Coordinates)的点。这可以通过将屏幕坐标系的点除以 viewport width 和 viewport height 来实现。NDC 坐标范围是[-1, 1],其中 (-1, -1) 表示左下角,(1, 1) 表示右上角。

  2. 使用投影矩阵逆矩阵(或逆投影矩阵)将点从 NDC 坐标系转换到裁剪坐标系。裁剪坐标系是一个四维坐标系,用于进行相机视角的裁剪和投影变换。

  3. 使用视图矩阵逆矩阵将点从裁剪坐标系转换到世界坐标系。视图矩阵表示从世界坐标系观察场景的视角和位置。

42.王者荣耀客户端用了什么优化机制

  1. 帧率控制
  2. 动态分辨率调整
  3. 网络优化
  4. 节能模式
  5. 资源压缩和加载优化

43.int,long在32位,64位中的长度

32位系统中: int 4字节 long: 4字节
64位系统中: int 4字节 long:8字节

44.指针占几字节

32位操作系统中,指针大小通常是4字节
64位操作系统中,指针大小通常是8字节

45.宏和const

  1. 宏是一种与处理指令,在编译时期进行简单的文本替换,宏定义使用#define关键字,将标识符域一个值或这一段代码绑定在一起
  2. 宏的优点是可以定义复杂的操作,例如函数调用、条件表达式等,在编译时被展开,但也容易出现宏展开后代码可读性差、容易出现问题等情况
    const
  3. const 是一种类型修饰符,用于定义常量。通过在变量声明前加上 const 关键字,可以使变量的值在程序运行期间不可修改。
  4. const 变量具有类型和作用域,并且可以享受编译器类型检查和作用域限制等好处。
  5. const 变量的值必须在声明时进行初始化,并且无法在后续代码中修改其值。

宏适用于需要进行复杂操作或条件编译的情况,但需要注意可读性和可能的宏展开问题。
const 则适用于定义常量值,提高代码的可读性和可维护性,以及类型和作用域的限制

46.如何将const转为非const

因为 const 修饰的变量是只读的,不允许进行修改。这是为了确保数据的不可变性和安全性。直接将 const 类型转换为非 const 类型是违反了这个原则的

  1. 强制类型转换const_cast:可能产生未定义行为,因为他违反了const对象的不可变性;
  2. 复制到非const变量:const变量的值复制到一个非const变量中,可通过修改非const变量来实现对值得修改,但同样会破坏const对象得不可变性

47.堆和栈,分别存些什么,栈中存放函数中哪些变量,函数参数的入栈顺序

  1. 堆(Heap):
    堆是用于动态分配内存的一部分。它的大小由操作系统控制,并且在程序运行时进行动态分配和释放。
    在堆中存放动态分配的对象、数据结构、类实例等。
    堆中的内存需要手动分配和释放,即通过 new 和 delete 或者 malloc 和 free 等操作来管理

  2. 栈(Stack):
    栈是用于存储局部变量和函数调用信息的一部分。
    栈的大小在编译时确定,以固定的栈帧大小为单位进行分配和释放。
    栈中存放函数的返回地址、参数、局部变量和临时变量等。
    栈中的内存分配和释放是自动进行的,随着函数的进入和返回而发生

函数中的变量都存储在栈中,包括函数参数、局部变量和临时变量。它们按照一定的顺序依次入栈,具体的入栈顺序可能与编译器、编程语言和系统有关。
一般而言,函数参数的入栈顺序是从右到左,也就是从最右边的参数开始入栈。这是因为在大多数的编程语言中,函数调用时参数是从右向左进行入栈的

int result = addNumbers(a, b, c);

那么 c 会先入栈,然后是 b,最后是 a

48.new除了分配内存还有什么用法,new重载有什么作用

除了用于动态分配内存外,new 还可以用于执行对象的构造函数,并返回指向已分配对象的指针。
当使用 new 运算符创建新的对象时,会先分配足够的内存来存储该对象,并然后调用对象的构造函数来初始化该对象。这个过程通常包括分配内存、调用构造函数以及返回指向已分配对象的指针。

class MyClass {
public:
    MyClass() {
        // 构造函数的实现
    }
};
// new来创建MyClass类型对象,并获得指向该对象的指针
MyClass* obj = new MyClass();

// 上述代码将会在堆中分配内存,然后调用 MyClass 的构造函数,最后返回指向已分配对象的指针 obj

new的重载,它允许我们通过自定义的版本来改变动态内存分配的行为。我们可以重载 new 操作符来实现自定义的内存管理、追踪内存分配、进行性能优化等。
通过重载 new,我们可以控制对象的内存分配方式,例如使用不同的内存池或者自定义的分配策略。这在一些特殊场景下可能非常有用,比如实现自定义的内存管理器或优化性能。

需要注意的是,使用自定义的 new 操作符时,也需要相应地重载 delete 操作符来释放对应的内存。这样可以确保在对应的内存分配和释放过程中保持一致性

49.析构函数为什么是虚函数,析构函数不是虚函数一定会造成内存泄漏吗

析构函数为虚函数的主要目的是支持多态的析构。当一个基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么在通过基类指针释放派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类中申请的资源没有被正确释放,从而引发内存泄漏的问题。

通过将析构函数声明为虚函数,在基类中定义一个虚析构函数,可以实现在通过基类指针删除派生类对象时,会依次调用派生类和基类的析构函数。这样可以确保派生类中的资源得到正确释放,避免内存泄漏的问题。

class Animal {
public:
    virtual ~Animal() { // 虚析构函数
        // 析构函数的实现
    }
};

class Dog : public Animal {
public:
    ~Dog() {
        // 析构函数的实现
    }
};

class Cat : public Animal {
public:
    ~Cat() {
        // 析构函数的实现
    }
};
// 当我们使用基类指针删除派生类对象时,由于基类的析构函数是虚函数,会依次调用派生类和基类的析构函数,从而正确释放资源
Animal* animal = new Dog();
delete animal; // 调用 Dog 的析构函数,然后调用 Animal 的析构函数

如果 Animal 的析构函数不是虚函数,那么上述代码只会调用 Animal 的析构函数,而不会调用 Dog 的析构函数,导致 Dog 中的资源无法得到正确释放,可能引发内存泄漏。

50.右值引用的作用,移动构造函数如何实现

右值引用的作用:
用于区分左值和右值:右值引用允许我们对临时的、将要被销毁的对象进行操作,而不会引起不必要的拷贝操作。通过使用 && 来声明变量为右值引用。
支持移动语义:右值引用是实现移动语义的基础,允许我们将资源从一个对象移动到另一个对象,而不是进行昂贵的拷贝操作。

移动构造函数的实现:
移动构造函数是一种特殊的构造函数,用于将资源从一个对象移动到另一个对象,避免了昂贵的拷贝操作。移动构造函数采用右值引用作为参数,并使用 std::move 将资源转移给正在构造的对象。

移动构造函数的通常实现步骤如下:
接收一个右值引用参数;
对资源进行浅拷贝或转移;
清空被移动对象的资源,保证在析构时不会释放或者重复释放该资源。
代码实现:

class MyObject {
public:
    // 移动构造函数
    // MyObject 类的移动构造函数接收一个右值引用 other。
    MyObject(MyObject&& other) noexcept {
    // 在函数体内,可将other中的资源转移到当前对象中,并清空other的资源
        // 对资源进行转移或浅拷贝
        // ...

        // 清空被移动对象的资源
        // ...
    }
};

// std::move(obj1) 将obj1转换为右值引用,然后调用了MyObject的移动构造函数,将obj1的资源移动到了obj2中
MyObject obj1; // 创建一个对象 obj1
MyObject obj2(std::move(obj1)); // 使用移动构造函数将 obj1 移动给 obj2

51.static_cast和dynamic_cast的区别,后者转化指针或引用失败时返回什么

static_castdynamic_cast
类型检查编译期进行类型检查运行时进行类型检查
功能范围基本类型之间转换、隐式转换(派生类到基类)、构造函数和转换运算符的调用在继承体系中进行安全的向下转型
成功失败无法捕获无效转换转换失败,返回一个空指针(对指针)或抛出一个异常(对引用)

52.C++编译链接的过程,链接的方式,多个程序共享一个动态库,其运行时加载几次,占用谁的内存

C++编译链接的过程通常包括编译、汇编和链接三个阶段;

  1. 编译:将源代码文件翻译成目标代码文件(.obj),这个阶段由编译器完成,编译器会检查代码的语法和语义,并生成相应的目标代码。
  2. 汇编:将目标代码文件转化为机器代码文件。这个阶段由汇编器完成,它会将目标代码翻译成与特定计算机体系结构相关的机器指令。
  3. 链接:将多个目标代码文件和库文件组合成一个可执行程序或库文件。这个阶段由链接器完成,链接器会解析符号引用、合并模块、分配内存地址等操作,最终生成可执行文件或共享库文件。

链接的方式主要有静态链接动态链接两种:
4. 静态链接(Static Linking):在静态链接中,链接器将所有被引用的目标文件和库文件的代码和数据复制到一个可执行文件中。这意味着在执行程序时,所有的代码和数据都包含在可执行文件中,不需要额外的依赖文件。静态链接生成的可执行文件比较大,但运行时独立,不受外部环境的影响。
5. 动态链接(Dynamic Linking):在动态链接中,链接器并不将所有代码和数据复制到可执行文件中,而是在运行时从动态链接库(或共享库)加载所需的代码和数据。这意味着多个程序可以共享同一个动态库,减少了内存占用和磁盘空间。动态链接生成的可执行文件较小,但在运行时需要依赖动态链接库

对于多个程序共享一个动态库,动态库只会在第一次被加载时进行运行时加载,加载后会被映射到操作系统的进程地址空间中,并由操作系统负责管理。其他程序在使用该动态库时,实际上是通过访问已经加载到内存中的动态库代码和数据。因此,一个动态库在运行时只会被加载一次,并在内存中占用一份内存空间,被多个程序共享

53.两个cpp中都定义int a会不会报错

如果两个cpp文件都定义了具有相同名称的全局变量 a,则在链接阶段会报错。这是因为全局变量具有外部链接(external linkage),它们在整个程序中都是可见的。当两个cpp文件被编译成目标文件并在链接阶段合并时,链接器将无法确定应该选择哪个定义来分配内存。

解决这个问题的一种常见方法是将变量 a 定义为 extern 以及在另一个cpp文件中声明它;
如果每个cpp文件都具有独立的命名空间(namespace),则可以在每个cpp文件中定义相同名称的变量而不会冲突;

54.STL有哪些容器,map的类型,区别

STL(标准模板库)提供了多种容器,常用的包括:

  1. vector:动态数组,支持随机访问和在末尾插入/删除元素。时间复杂度:随机访问 O(1),插入/删除末尾元素 O(1),插入/删除中间元素 O(n)。

  2. list:双向链表,支持在任意位置插入/删除元素。时间复杂度:插入/删除 O(1),随机访问 O(n)。

  3. deque:双端队列,支持在两端插入/删除元素。时间复杂度:随机访问 O(1),插入/删除首尾元素 O(1),插入/删除中间元素 O(n)。

  4. stack:栈,后进先出(LIFO)的数据结构,只能在栈顶进行插入/删除操作。

  5. queue:队列,先进先出(FIFO)的数据结构,只能在队尾插入,在队头删除。

  6. priority_queue:优先队列,按照一定的优先级来进行插入和删除操作。

  7. set:集合,内部元素按照一定规则自动排序,不允许重复元素。

  8. multiset:多重集合,与 set 类似,但允许重复元素。

  9. map:映射,存储键值对,按照键进行自动排序,不允许重复键。

  10. multimap:多重映射,与 map 类似,但允许重复键。

区别:

  1. vector、list 和 deque 是序列容器,它们允许按顺序存储和访问元素,但在插入和删除元素时性能有所不同。vector 在末尾插入和删除元素的性能较好,随机访问也很高效;list 在任意位置插入和删除元素的性能较好,但随机访问较慢;deque 在两端插入和删除元素的性能较好,随机访问也很高效。

  2. stack 和 queue 是适配器容器,基于序列容器实现,提供了栈和队列的功能。stack 实现了后进先出(LIFO)操作,只允许在栈顶插入和删除元素;queue 实现了先进先出(FIFO)操作,只允许在队尾插入,在队头删除。

  3. set、multiset、map 和 multimap 是关联容器,内部元素按一定规则自动排序。set 和 multiset 存储唯一元素,不允许重复;map 和 multimap 存储键值对,其中 map 的键唯一,multimap 的键可以重复。

55.网络编程

网络编程是指使用编程语言进行网络通信的过程。它涉及到在计算机网络中传输数据、建立连接、发送和接收数据等操作。下面介绍一些网络编程的基本概念和常用的编程框架:

  1. 协议:网络通信需要通过协议来规定通信双方的行为规范。常用的网络协议有TCP(传输控制协议)和UDP(用户数据报协议)。

  2. IP地址:IP地址是计算机在网络中的唯一标识,用于寻址和路由。IPv4是目前广泛使用的IP地址版本,而IPv6是下一代IP地址版本。

  3. 端口:端口是计算机上网络应用程序的标识符。每个应用程序会监听一个特定的端口,以便与其他应用程序进行通信。

  4. Socket:Socket是网络编程中用于实现网络通信的一种编程接口。通过Socket,可以建立连接、发送和接收数据。

  5. 客户端和服务器:网络通信中的参与方被称为客户端和服务器。客户端发起请求并向服务器发送数据,而服务器接收请求并响应客户端。

常用的网络编程框架有:

  1. TCP/IP套接字编程:使用底层的套接字(socket)API,它提供了对TCP/IP协议族的封装,可以实现基于TCP和UDP的网络通信。

  2. HTTP库:常用的HTTP库如Python中的Requests、Java中的HttpClient,它们封装了HTTP协议,提供了简化的HTTP请求和响应处理接口。

  3. WebSocket:WebSocket是一种实现全双工通信的协议,允许通过单个TCP连接进行双向通信。可以使用WebSocket库来实现WebSocket通信。

  4. RPC(远程过程调用)框架:RPC框架允许在不同计算机上的程序之间进行函数调用。常见的RPC框架包括 gRPC、Apache Thrift、Dubbo 等。

  5. Web框架:Web框架如Flask、Django、Spring Boot等提供了高层次的抽象,使开发者能够快速构建Web应用程序,并处理HTTP请求和响应。

网络编程涉及到许多细节和技术,如网络安全、网络协议的解析和处理等。

56.浏览器输入网址到呈现网页流程

  1. DNS解析:浏览器首先尝试从缓存中查找目标网址对应的IP地址,如果没有则向本地DNS服务器发起请求,根据域名解析得到对应的IP地址。如果本地DNS服务器也没有对应IP地址,则向根域名服务器、顶级域名服务器等依次递归查询,直到解析出目标网址对应的IP地址。

  2. 建立TCP连接:浏览器使用HTTP协议通过TCP协议在客户端和服务器之间建立一个TCP连接。TCP协议提供了可靠的、面向连接的数据传输服务,确保数据的有序传输和错误重传。

  3. 发送HTTP请求:建立TCP连接后,浏览器向服务器发送一个HTTP请求。HTTP协议定义了客户端和服务器之间交换数据的格式和规则。

  4. 服务器处理请求并返回响应:服务器收到HTTP请求后,在Web服务器上开始查找请求中所需要的资源,并通过HTTP响应将这些资源返回给浏览器。响应包括HTTP状态码、响应头和实体内容等信息。

  5. 浏览器解析和渲染页面:浏览器接收到服务器返回的HTML、CSS、JavaScript等资源后,开始解析HTML文档,构建DOM树,同时进行样式计算和JavaScript解析。然后,浏览器利用DOM树和样式计算生成渲染树,并根据布局信息绘制出页面。

  6. 客户端执行JavaScript代码:如果HTML文档中包含JavaScript代码,则浏览器会解析并执行该代码,以实现页面交互和动态效果。

  7. TCP连接关闭:当浏览器完成网页的加载和渲染后,向服务器发送TCP连接释放请求,服务器响应并释放连接,浏览器也同步释放连接

57.进程和线程的区别,多线程有多少栈,无锁多线程如何协作

进程和线程是操作系统中的两个重要概念,它们具有以下区别:

  1. 资源占用:进程是程序的执行实例,每个进程都有独立的内存空间、文件描述符和其他系统资源。而线程是进程内的执行单元,多个线程共享进程的资源。

  2. 切换开销:由于线程共享进程的资源,线程的切换开销比进程的切换开销更小。进程的切换需要保存和恢复整个进程的上下文,而线程的切换只需要保存和恢复线程的上下文。

  3. 并发性:不同的进程之间可以并发执行,彼此独立。而线程之间共享进程的地址空间,可以实现更高效的通信和数据共享。

  4. 通信方式:进程之间的通信需要使用操作系统提供的机制,如管道、共享内存、消息队列等。而线程之间可以通过共享的变量或者锁机制来进行通信。

至于多线程有多少栈,这取决于操作系统和编译器的实现。一般情况下,操作系统会为每个线程分配一个栈空间,栈用于存储线程的局部变量、函数调用信息等。

在无锁多线程中,线程之间通过非阻塞的方式进行协作,而不依赖于互斥锁或其他同步机制。无锁多线程的目标是避免线程之间的竞争和阻塞,以提高并发性能。

无锁多线程的协作方式可以有多种,常见的包括以下几种:

  1. 原子操作:使用原子操作可以实现对共享变量的无锁访问,保证操作的原子性,避免竞争条件。

  2. 无锁数据结构:使用无锁数据结构,如无锁队列、无锁哈希表等,来避免对共享数据的竞争,从而实现线程间的协作。

  3. CAS(比较和交换):CAS操作是一种无锁机制,通过比较共享变量的当前值和期望值,如果相等则将其更新为新值,否则什么都不做。CAS操作可以用于实现自旋锁、无锁计数器等。

无锁多线程的设计和实现需要考虑多线程的并发访问和数据一致性问题,通常需要使用内存屏障、原子操作、无锁算法等技术手段来解决。在实际应用中,需要根据具体场景和需求来选择合适的无锁协作机制。

58.设计模式了解哪些,MVC解决什么问题

我了解许多常见的设计模式,下面是一些常见的设计模式:

  1. 工厂模式(Factory Pattern):用于创建对象的模式,将对象的实例化过程封装在工厂类中,使客户端程序无需关心具体的对象创建过程。

  2. 单例模式(Singleton Pattern):保证一个类只有一个实例,并提供全局访问点。

  3. 观察者模式(Observer Pattern):定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。

  4. 装饰者模式(Decorator Pattern):动态地给一个对象添加额外的职责,就增加功能来说,装饰者模式比生成子类更灵活。

  5. 策略模式(Strategy Pattern):定义了一组算法族,分别封装起来,让它们之间可以相互替换。

  6. 适配器模式(Adapter Pattern):将一个类的接口转换成客户期望的另一个接口。

  7. 迭代器模式(Iterator Pattern):提供一种方法顺序访问一个聚合对象中的每个元素,而又不暴露其内部的表示。

MVC(Model-View-Controller)是一种软件架构模式,用于分离应用程序的关注点,解决了复杂应用程序中数据、视图和控制流的耦合问题。具体而言,MVC解决了以下问题:

  1. 分离关注点:MVC将应用程序分为三个部分,即模型(Model)、视图(View)和控制器(Controller),每个部分负责不同的功能,实现了关注点分离。

  2. 提高可维护性:通过将应用程序分层,每个部分都有清晰的职责和接口,使得代码更易于理解、修改和扩展。

  3. 支持多态性:MVC模式鼓励使用多态来实现不同的视图和控制器,从而支持应用程序的灵活性和可定制性。

  4. 提供可测试性:MVC模式使得模型、视图和控制器可以独立地进行单元测试,从而更容易检测和解决问题。

MVC模式在Web开发框架中广泛应用,帮助开发人员有效地组织和管理复杂的代码。通过明确的角色划分和清晰的代码结构,MVC模式提供了一种可扩展、可维护和可测试的架构,使开发过程更加高效和可靠。

59.协程是什么?与线程的区别?

协程(Coroutine)是一种轻量级的非抢占式并发机制,也被称为用户态线程或者纤程。它可以看作是一种特殊的函数,可以在执行过程中暂停并保存当前状态,然后返回执行权给调用者,之后再恢复执行,并接着上次暂停的地方继续执行。协程能够实现在单线程内的多个任务之间进行切换,使得编写异步和并发代码更加简洁和容易理解。

与线程相比,协程具有以下几点不同之处:

  1. 调度方式:线程由操作系统进行调度,是抢占式多任务处理,操作系统决定何时切换线程。而协程由程序员控制,是非抢占式的,即只有在协程主动让出执行权时才会切换到其他协程。

  2. 并行性:线程可以同时运行在多个物理核心上,实现真正的并行执行。而协程在单个线程内运行,并不能直接利用多核处理器的并行性能。

  3. 内存占用:每个线程都需要独立的栈空间和线程上下文,因此线程的创建和切换开销较大。而协程共享相同的栈空间,创建和切换开销较小。

  4. 数据共享:线程之间的数据共享需要使用同步机制,如互斥锁、条件变量等,以避免竞态条件。而协程可以通过共享栈空间直接读写共享数据,无需额外的同步机制。

  5. 调试和理解:由于协程是通过代码中的暂停和恢复来进行控制的,因此对于协程的调试和理解相对容易,可以更好地跟踪程序的执行流程。

总的来说,协程与线程相比具有更轻量级的特点,在高并发、IO密集型的应用场景下更加适用。它能够提供更高的并发性能,并且由于其非抢占式的特点,编写异步和并发代码更加简洁和直观。但是协程不能利用多核处理器的并行性能,对于计算密集型任务并不适用。

60.const可以和volatile联合使用吗?

可以的。在某些情况下,一个变量可能即具有不可修改的属性又具有可能发生变化的属性。这种情况就可使用const和volatile结合来修饰变量;如

const volatile int x =5;

这里的 x 是一个既不能被修改(const),又可能在程序执行期间发生变化(volatile)的变量,通常他们在一些特殊场景下使用,比如操作硬件寄存器或多线程共享变量等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值