高德地图-汽车业务-C++开发-二面 面经
1. 详细介绍一个你做过的最复杂的项目,架构是怎样的?
二面会对项目做深度追问,不能只说"做了什么",要能讲清楚为什么这样设计。
建议准备的内容:
- 整体架构图(能口头描述清楚模块划分)
- 自己负责的核心模块,技术选型的理由
- 遇到的性能瓶颈或设计缺陷,以及如何改进的
- 如果重新做这个项目,哪里会做得不一样
面试官会顺着你说的内容追问,所以只说自己真正理解的部分,不要夸大。
2. C++11 之后有哪些重要的新特性?你在项目中用过哪些?
C++11/14/17 引入了大量现代特性,常考的有:
- 移动语义和右值引用:避免不必要的深拷贝,大幅提升性能,配合 std::move 使用。
- 智能指针:unique_ptr、shared_ptr、weak_ptr,替代裸指针管理资源。
- lambda 表达式:匿名函数,配合 STL 算法和异步任务使用非常方便。
- auto 和 decltype:简化类型声明,提升泛型代码可读性。
- 范围 for 循环:简洁遍历容器。
- std::thread 和并发库:标准化多线程支持。
- constexpr:编译期计算,提升运行时性能。
- 变参模板:实现泛型工厂、tuple 等。
- std::optional(C++17):替代返回值为空的 nullptr 或魔法值,语义更清晰。
回答时结合项目说用过哪些,比单纯背特性更有说服力。
3. 移动语义是什么?什么时候会触发移动构造?
移动语义允许将资源的所有权从一个对象"转移"给另一个对象,而不是复制,避免了深拷贝的开销。
触发时机:
- 用右值(临时对象)初始化或赋值时,编译器优先选择移动构造/移动赋值
- 显式调用 std::move() 将左值转为右值引用时
- 函数返回局部对象时,编译器可能触发 RVO(返回值优化)或移动构造
实现移动构造的关键:接管源对象的资源指针,然后将源对象的指针置为 nullptr,防止析构时双重释放。
对于性能敏感的场景(如地图数据大对象的传递),合理使用移动语义能显著减少内存分配次数。
4. 说说 STL 中 map 和 unordered_map 的区别,底层实现是什么?
- map:底层是红黑树,有序存储,查找/插入/删除时间复杂度 O(log n),key 需要支持
<运算符。 - unordered_map:底层是哈希表,无序存储,平均查找/插入/删除 O(1),最坏 O(n)(哈希冲突严重时),key 需要支持
==和哈希函数。
选择原则:
- 需要有序遍历、范围查找 → map
- 只需要快速查找、不关心顺序 → unordered_map
- key 是自定义类型 → map 更容易实现(只需重载
<),unordered_map 需要自定义哈希函数
unordered_map 在极端情况下(哈希碰撞攻击)性能会退化,安全场景下要注意。
5. 什么是 RAII?在 C++ 中如何体现?
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 最核心的资源管理思想:将资源的生命周期绑定到对象的生命周期,构造时获取资源,析构时释放资源。
体现:
- 智能指针:对象离开作用域自动释放堆内存
- lock_guard/unique_lock:构造时加锁,析构时解锁,异常安全
- 文件流(fstream):构造时打开文件,析构时自动关闭
- 自定义资源类:封装 socket、数据库连接等,析构时自动清理
RAII 的核心价值是异常安全,即使中途抛出异常,栈上对象的析构函数也一定会被调用,资源不会泄漏。
6. 死锁是怎么产生的?如何预防和检测?
死锁产生的四个必要条件(Coffman 条件):
- 互斥:资源同一时刻只能被一个线程持有
- 持有并等待:线程持有资源的同时等待其他资源
- 不可剥夺:资源不能被强制抢占
- 循环等待:线程间形成环形等待链
预防方式:
- 固定加锁顺序:所有线程按相同顺序申请锁,破坏循环等待
- 使用 std::lock() 同时锁多个 mutex,原子操作避免部分加锁
- 设置超时:用 try_lock 或 timed_mute
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
