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
。
static_cast
是用于在已知的相关类型之间进行转换,比如整型和浮点型、指向基类的指针与指向派生类的指针之间的转换。他在编译时进行类型检查。dynamic_cast
主要用于处理多态,即将基类指针或引用安全地转换为派生类的指针或引用,并在运行时检查类型的转换是否安全。这种转换只有在类型为多态类型时才能使用,也就是说,这个类型必须包含至少一个虚函数。如果转换不成功,对于指针类型会返回nullptr,对于引用类型会抛出std::bad_cast异常。
解释:
1. static_cast
static_cast
用于在已知的相关类型之间进行转换。这种转换发生在编译时,即编译器在编译过程中对类型进行检查,确保转换是合理的。以下是具体的场景和示例:
场景:
- 基本数据类型之间的转换:如整型和浮点型之间的转换。
- 枚举类型与整型之间的转换。
- 指针类型的转换:如从基类指针转换为派生类指针。
- 向下转换:即从基类指针转换为派生类指针(需要手动确保转换的安全性)。
- 向上转换:即从派生类指针转换为基类指针。
c
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):利用运行时类型信息机制进行安全的类型转换。
c
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) { // 转换失败,处理异常 }
区别和总结:
-
static_cast
:- 发生在编译时。
- 编译器进行类型检查。
- 转换速度快,但需要开发者确保转换的安全性(在向下转换时尤为重要)。
- 不检查多态类型。
-
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++中有多种用法:
- 静态局部变量:在函数内部声明的static变量,其生命周期跨越整个程序运行期,但仅在该函数作用域内可见。他被初始化一次,即在第一次进入函数时。
- 静态类成员:用static声明的类成员变量或函数属于整个类,而不是类的某个特定对象。静态成员函数只能访问静态成员变量或其他静态成员函数。
- 静态全局变量或函数:在文件的全局作用域中声明的static变量或函数限定了其链接范围,使他们在定义他们的文件内可见。
对于初始化时机:
- 静态局部变量在程序执行流首次通过改变量时进行初始化。
- 静态全局变量和静态类成员变量在程序启动(进入main函数之前)时初始化。
- 如果存在多个静态变量的初始化顺序依赖,那么在同一个编译单元(文件内),他们按照定义的顺序进行初始化。跨编译单元的初始化顺序是未定义的,可能导致静态初始化顺序问题。
379. git rebase 和 merge 的原理讲一下
git merge:git merge命令将两个分支的更改合并到一起。当你执行一个合并时,Git会尝试自动融合不同分支的更改。如果两个分支在相同的文件行上有更改,那么可能会发生冲突,需要手动解决。合并完成后,会产生一个新的"合并提交"来表示这次合并。
git rebase:git rebase命令的本质是将一系列提交从一个分支上取下来,然后再应用到另一个分支上。Rebase重新写作了提交历史,她通过将提交------复制到新的基底上来实现。这个过程可以使历史再线性但是会改变现有的提交历史。需要注意的是,对于已经公开的分支执行rebase操作可能会导致问题,因为它会改变提交历史。
380. 如果我在某一个feature分支开发了两个月,这个时候要回去dev分支或者主分支,你应该应用哪个命令?
在一个feature分支上开发了较长时间,且希望将这些更改合并回dev分支或主分支,可以使用git merge或git rebase命令。
- 如果您希望在历史纪录中保留feature分支的合并点,保证完整性和合并的上下文,您应该使用git merge。
- 如果您想要一个更干净、线性的历史记录,可以选择使用git rebase先重新定位您的feature分支上的更改(确保先于dev分支同步),然后再将其合并到dev分支或主分支。但请注意,如果feature分支的更改是公开的并被其他人所使用,使用git rebase可能导致写作上的问题。
381. RTSP、RTMP、HLS的区别
- RTSP(Real Time Streaming Protocol):
- 用途:主要用于视频监控和视频会议系统。
- 特点:支持暂停、播放、快进等操作,实现了对流媒体的实时控制。
- 延迟:低至几百毫秒,适合实时交互。
- RTMP(Real Time Messaging Protocol):
- 用途:初期主要用于Adobe Flash播放器,现在用于直播。
- 特点:在传输过程中可以加密,更加安全。
- 延迟:较低,适合直播。
- HLS(HTTP Live Streaming):
- 用途:主要用于在线视频平台和OTT(Over The Top)流媒体。
- 特点:基于HTTP传输,易于跨平台,且便于跨防火墙和代理服务器传输。
- 延迟:较高,通常在几秒到十几秒,但最新的技术精湛一能显著降低延迟。
382. 视频编码
常见的视频编码标准包括 H.264 、 H.265 、 VP9等。
解释 :
这段话提到了三种常见的视频编码标准:H.264、H.265 和 VP9。以下是对每种编码标准的解释:
-
H.264(也称为 AVC,Advanced Video Coding):
- 概述:H.264 是一种广泛使用的视频压缩标准,由国际电信联盟 (ITU) 和国际标准化组织 (ISO) 联合开发。它在2003年发布,已成为流行的视频编码标准。
- 特点:H.264 提供了高效的视频压缩,使得在保持较高视频质量的同时显著减少文件大小。这使得它在各种应用中广泛使用,包括蓝光光盘、在线视频流(如 YouTube 和 Netflix)、以及视频会议等。
- 优点:高压缩率、良好的视频质量、广泛的硬件和软件支持。
-
H.265(也称为 HEVC,High Efficiency Video Coding):
- 概述:H.265 是 H.264 的继任者,于2013年发布。它由 ITU 和 ISO 开发,用于进一步提高视频压缩效率。
- 特点:H.265 能够在同样的视频质量下,比 H.264 更高效地压缩视频,通常可以将文件大小减少一半。这对于 4K 和 8K 视频特别重要,因为这些高分辨率视频需要更高的带宽和存储容量。
- 优点:更高的压缩效率、更好的视频质量、适用于超高清(UHD)视频。
-
VP9:
- 概述:VP9 是由 Google 开发的开源视频压缩标准,于2013年发布。它是 VP8 的后续版本,旨在与 H.265 竞争。
- 特点:VP9 提供类似于 H.265 的压缩效率,常用于互联网视频流,如 YouTube 和其他在线视频平台。与 H.265 不同,VP9 是开源的,无需支付专利费用,这使得它在一些场景中更具吸引力。
- 优点:高压缩效率、开源、无专利费用、良好的网络视频流支持。
总的来说,这三种编码标准各有优点,在不同的应用场景中得到了广泛应用。
383. 影响编码效率的因素
- 编码算法:不同编码标准如H.264、H.265的算法复杂行不同,影响效率。
- 分辨率和帧率:高分辨率和高帧率视频需要更多的数据处理。
- 码率:码率高时数据量大,编码负荷更重。
- 内容复杂度:场景复杂多变的视频比静态或重复场景的视频编码难度大。
- 颜色深度:颜色深度大的视频如10bit比8bit的视频编码费时。
- 并发编码算法:硬件加速和多线程技术可以提高编码效率。
- 压缩方式:使用更高级的压缩技术(如CABAC)可以提升编码效率。
384. 视频延迟来自哪方面?
- 采集延迟:摄像头和麦克风捕捉数据的时间。
- 编码延迟:将原始视频和音频数据编码成数字流的处理时间。
- 处理延迟:视频图像的处理,滤镜应用等额外处理步骤。
- 封装延迟:将编码后的数据打包成特定格式的时间。
- 网络传输延迟:数据包通过网络从发送者到接收者的时间,包括传播、排队、处理和解包时间。
- 缓冲延迟:客户端将接受的数据流解码为可播放的视频和音频的时间。
- 播放延迟:视频渲染和播放等待时间。
385. 开源流媒体服务器了解么?
- SRS : 简单高效的RTMP/HLS直播服务器。
- Nginx-RTMP : 基于Ngnix开发的RTMP流媒体服务器。
- Red5 : 使用Java开发的流媒体服务器,支持多种流媒体协议。
- MediaSoup : 针对WebRTC的高性能SFU服务器。
- Janus : 实时通信服务器,支持WebRTC等多种协议。
386. 编码的参数有哪些?
- 比特率(Bitrate):编码时数据传输速率,直接影响视频和音频质量。
- 帧率(Frame Rate):每秒显示图片数,影响视频流畅度。
- 分辨率(Resolution):视频的宽度和高度,影响视频清晰度。
- 编码格式(Codec):映像文件兼容性,如H.264 , H.265等。
- GOP(Group of Pictures):影响I帧(关键帧)的帧率,进而影响视频可寻址性和压缩效果。
387. I帧、P帧、B帧
I帧 : 关键帧,独立编码,不依赖其他帧。
P帧 : 向前预测帧,依赖前面的I帧或P帧进行编码。
B帧 : 双向预测帧,依赖前后帧进行编码,压缩率最高。
388. 手撕大小端转换
- 掩码并取得各个字节。
- 通过移位把字节放到相反的位置。
- 组合这些字节得到最终结果。
![[images/Pasted image 20240806162157.png]]
389. 说一下unity dots的ecs
Unity的DOTS是Unity的一种高性能编程模式,核心是ECS。ECS主要包括三个基本概念:实体(Entity)、组件和系统。
- 实体:代表游戏世界中的对象,比如玩家、敌人或任何想要表示的事物。在ECS中,实体本身几乎不包含任何数据或行为,它更像是一个标识符。
- 组件:包含数据的容器,用于描述实体的特征或状态。例如,一个位置组件可能包含X,Y和Z坐标。
- 系统:包含逻辑和行为,用于操作具有特定组件的实体集合。系统会执行具体的任务,如移动所有具体位置和速度组件的实体。
390. baker过程主要是输出什么?
产生的输出通常包括:
- 贴图(比如法线贴图、位移贴图、环境光遮蔽贴图等),用于提高渲染效率,同时保持视觉上的复杂度和细节。
- 静态光照信息,将光照信息嵌入到贴图中,减少实时光照计算的需要。
391. 说一下archetype的概念,它的用处主要是做什么的?
Archetype的概念源自于Unity的ECS,指的是一种具有一组特定组件配置的实体模板。每个Archetype定义了一组实体共享的组件类型,这使得具有相同组件集合的实体可以高效的存储和访问。
Archetype的主要用处是优化数据的存储和访问。通过将相同Archetype的实体数据紧凑的存储在一起,Unity可以更高效的遍历这些数据,从而提高了缓存的命中率和性能。这种数据组织方式使得针对大量相似实体进行操作时,如同批处理一样高效。
392. entity上面挂载三个component,数据是分开存的还是存在一起的?
实体上挂载的组件数据是分开存储的。
393. uitookit和ugui区别
- UI Toolkit:是一种新的UI构建方式,它使用CSS样式表进行样式定义,支持视觉树,旨在提高UI开发的效率和性能。主要应用于编辑器扩展开发,但也支持运行时UI开发。
- UGUI(Unity GUI):是Unity的早期UI系统,基于游戏对象和组件,通过拖放和配置这些组件在场景中创建UI。它非常直观,易于入门,适用于游戏内UI开发。
394. 说一下水位线对象池
水位线对象池是一种内存管理策略,它通过预先分配一定数量的对象 ,并在这个基础上动态的根据需要创建或回收对象来管理内存使用。"水位线"指的是对象池中对象的数量阈值:低水位线和高水位线。
- 当对象池中的可用对象数量下降到低水位线以下时,对象池会预先创建更多的对象,以避免运行时创建对象的开销。
- 当对象池中的对象数量上升到高水位线以上时,多余的对象会被销毁或回收,以释放内存。
395. uitoolkit里面怎么去实现自定义事件
- 创建一个新的事件类,继承自EventBase< T > , 其中T是你的新事件类名
- 在你的自定义事件类中,添加所需的属性和方法。
- 使用RegisterCallback< T >() 方法在感兴趣的UI元素上注册事件监听器,T是你的自定义事件类。
- 使用visualElement.SendEvent()方法触发自定义事件。