SLAM 岗位 C++ 面试速查手册

张开发
2026/6/9 21:21:07 15 分钟阅读

分享文章

SLAM 岗位 C++ 面试速查手册
本文覆盖 SLAM 算法岗面试中最常见的 17 道 C 基础题每题给出标准答案 面试官常见追问的应对策略。面试速查30秒版知识点一句话核心面向对象 vs 面向过程OOP通过封装/继承/多态管理复杂度POP以函数为中心更高效指针 vs 引用引用是别名不可变不可空指针是地址可重指可为空多态运行时多态靠虚函数表(vtable)实现动态绑定虚/纯虚函数虚函数有默认实现可覆盖纯虚函数无实现必须覆盖析构函数为虚基类指针delete派生对象时确保析构完整4种类型转换static快但不安全dynamic慢但安全(RTTI)const去常量reinterpret底层位模式智能指针unique独占shared共享(引用计数)weak打破循环引用锁mutex互斥shared_mutex读写condition_variable条件等待堆 vs 栈栈自动管理快速连续堆手动管理灵活离散STL容器vector连续随机访问list离散快速插删map有序log(n)unordered_map哈希O(1)vectorbool是位压缩特化非真正容器不支持指针/引用resize vs reserveresize改变size(可访问)reserve改变capacity(预分配)左值/右值左值有地址可取右值是临时量右值引用实现移动语义避免拷贝1. 面向过程 vs 面向对象编程答案面向过程POP以函数/过程为核心组织代码数据和操作分离。优点执行效率高、逻辑直观、适合小程序缺点数据和函数耦合松散大型系统难维护面向对象OOP以类/对象为核心通过封装、继承、多态组织代码。优点高内聚低耦合、代码复用、适合大型系统缺点设计开销大、运行时有虚函数表等间接开销SLAM 工程中的体现PCL、OpenCV 大量使用面向对象继承体系但 FAST-LIO2 中so3_math.h的模板函数是面向过程风格追求效率实际工程中两者结合框架用 OOP核心计算用 POP/模板2. 指针和引用的区别答案5个核心区别维度指针引用本质存储变量地址的变量变量的别名可否为空可以为 nullptr不能为空声明时必须初始化可否重新绑定可以指向其他对象一旦绑定不可更改sizeof指针本身大小64位8字节所引用对象的大小多级有多级指针int**无多级引用自增地址偏移值递增追问什么时候用指针什么时候用引用参数可能为空 → 指针需要重新绑定/多态所有权 → 指针函数参数传递避免拷贝 → const引用运算符重载返回值 → 引用3. 什么是多态虚函数和纯虚函数的区别答案多态同一接口不同实现。分为编译时多态函数重载、模板运行时多态虚函数 基类指针/引用虚函数 vs 纯虚函数维度虚函数纯虚函数声明virtual void f() {}virtual void f() 0;基类能否实例化能不能抽象类是否必须重写不必须派生类必须重写用途提供默认实现定义接口规范虚函数表vtable机制Base* p new Derived(); p-func(); // 通过 vtable 查找实际调用的函数 对象内存布局 ------------------ | vptr → vtable | ← 指向虚函数表的指针 | member data | ------------------ vtable: ------------------ | Derived::func() | ← 被覆盖指向派生类实现 | Base::other() | ← 未覆盖仍指向基类 ------------------4. 为什么基类析构函数建议定义为虚函数答案Base*pnewDerived();deletep;// 如果~Base()不是虚函数只调用Base的析构Derived部分内存泄漏当通过基类指针delete派生类对象时非虚析构只调用基类析构函数 → 派生类资源泄漏虚析构通过 vtable 找到派生类析构函数正确释放所有资源注意纯虚析构函数virtual ~Base() 0;也必须提供定义因为派生类析构会隐式调用基类析构。5. C 的4种类型转换答案转换用途运行时开销安全性static_cast编译时已知的类型转换基本类型、上行转换无中不检查运行时类型dynamic_cast多态类的安全下行转换有RTTI高失败返回nullptrconst_cast去除/添加 const/volatile无低滥用导致UBreinterpret_cast底层位模式重解释无最低6. 基类指针调用派生类对象时用哪种转换答案下行转换Base→ Derived用dynamic_cast**Base*basegetObject();Derived*ddynamic_castDerived*(base);if(d!nullptr){d-derivedMethod();}需要基类有虚函数开启RTTI失败时返回nullptr指针或抛出bad_cast引用上行转换Derived→ Base用static_cast** 或隐式转换即可。7. dynamic_cast vs static_cast 哪个效率高答案static_cast效率更高。原因static_cast编译时完成零运行时开销dynamic_cast需要 RTTIRun-Time Type Information运行时遍历类继承链检查类型有开销但static_cast的下行转换是不安全的不检查实际类型如果类型不匹配会导致未定义行为。SLAM 工程实践高频调用如每个点的处理→ 用static_cast确保类型正确的前提下低频调用如传感器类型判断→ 用dynamic_cast安全优先8. 智能指针有哪几种区别是什么答案智能指针语义引用计数典型场景unique_ptr独占所有权无工厂函数返回值、独占资源shared_ptr共享所有权有线程安全的原子计数多处共用同一资源weak_ptr弱引用不增加计数观察shared_ptr打破循环引用、缓存SLAM 工程中的实例// FAST-LIO2 中shared_ptrPreprocessp_pre(newPreprocess());// 多处使用预处理器shared_ptrImuProcessp_imu(newImuProcess());// 多处使用IMU处理器// 点云用裸指针PCL的Ptr本质上是shared_ptrPointCloudXYZI::Ptrfeats_undistort(newPointCloudXYZI());// PCL中 Ptr boost::shared_ptrPointCloud追问shared_ptr 的性能问题引用计数的原子操作有开销多线程下尤其明显内存布局控制块计数器和数据可能不连续除非用make_sharedmake_shared一次分配控制块对象更高效9. unique_ptr 什么时候允许赋值答案unique_ptr禁止拷贝删除了拷贝构造和拷贝赋值但允许移动unique_ptrintp1make_uniqueint(42);// unique_ptrint p2 p1; // ❌ 编译错误unique_ptrintp2std::move(p1);// ✅ 移动p1变为nullptr编译器允许的赋值场景显式std::move如上函数返回局部变量NRVO/移动语义unique_ptrFoocreateFoo(){autopmake_uniqueFoo();returnp;// ✅ 编译器自动move返回值优化}临时对象赋值unique_ptrintpmake_uniqueint(10);// ✅ 右值直接构造10. C 中提供了哪些锁答案锁/同步原语头文件特点适用场景std::mutexmutex基本互斥锁非递归通用互斥访问std::recursive_mutexmutex可递归加锁同线程多次lock递归函数中的互斥std::timed_mutexmutex支持超时的互斥锁避免死锁等待std::shared_mutex(C17)shared_mutex读写锁多读单写读多写少场景std::condition_variablecondition_variable条件等待生产者-消费者std::atomicatomic无锁原子操作简单计数器/标志位SLAM 工程实例// FAST-LIO2 中的数据缓冲区保护mutex mtx_buffer;condition_variable sig_buffer;voidimu_cbk(...){mtx_buffer.lock();imu_buffer.push_back(msg);mtx_buffer.unlock();sig_buffer.notify_all();// 通知主循环有新数据}RAII锁管理// 推荐用 lock_guard/unique_lock不要手动 lock/unlock{std::lock_guardstd::mutexlock(mtx_buffer);// 构造时加锁析构时解锁imu_buffer.push_back(msg);}// 离开作用域自动解锁异常安全11. 堆与栈的区别从内存管理角度维度栈 (Stack)堆 (Heap)分配/释放编译器自动管理LIFO程序员手动(new/delete)或智能指针速度极快移动栈指针较慢系统调用/内存碎片大小有限通常1-8MB很大受物理内存限制地址增长向低地址增长向高地址增长碎片无有外部碎片生命周期函数结束自动回收手动释放或引用计数为0从数据结构角度维度栈 (Stack)堆 (Heap)结构LIFO后进先出完全二叉树优先队列操作push/pop O(1)insert/extract O(log n)应用函数调用、DFS、括号匹配优先队列、TopK问题STLstd::stackstd::priority_queue12. STL 容器有哪些答案类别容器底层实现随机访问插入/删除序列vector动态数组O(1)尾部O(1)中间O(n)序列deque分段数组O(1)头尾O(1)中间O(n)序列list双向链表O(n)任意位置O(1)已定位关联map/set红黑树—O(log n)关联unordered_map/set哈希表—平均O(1)最差O(n)适配器stack/queue/priority_queue封装底层容器——13. vectorint vs vectorbool 的区别答案vectorbool是一个特化版本不是真正的 STL 容器维度vectorintvectorbool存储每个元素占4字节每个元素占1 bit位压缩operator[]返回int真正的引用返回代理对象reference非真引用取地址v[0]合法v[0]不合法与C数组互操作v.data()可用不支持迭代器随机访问迭代器特殊迭代器替代方案// 如果需要真正的bool容器std::dequebool// 不做位压缩std::vectorchar// 用char模拟boolstd::bitsetN// 编译时大小确定的位集合14. resize 和 reserve 的区别答案vectorintv;v.reserve(100);// capacity100, size0, 不能v[50]访问v.resize(100);// capacity≥100, size100, 可以v[50]访问值初始化为0操作改变 size改变 capacity可访问元素构造元素reserve(n)否是≥n不变否resize(n)是n可能如果ncapacity增加/减少是默认构造SLAM 实践// FAST-LIO2 中预分配避免频繁重新分配Nearest_Points.resize(feats_down_size);// 需要访问每个元素PointToAdd.reserve(feats_down_size);// 只是预分配空间后续push_back15. map vs unordered_map答案维度std::mapstd::unordered_map底层红黑树自平衡BST哈希表拉链法/开放寻址查找/插入/删除O(log n)平均 O(1)最差 O(n)有序性key有序遍历无序内存每节点含左右指针颜色bucket数组链表key要求需要operator需要hashoperator时间效率选择绝大多数场景用unordered_mapO(1) vs O(log n)需要有序遍历时用map数据量小100时map可能更快cache友好、无哈希开销哈希冲突与性能退化// 最差情况所有key哈希到同一bucket → O(n)// 解决// 1. 好的哈希函数// 2. 预留足够bucket: m.reserve(n)// 3. 控制负载因子: m.max_load_factor(0.7)16. 频繁插入中间且频繁访问用什么容器答案这是一个经典的 tradeoff 问题容器中间插入随机访问vectorO(n)需要搬移O(1)listO(1)已定位O(n)不支持随机访问dequeO(n)中间插入O(1)推荐方案std::deque如果插入主要在头尾附近deque 是好选择std::list 缓存迭代器如果确实需要频繁中间插入用 list 并缓存常用位置的迭代器std::vector 标记删除如果访问极频繁而插入不多用 vector 惰性删除标记而非真正删除考虑std::deque或boost::container::flat_set分段连续内存折中方案面试追问的标准答案如果同时需要快速随机访问和快速中间插入考虑std::deque折中或根据实际访问模式选择。没有完美方案需要根据具体的 读/写比例 权衡。17. 左值、右值与右值引用答案左值 (lvalue)有持久身份的表达式可以取地址xintx10;// x 是左值intrefx;// 左值引用右值 (rvalue)临时的、即将销毁的表达式intrref10;// 右值引用绑定到临时量intrref2x1;// x1 的结果是临时量右值右值引用的使用场景1. 移动语义避免深拷贝classPointCloud{vectorPointpoints;public:// 移动构造函数偷走临时对象的资源PointCloud(PointCloudother)noexcept:points(std::move(other.points)){}};PointCloudprocessCloud(){PointCloud cloud;// ... 处理returncloud;// 移动而非拷贝或NRVO直接省略}2. 完美转发模板编程templatetypenameTvoidwrapper(Targ){// 万能引用realFunction(std::forwardT(arg));// 完美转发保持左/右值性}3. STL容器的 emplace 系列vectorPoseposes;poses.emplace_back(rotation,translation);// 直接原地构造无临时对象// 比 push_back(Pose(rotation, translation)) 少一次移动SLAM 中的实际应用// FAST-LIO2 中 set_pose6d 使用移动语义returnmove(rot_kp);// 避免拷贝 Pose6D 结构体// Eigen 矩阵的移动Eigen 3.4 支持移动语义Eigen::MatrixXdcomputeH(){Eigen::MatrixXdH(n,12);// ... 计算returnH;// 移动返回无需拷贝大矩阵}附SLAM工程中的C最佳实践总结场景推荐做法点云传递PointCloud::Ptrshared_ptr矩阵参数const Eigen::Refconst MatrixXd数据缓冲区std::dequemutexcondition_variable高频小对象栈分配 or 对象池配置参数const 传递回调函数std::functionor 模板无虚函数开销并行计算OpenMP#pragma omp parallel for内存对齐EIGEN_MAKE_ALIGNED_OPERATOR_NEW面试真题与答题要点Q: 面试官“说说 SLAM 项目中你用到了哪些 C 特性”答题模板在我的 FAST-LIO2 项目中模板编程SO(3) 数学库全部用模板实现so3_math.h支持 float/double智能指针点云用shared_ptr管理生命周期避免手动内存管理多线程IMU回调和主处理循环通过mutexcondition_variable同步STL容器IMU缓冲区用deque双端操作最近邻结果用vector随机访问Eigen内存对齐EIGEN_MAKE_ALIGNED_OPERATOR_NEW避免SSE指令的段错误OpenMP并行点云配准中的最近邻搜索和残差计算并行化延伸阅读书籍Scott Meyers, “Effective Modern C”C11/14最佳实践书籍Anthony Williams, “C Concurrency in Action”并发编程参考cppreference.com权威语言参考实践阅读 PCL / Eigen / Sophus 源码学习模板和内存管理

更多文章