影石 | 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。

全部评论

相关推荐

又臭又长 12 题分享面经攒人品,感觉 ai 面就是一个大型的 agent,进入面试随机给你出问题,然后根据你的回答随机挑选关键词进行追问,然后进行下一轮的面试每题 (追问也是) 2min 回答,30s 准备时间1. 在任务完成过程中,主动找到核心问题,并提出解决方案的经历,当时是什么情况?你是怎么做的?请详细谈谈你的思路和策略。追问:在此基础上,我想了解您是如何和深入分析问题寻找根本原因的,能否请您详述描述一下在发现问题后,您是如何在一步一步深入分析找到问题核心所在,并最终确定解决方案的?2.在任务中定期检查进度发现问题并及时调整方向的经历,当时是什么情景?你是如何调整的?结果如何?追问:我想进一步了解的是在这次经历中您是否有对自己学习或改进的反思?例如您是否这次经历中提炼出了具体的改进点,并将其应用在后续的工作中3.分享某个新项目或活动被采纳的经历,你是如何推动起落地的又如何解决遇到的困难?追问:在此基础上,我想进一步了解您在推动这个新方案的过程中,是否有遇到过特别大的挑战或阻力,如果有零是如何克服这些困难,保证方案顺利实施的?4.回忆一次你发现自己的工作方式可能影响团队进度的经历。当你在寻求团队反馈并做出调整的细节。当时是什么情况?具体采取了什么措施?结果如何?追问:我想进一步了解的是这次调整中您是如何处理和协调团队成员之间不同观点和意见的你是如何确保团队成员理解和接受你的新工作方式,并共同推进项目向前发展?5.举一个实际的例子说明你在面对需求方有多个紧急需求时是如何评估优先及并做出回应的?当时是什么样的场景?你是如何分配时间和资源的?要是如何确保需求方对你的回复和解决方案满意的。追问:我想进一步了解在这个过程中,您是如何站在客户角度去理解和满足他们需求的比如您是如何保证他们的真实需要?以及你是如何确保这解决方案能够满足他们的预期?6.分享在项目或课程时需要学习到陌生的知识的经历。你是如何制定学习计划又是如何确保自己理解陌生知识的最终如何用在任务当中追问:在这过程中,我想了解你是否有过对学习内容深入理解和批判性思考,例如你是否对学到新知识有过疑惑或者是不同的见解?
查看12道真题和解析
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

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