影石 | C++开发工程师 一面 面经

1. 自我介绍

答:按"个人背景 → 核心技术栈 → 代表项目 → 求职意向"四段走,控制在2分钟以内。重点突出和影石岗位相关的经验,比如嵌入式Linux、音视频、IoT方向,不要把简历从头念一遍。结尾说一句为什么对影石感兴趣,体现你做过功课。

2. C++中的深拷贝和浅拷贝有什么区别?什么情况下必须自定义拷贝构造函数?

答:浅拷贝是按位复制,对于含有指针成员的类,两个对象会指向同一块内存,析构时会double free,这是典型的问题场景。深拷贝会为新对象重新分配内存并复制内容,两者完全独立。

必须自定义拷贝构造函数的情况:类中有裸指针管理的堆内存;类持有文件描述符、socket、锁等不可共享的资源;类的语义要求每个对象独立拥有自己的数据。

遵循"三五法则":如果需要自定义析构函数,通常也需要自定义拷贝构造和拷贝赋值;C++11之后还要考虑移动构造和移动赋值,合称"五法则"。现代C++更推荐用RAII封装资源(如unique_ptr),让编译器自动生成正确的拷贝/移动语义,或者显式delete拷贝操作。

3. std::vector的扩容机制是怎样的?频繁push_back导致性能问题时如何优化?

答:vector扩容时,当size等于capacity,会重新分配一块更大的内存(通常是当前capacity的1.5倍或2倍,不同实现不同),把所有元素移动或拷贝到新内存,然后释放旧内存。这个过程时间复杂度是O(n),但均摊下来每次push_back是O(1)。

扩容的代价:内存重新分配、元素移动(如果元素没有noexcept的移动构造,会退化成拷贝)、所有迭代器和指针失效。

优化手段:如果能预估元素数量,提前调用reserve(n)一次性分配足够容量,避免多次扩容;元素类型要提供noexcept的移动构造函数,让vector扩容时用移动而不是拷贝;如果元素是大对象,考虑存指针或用emplace_back直接在容器内构造,避免额外拷贝;如果大小固定,用std::array完全避免动态分配。

4. unordered_map的底层实现是什么?哈希冲突是如何处理的?什么情况下它会退化成O(n)?

答:unordered_map底层是哈希表,用开链法(拉链法)处理冲突,每个桶是一个链表(C++11标准要求),元素根据key的哈希值映射到对应桶,冲突的元素挂在同一个桶的链表上。

查找过程:计算key的hash,取模得到桶索引,遍历该桶的链表逐一比较key,平均O(1),最坏O(n)。

退化成O(n)的情况:哈希函数设计很差,大量key映射到同一个桶,链表变得很长;或者遭受哈希碰撞攻击(恶意构造大量相同哈希的key)。C++标准库对此有一定防护,但自定义哈希函数时要注意。另外,当负载因子(元素数/桶数)超过阈值时会触发rehash,重新分配桶数组并重新插入所有元素,这个过程是O(n)的,会造成偶发性延迟。

和map的选择:map底层红黑树,O(log n)但稳定;unordered_map平均O(1)但有最坏情况,且不保证顺序。需要有序遍历用map,纯查找性能优先用unordered_map,但要用好的哈希函数。

5. 请解释C++中的左值、右值、左值引用、右值引用,以及移动语义解决了什么问题?

答:左值是有名字、有持久地址的表达式,可以出现在赋值号左边;右值是临时的、没有持久地址的表达式,通常是字面量、临时对象、函数返回的非引用值。

左值引用(T&)只能绑定左值;右值引用(T&&)只能绑定右值(临时对象);const左值引用(const T&)可以绑定任何值,这是C++11之前延长临时对象生命周期的唯一手段。

移动语义解决的核心问题:C++11之前,函数返回大对象、容器插入元素,都会触发深拷贝,代价高昂。移动语义允许"窃取"临时对象的资源(比如直接接管其内部指针),而不是复制,原对象置为有效但未定义的状态。典型例子是std::string或std::vector的移动构造,只需要复制几个指针,O(1)完成,而拷贝构造是O(n)。

实际意义:函数返回局部vector,编译器会用RVO(返回值优化)或移动语义,不会有拷贝;往容器里插入临时对象,用emplace_back或传右值,触发移动而不是拷贝。

6. Linux中的管道、消息队列、共享内存三种IPC方式各有什么特点?在嵌入式项目中你会怎么选择?

答:管道(pipe/fifo)是字节流,有内核缓冲,读写是阻塞的,适合父子进程或有亲缘关系的进程间单向数据流,使用简单,但只能传字节流,没有消息边界,需要自己处理粘包。命名管道(FIFO)可以用于无亲缘关系的进程。

消息队列是有消息边界的,每条消息有类型,接收方可以按类型选择性接收,内核维护队列,进程崩溃后消息不丢失(除非系统重启)。适合需要消息分类、异步解耦的场景,但有消息大小限制,吞吐量不如共享内存。

共享内存是最快的IPC方式,多个进程映射同一块物理内存,读写直接操作内存,没有内核拷贝开销。但本身没有同步机制,需要配合信号量或互斥锁使用,编程复杂度最高,出错风险也最大。

嵌入式项目选择思路:数据量小、逻辑简单用管道;需要消息分类、解耦用消息队列;高频大数据量传输(比如视频帧在进程间传递)用共享内存+信号量。影石的全景相机场景,多个进程处理视频流,共享内存是最合适的,避免帧数据在进程间反复拷贝。

7. 什么是内存泄漏?除了忘记delete,还有哪些不容易发现的内存泄漏场景?

答:内存泄漏是申请的堆内存没有被释放,且没有任何指针指向它,导致这块内存永远无法被回收,进程内存持续增长。

容易被忽视的泄漏场景:

shared_ptr循环引用,A持有B的shared_ptr,B持有A的shared_ptr,引用计数永远不为0,两者都不会析构,这是C++项目里最常见的隐性泄漏。

基类析构函数不是虚函数,用基类指针delet

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论

相关推荐

03-26 12:00
已编辑
门头沟学院 Java
offer魅魔_oc...:100-200每天,你还要倒贴100
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
正在热议
更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务