c++oding="utf-8" ?>
面试官真正考察的是实战经验而非死记硬背,核心聚焦四点:指针与内存生命周期、构造/析构行为、虚函数与多态底层机制、stl容器真实行为;需能解释代码在调试器中的实际执行路径及内存布局约束。

面试官真正想确认的不是你背过多少题,而是你写过的每一行 new、delete、vector::push_back 和 dynamic_cast 有没有踩过坑、修过 bug、调过内存布局。C++ 面试问题高度收敛,90% 都落在四个硬核点上:指针与内存生命周期、对象构造/析构行为、虚函数与多态底层机制、STL 容器真实行为。背答案没用,得能讲清“为什么这行代码在调试器里跳进了析构函数”“为什么这个 shared_ptr 没释放”。
为什么空类 sizeof 是 1,而带虚函数就变成 8?
这不是编译器随意定的数字,是对象内存布局的刚性约束。空类必须占 1 字节,否则 sizeof(A) == 0 时,A a1, a2; 可能被分配到同一地址,违反 C++ 对象唯一性要求。一旦加了虚函数,编译器必须插入虚表指针(vptr),64 位系统下就是 8 字节——它不存数据,只存地址,指向该类型的虚函数表。注意:继承虚基类也会额外增加偏移字段,不是简单叠加;虚继承的派生类大小往往比预期大,因为要存虚基类表指针。
vector::push_back 迭代器失效的条件和代价
失效只发生在扩容时,即当前容量(capacity())不够用。此时 vector 会申请新内存、逐个拷贝/移动旧元素、销毁旧对象、释放旧内存。所有原有迭代器、指针、引用全部失效——不只是 end(),连 begin() 都不能继续用。关键点:
- 扩容策略不是固定 2 倍,GCC libstdc++ 用 1.5 倍,MSVC 用 2 倍,但都保证均摊 O(1) 复杂度
- 如果元素类型没有移动构造函数(比如含
std::mutex成员),扩容只能拷贝,性能更差 -
reserve()能预防失效,但不会改变size();resize()会改变size(),可能触发扩容
智能指针循环引用怎么破?
shared_ptr 的引用计数无法解决两个对象互相持有对方 shared_ptr 的情况。典型场景:父节点持子节点 shared_ptr,子节点又通过 shared_ptr 回指父节点。此时引用计数永远 ≥1,析构永远不会触发。解法只有两个:
- 一方改用
weak_ptr:子节点用weak_ptr持父节点,访问前调用lock()转成临时shared_ptr,避免悬挂 - 彻底不用智能指针管理生命周期:由外部容器或明确 owner 控制释放,子节点只存原始指针或引用(前提是 owner 生命周期严格长于子节点)
别信“用 enable_shared_from_this 就安全”——它只解决“从 this 创建 shared_ptr”的问题,不解决双向持有。
虚函数表(vtable)在哪?谁填?怎么查?
虚表是编译期生成的静态数组,每个含虚函数的类(或其子类)对应一张表,存在程序的只读数据段(`.rodata`)。表中存的是函数指针,顺序按虚函数声明顺序排列。关键事实:
- 每个对象实例开头 8 字节(64 位)就是
vptr,指向所属类的虚表 - 子类重写虚函数,虚表里对应槽位被替换成子类函数地址;新增虚函数则追加新槽位
- 调试时可用 GDB 查:
p/x *(void**)obj看vptr值,再x/5a *(void**)obj查前 5 个函数地址 - 纯虚函数在虚表中存的是
__cxa_pure_virtual地址,调用即 abort
最易忽略的是:虚表本身不参与对象拷贝,memcpy 一个含虚函数的对象会导致目标对象的 vptr 指向错误虚表——这是未定义行为,但编译器通常不报错。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











