第3章 技术面试:C/C++高频考点:指针与内存、虚函数、多态、STL

本章聚焦C/C++面试中最高频的三大模块:指针与内存管理、虚函数与多态、STL容器与算法。面试官常通过追问底层实现、内存布局和异常场景来考察你的“C++工程理解深度”。下面先给出考点全景总览,再逐一拆解典型问答。

考点类别核心考察点典型追问方向
指针与内存智能指针原理、内存泄漏、野指针、RAIIweak_ptr如何解决循环引用?new/delete vs malloc/free
虚函数与多态虚表指针、动态绑定、纯虚函数、构造/析构调用虚函数为什么构造函数不能是虚函数?析构函数为何要虚?
STL容器与算法容器特性、迭代器失效、reserve vs resize、sort实现vector扩容机制、map底层红黑树 vs unordered_map哈希表
C/C++考点回答框架 指针与内存 智能指针/RAII/内存布局 虚函数与多态 vptr/vtable/动态绑定 STL容器与算法 迭代器失效/扩容/排序 结构化回答:概念 + 代码 + 避坑 面试官期望:先给结论 → 简述原理 → 举代码例子 → 补充避坑经验

面试问答卡片

1. 请比较 shared_ptr 与 unique_ptr,它们如何管理内存?

面试官考察点: 智能指针底层原理、RAII思想、引用计数实现、性能与所有权语义。

回答框架: 首先指出两者都是RAII类,自动释放资源。接着说 unique_ptr 独占所有权,只可移动不可复制;shared_ptr 共享所有权,通过引用计数管理。然后提一句底层控制块实现。最后强调使用场景区别。

示例回答: “unique_ptr 独占所有权,只允许 move,性能接近裸指针;shared_ptr 通过控制块维护引用计数,支持复制,但额外代价是原子操作。我通常在明确唯一所有权时用 unique_ptr,多对象共享资源时用 shared_ptr,并用 weak_ptr 打破循环引用。”

可能追问: weak_ptr 如何解决循环引用?控制块默认存放在哪?自定义删除器如何传入?

避坑提醒: 不要只说“智能指针自动释放”,必须说出引用计数、循环引用场景和 weak_ptr 的 lock() 机制。千万别混淆 shared_ptr 与 unique_ptr 的移动/复制语义。

2. 为什么 C++ 中析构函数通常要声明为虚函数?

面试官考察点: 多态对象析构的正确性、虚函数表机制、对象切片风险。

回答框架: 先说明基类指针指向派生类对象时,非虚析构只调用基类析构,导致派生类资源泄漏。然后指出虚析构会通过虚表动态绑定到正确析构函数。最后补充如果类不打算作为基类,则不用虚析构以避免增加虚表开销。

示例回答: “当通过基类指针删除派生类对象时,如果析构函数非虚,只会调用基类的析构,派生类成员可能泄漏。声明为 virtual 后,通过 vptr 找到正确析构函数,实现完整销毁。注意:如果类不会被继承,就没必要加虚析构,避免额外开销。”

可能追问: 构造函数为什么不能是虚函数?虚析构函数在继承链中的调用顺序?纯虚析构函数怎么定义?

避坑提醒: 不要只说“虚析构是为了防止内存泄漏”,更要讲清楚虚表查找机制。举例时强调对象切片后果。避免回答“所有类都要加虚析构”,要分场景。

3. vector 的 push_back 可能导致迭代器失效,具体什么情况?如何避免?

面试官考察点: STL容器内存重分配机制、迭代器失效类型、reserve 优化、移动语义。

回答框架: 先说明 vector 在空间满时会重新分配更大内存,导致原有迭代器、指针、引用全部失效。然后给出避免方法:提前 reserve 足够空间,或改用索引而非迭代器。最后补充插入/删除操作也会使部分迭代器失效。

示例回答: “push_back 当 size == capacity 时会 reallocate,原迭代器指向旧内存,全部失效。我一般用 reserve 预分配容量;如果必须用迭代器,改用索引或等到所有插入操作完成后再获取新迭代器。注意 erase 会返回下一个有效迭代器。”

可能追问: vector 的扩容因子是多少(通常 1.5 或 2)?resize 和 reserve 区别?其他容器迭代器失效情况如何?

避坑提醒: 不要遗漏“插入操作也可能使部分迭代器失效”(如 insert 在中间)。回答时最好举例:for 循环中使用迭代器 push_back 导致死循环或崩溃。强调“先 reserve 再 push_back”是高性能编程习惯。

项目案例:基于智能指针的多线程任务调度系统

项目名称TaskFlow — 基于 shared_ptr / weak_ptr 的任务依赖调度器
业务背景IOT边缘计算节点需并发执行多个传感器数据解析、推理与上传任务,任务间存在依赖(如解析完成后才能推理),需动态管理任务生命周期,避免内存泄漏。
规模指标支撑 1000+ 任务节点并行调度,内存占用 < 200MB,任务平均延迟 < 50ms。
技术栈C++17、std::shared_ptr / weak_ptr、std::thread 池、std::function、std::unordered_map、std::vector。
核心难点1) 任务间循环依赖导致 shared_ptr 循环引用,内存泄漏;2) 任务执行时动态添加新依赖,需避免迭代器失效;3) 线程安全地修改任务状态。
面试高频追问- 你如何发现循环引用?用了什么工具(如 valgrind)?
- weak_ptr 的 lock() 返回空时如何处理?
- 任务容器你用了 vector 还是 list?为什么?
- 线程池如何避免任务对象析构时仍被线程执行?
可迁移回答话术“我在项目中通过将父任务持有子任务的 weak_ptr,子任务持有父任务的 shared_ptr,打破了循环。同时用 shared_from_this() 确保任务对象在回调中生存期合理。对任务容器提前 reserve 避免动态扩容导致迭代器失效,再结合生产者-消费者队列保证线程安全。”
面试加分提示: 在回答智能指针问题时,主动引出你的项目经历(如上述任务调度系统)能立刻让面试官看到你的“实战深度”。记得用“场景→方案→效果”来组织语言。