C++面试速通宝典——21

发布于:2024-10-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

371. C++多态是怎么做到的——模板编程和虚函数

‌‌‌‌  C++的多态主要通过虚函数来实现。在基类中定义虚函数,在派生类中重写该函数,通过基类的指针或引用来调用虚函数时,会根据对象的实际类型动态绑定到适当的函数,这样就实现了运行时的多态。C++的函数重载和模板也可以实现编译时的多态。

372. 队列之间是怎么同步消息,mmap解释一下是怎么通信的?

‌‌‌‌  队列之间通常通过锁(互斥锁)和条件变量来同步消息,确保在写入和读取数据时能够正确地协调不同线程间的行为,防止数据竞争和不一致状态。

‌‌‌‌  mmap是一种内存映射文件的技术,它可以将文件或设备的内容映射到进程的地址空间通过mmap创建的内存区域可以被多个进程共享,从而实现进程间通信(PC)。当一个进程修改了内存映射区域的内容,这些修改将会反映到磁盘文件上,其他共享同一个映射的进程也能看到这些变更,实现了进程间的数据同步。

373. extern有什么用?为什么要这样做?不这样做为什么报错?

‌‌‌‌  extern关键字在C++中用于声明一个全局变量或函数的存在,但不定义它。当你在多个文件中共享同一变量或函数时,extern告诉编译器这个标识符是在其他地方定义的,可以在不同的编译单元中使用它。

‌‌‌‌  这样做的原因是,C++对于任何给定的变量或函数,只允许在一个地方进行定义(也就是分配存储空间),但他可以在其他多个地方声明。如果没有使用extern,并且在多个文件中定义相同变量或函数,链接器将会报错,因为他找到多个相同名称的符号定义,这违反了One Definition Rule(ODR)。

‌‌‌‌  如果不使用extern而直接在多个文件中定义相同的全局变量,则每个定义都会在各自文件的翻译单元中分配内存,导致多份副本存在。这会在链接时导致重定义的错误。使用extern可以避免这个问题,确保只有一份变量的定义和分配内存空间,而其他文件中则通过extern声明来引用这个个全局变量。

374. 四个cast讲一下,dynamic_cast和static_cast是在什么时候转换的?

‌‌‌‌  在C++编程中,有四种类型转换操作,分别是:static_cast、dynamic_cast、const_cast以及reinterpret_cast

  1. static_cast是用于在已知的相关类型之间进行转换,比如整型和浮点型、指向基类的指针与指向派生类的指针之间的转换。他在编译时进行类型检查。
  2. dynamic_cast主要用于处理多态,即将基类指针或引用安全地转换为派生类的指针或引用,并在运行时检查类型的转换是否安全。这种转换只有在类型为多态类型时才能使用,也就是说,这个类型必须包含至少一个虚函数。如果转换不成功,对于指针类型会返回nullptr,对于引用类型会抛出std::bad_cast异常。

解释

1. static_cast

‌‌‌‌  static_cast 用于在已知的相关类型之间进行转换。这种转换发生在编译时,即编译器在编译过程中对类型进行检查,确保转换是合理的。以下是具体的场景和示例:

场景:
  • 基本数据类型之间的转换:如整型和浮点型之间的转换。
  • 枚举类型与整型之间的转换
  • 指针类型的转换:如从基类指针转换为派生类指针。
  • 向下转换:即从基类指针转换为派生类指针(需要手动确保转换的安全性)。
  • 向上转换:即从派生类指针转换为基类指针。
float a = 3.14; 
int b = static_cast<int>(a); // 把浮点型转换为整型 
class Base {};
class Derived : public Base {}; 
Base *basePtr = new Derived(); 
Derived *derivedPtr = static_cast<Derived*>(basePtr); // 向下转换,需要确保basePtr指向的是Derived类型

2. dynamic_cast

dynamic_cast 主要用于处理多态,即安全地将基类指针或引用转换为派生类的指针或引用。这种转换发生在运行时,并进行类型检查,以确保转换的安全性。适用于有虚函数的多态基类。

场景:
  • 在继承关系中进行向下转换:将基类指针转换为派生类指针时,运行时检查确保转换安全(即基类指针实际指向派生类对象)。
  • 动态类型识别(RTTI):利用运行时类型信息机制进行安全的类型转换。
class Base { virtual void foo() {} // 必须有至少一个虚函数 };
class Derived : public Base {}; 
		  
Base *basePtr = new Derived(); 
Derived *derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) { 
		// 确认basePtr实际指向的是Derived类型对象 // 安全地使用derivedPtr } 
else { // 转换失败,basePtr不指向Derived类型对象 // 处理转换失败的情况 
} 
Base &baseRef = *basePtr; 
try { Derived &derivedRef = dynamic_cast<Derived&>(baseRef); // 成功转换,安全使用derivedRef } 
catch (std::bad_cast &e) { // 转换失败,处理异常 }
区别和总结:
  1. static_cast

    • 发生在编译时。
    • 编译器进行类型检查。
    • 转换速度快,但需要开发者确保转换的安全性(在向下转换时尤为重要)。
    • 不检查多态类型。
  2. dynamic_cast

    • 发生在运行时。
    • 动态类型检查,确保转换的安全性。
    • 适用于多态类型,即基类必须包含至少一个虚函数。
    • 转换失败时,指针类型返回nullptr,引用类型会抛出std::bad_cast异常。

375. 如果dynamic_cast是在运行时转换,原理是什么?

‌‌‌‌  dynamic_cast在运行时进行类型转换的原理基于对象运行时类型信息RTTI)。当使用dynamic_cast进行向下类型转换时(即将基类指针或引用转换为派生类指针或引用),他会使用RTTI来检查对象的实际类型是否与目标类型兼容。如果转换合法,dynamic_cast会返回转换后的指针或引用;如果不合法,它会返回nullptr(对于指针类型)或抛出std::bad_cast异常(对于引用类型)。这个检查过程确保了类型转换的安全性,避免了无效的转换。

376. type-id是什么?RTTI了解多少?

‌‌‌‌  type_id是指typeid操作符,他是C++中运行时类型信息(RTTI)的一部分。typeid用于获取一个表达式的类型信息。它可以用来在运行时识别对象的确切类型,并提供std::type_info对象,该对象包含了类型的相关信息,例如类的名称。

‌‌‌‌  RTTI是C++语言的一项特性,提供了在运行确定对象类型的能力。RTTI主要包括两个部分:typeid操作符和dynamic_cast操作符。他们允许在对象的类层次结构中执行类型安全的转换,并在运行时检查对象类型。RTTI通常用于多态场合,当需要在运行时判断对象的实际类型时非常有用。然而,RTTI也有一定的性能开销,并不总是被开发者利用。

377. 设计模式了解多少?单例模式怎么实现的?magic static怎么保证线程安全?

单例模式通常通过将构造函数设置为私有来限制对象的创建,并提供一个静态方法来获取类的唯一实例。使用magic static可以保证线程安全。

magic static指的是C++11标准中引入的特性,即局部静态变量的初始化是线程安全的。这意味着如果多个线程同时首次访问getlnstance()方法,编译器和运行时环境会保证仅有一个线程可以初始化instance,而其他线程将等待,直到初始化完成。这样就在不使用锁的情况下保证了线程安全。

378. 解释一下static关键字的用法?static的初始化是在什么时候的?

static关键字在C++中有多种用法:

  1. 静态局部变量:在函数内部声明的static变量,其生命周期跨越整个程序运行期,但仅在该函数作用域内可见。他被初始化一次,即在第一次进入函数时。
  2. 静态类成员:用static声明的类成员变量或函数属于整个类,而不是类的某个特定对象。静态成员函数只能访问静态成员变量或其他静态成员函数。
  3. 静态全局变量或函数:在文件的全局作用域中声明的static变量或函数限定了其链接范围,使他们在定义他们的文件内可见。

对于初始化时机:

  1. 静态局部变量在程序执行流首次通过改变量时进行初始化
  2. 静态全局变量和静态类成员变量在程序启动(进入main函数之前)时初始化
  3. 如果存在多个静态变量的初始化顺序依赖,那么在同一个编译单元(文件内),他们按照定义的顺序进行初始化。跨编译单元的初始化顺序是未定义的,可能导致静态初始化顺序问题

379. git rebase 和 merge 的原理讲一下

git merge:git merge命令将两个分支的更改合并到一起。当你执行一个合并时,Git会尝试自动融合不同分支的更改。如果两个分支在相同的文件行上有更改,那么可能会发生冲突,需要手动解决。合并完成后,会产生一个新的“合并提交”来表示这次合并。

git rebase:git rebase命令的本质是将一系列提交从一个分支上取下来,然后再应用到另一个分支上。Rebase重新写作了提交历史,她通过将提交——复制到新的基底上来实现。这个过程可以使历史再线性但是会改变现有的提交历史。需要注意的是,对于已经公开的分支执行rebase操作可能会导致问题,因为它会改变提交历史。

380. 如果我在某一个feature分支开发了两个月,这个时候要回去dev分支或者主分支,你应该应用哪个命令?

‌‌‌‌  在一个feature分支上开发了较长时间,且希望将这些更改合并回dev分支或主分支,可以使用git merge或git rebase命令。

  1. 如果您希望在历史纪录中保留feature分支的合并点,保证完整性和合并的上下文,您应该使用git merge。
  2. 如果您想要一个更干净、线性的历史记录,可以选择使用git rebase先重新定位您的feature分支上的更改(确保先于dev分支同步),然后再将其合并到dev分支或主分支。但请注意,如果feature分支的更改是公开的并被其他人所使用,使用git rebase可能导致写作上的问题。

381. RTSP、RTMP、HLS的区别

  1. RTSP(Real Time Streaming Protocol):
    1. 用途:主要用于视频监控和视频会议系统。
    2. 特点:支持暂停、播放、快进等操作,实现了对流媒体的实时控制。
    3. 延迟:低至几百毫秒,适合实时交互。
  2. RTMP(Real Time Messaging Protocol):
    1. 用途:初期主要用于Adobe Flash播放器,现在用于直播。
    2. 特点:在传输过程中可以加密,更加安全。
    3. 延迟:较低,适合直播。
  3. HLS(HTTP Live Streaming):
    1. 用途:主要用于在线视频平台和OTT(Over The Top)流媒体。
    2. 特点:基于HTTP传输,易于跨平台,且便于跨防火墙和代理服务器传输。
    3. 延迟:较高,通常在几秒到十几秒,但最新的技术精湛一能显著降低延迟。

382. 视频编码

常见的视频编码标准包括 H.264 、 H.265 、 VP9等。

解释
这段话提到了三种常见的视频编码标准:H.264、H.265 和 VP9。以下是对每种编码标准的解释:

  1. H.264(也称为 AVC,Advanced Video Coding)

    • 概述:H.264 是一种广泛使用的视频压缩标准,由国际电信联盟 (ITU) 和国际标准化组织 (ISO) 联合开发。它在2003年发布,已成为流行的视频编码标准。
    • 特点:H.264 提供了高效的视频压缩,使得在保持较高视频质量的同时显著减少文件大小。这使得它在各种应用中广泛使用,包括蓝光光盘、在线视频流(如 YouTube 和 Netflix)、以及视频会议等。
    • 优点:高压缩率、良好的视频质量、广泛的硬件和软件支持。
  2. H.265(也称为 HEVC,High Efficiency Video Coding)

    • 概述:H.265 是 H.264 的继任者,于2013年发布。它由 ITU 和 ISO 开发,用于进一步提高视频压缩效率。
    • 特点:H.265 能够在同样的视频质量下,比 H.264 更高效地压缩视频,通常可以将文件大小减少一半。这对于 4K 和 8K 视频特别重要,因为这些高分辨率视频需要更高的带宽和存储容量。
    • 优点:更高的压缩效率、更好的视频质量、适用于超高清(UHD)视频。
  3. VP9

    • 概述:VP9 是由 Google 开发的开源视频压缩标准,于2013年发布。它是 VP8 的后续版本,旨在与 H.265 竞争。
    • 特点:VP9 提供类似于 H.265 的压缩效率,常用于互联网视频流,如 YouTube 和其他在线视频平台。与 H.265 不同,VP9 是开源的,无需支付专利费用,这使得它在一些场景中更具吸引力。
    • 优点:高压缩效率、开源、无专利费用、良好的网络视频流支持。

总的来说,这三种编码标准各有优点,在不同的应用场景中得到了广泛应用。

383. 影响编码效率的因素

  1. 编码算法:不同编码标准如H.264、H.265的算法复杂行不同,影响效率。
  2. 分辨率和帧率:高分辨率和高帧率视频需要更多的数据处理。
  3. 码率:码率高时数据量大,编码负荷更重。
  4. 内容复杂度:场景复杂多变的视频比静态或重复场景的视频编码难度大。
  5. 颜色深度:颜色深度大的视频如10bit比8bit的视频编码费时。
  6. 并发编码算法:硬件加速和多线程技术可以提高编码效率。
  7. 压缩方式:使用更高级的压缩技术(如CABAC)可以提升编码效率。

384. 视频延迟来自哪方面?

  1. 采集延迟:摄像头和麦克风捕捉数据的时间。
  2. 编码延迟:将原始视频和音频数据编码成数字流的处理时间。
  3. 处理延迟:视频图像的处理,滤镜应用等额外处理步骤。
  4. 封装延迟:将编码后的数据打包成特定格式的时间。
  5. 网络传输延迟:数据包通过网络从发送者到接收者的时间,包括传播、排队、处理和解包时间。
  6. 缓冲延迟:客户端将接受的数据流解码为可播放的视频和音频的时间。
  7. 播放延迟:视频渲染和播放等待时间。

385. 开源流媒体服务器了解么?

  1. SRS : 简单高效的RTMP/HLS直播服务器。
  2. Nginx-RTMP : 基于Ngnix开发的RTMP流媒体服务器。
  3. Red5 : 使用Java开发的流媒体服务器,支持多种流媒体协议。
  4. MediaSoup : 针对WebRTC的高性能SFU服务器。
  5. Janus : 实时通信服务器,支持WebRTC等多种协议。

386. 编码的参数有哪些?

  1. 比特率(Bitrate):编码时数据传输速率,直接影响视频和音频质量。
  2. 帧率(Frame Rate):每秒显示图片数,影响视频流畅度。
  3. 分辨率(Resolution):视频的宽度和高度,影响视频清晰度。
  4. 编码格式(Codec):映像文件兼容性,如H.264 , H.265等。
  5. GOP(Group of Pictures):影响I帧(关键帧)的帧率,进而影响视频可寻址性和压缩效果。

387. I帧、P帧、B帧

I帧 : 关键帧,独立编码,不依赖其他帧。
P帧 : 向前预测帧,依赖前面的I帧或P帧进行编码。
B帧 : 双向预测帧,依赖前后帧进行编码,压缩率最高。

388. 手撕大小端转换

  1. 掩码并取得各个字节。
  2. 通过移位把字节放到相反的位置。
  3. 组合这些字节得到最终结果。

![[images/Pasted image 20240806162157.png]]

389. 说一下unity dots的ecs

Unity的DOTS是Unity的一种高性能编程模式,核心是ECS。ECS主要包括三个基本概念:实体(Entity)、组件和系统。

  1. 实体:代表游戏世界中的对象,比如玩家、敌人或任何想要表示的事物。在ECS中,实体本身几乎不包含任何数据或行为,它更像是一个标识符。
  2. 组件:包含数据的容器,用于描述实体的特征或状态。例如,一个位置组件可能包含X,Y和Z坐标。
  3. 系统:包含逻辑和行为,用于操作具有特定组件的实体集合。系统会执行具体的任务,如移动所有具体位置和速度组件的实体。

390. baker过程主要是输出什么?

产生的输出通常包括:

  1. 贴图(比如法线贴图、位移贴图、环境光遮蔽贴图等),用于提高渲染效率,同时保持视觉上的复杂度和细节。
  2. 静态光照信息,将光照信息嵌入到贴图中,减少实时光照计算的需要。

391. 说一下archetype的概念,它的用处主要是做什么的?

‌‌‌‌  Archetype的概念源自于Unity的ECS,指的是一种具有一组特定组件配置的实体模板。每个Archetype定义了一组实体共享的组件类型,这使得具有相同组件集合的实体可以高效的存储和访问。

‌‌‌‌  Archetype的主要用处是优化数据的存储和访问。通过将相同Archetype的实体数据紧凑的存储在一起,Unity可以更高效的遍历这些数据,从而提高了缓存的命中率和性能。这种数据组织方式使得针对大量相似实体进行操作时,如同批处理一样高效。

392. entity上面挂载三个component,数据是分开存的还是存在一起的?

‌‌‌‌  实体上挂载的组件数据是分开存储的。

393. uitookit和ugui区别

  1. UI Toolkit:是一种新的UI构建方式,它使用CSS样式表进行样式定义,支持视觉树,旨在提高UI开发的效率和性能。主要应用于编辑器扩展开发,但也支持运行时UI开发。
  2. UGUI(Unity GUI):是Unity的早期UI系统,基于游戏对象和组件,通过拖放和配置这些组件在场景中创建UI。它非常直观,易于入门,适用于游戏内UI开发。

394. 说一下水位线对象池

‌‌‌‌  水位线对象池是一种内存管理策略,它通过预先分配一定数量的对象并在这个基础上动态的根据需要创建或回收对象来管理内存使用。“水位线”指的是对象池中对象的数量阈值:低水位线和高水位线。

  1. 当对象池中的可用对象数量下降到低水位线以下时,对象池会预先创建更多的对象,以避免运行时创建对象的开销。
  2. 当对象池中的对象数量上升到高水位线以上时,多余的对象会被销毁或回收,以释放内存。

395. uitoolkit里面怎么去实现自定义事件

  1. 创建一个新的事件类,继承自EventBase< T > , 其中T是你的新事件类名
  2. 在你的自定义事件类中,添加所需的属性和方法。
  3. 使用RegisterCallback< T >() 方法在感兴趣的UI元素上注册事件监听器,T是你的自定义事件类。
  4. 使用visualElement.SendEvent()方法触发自定义事件。