跳转至

💡 一分钟速览:C++ 高频面试题清单与简答:const、static、虚函数、智能指针、STL 底层、移动语义、多线程等,每题附深入讲解链接,覆盖 C/C++ 面试常考知识点。

最后更新:2026-11-10

C++ 面试题及答案

整理 C++ 面试中最常被问到的题目,每题给出可直接口述的简答,并附上深入讲解链接(含代码、底层实现与常见陷阱)。覆盖语言基础、面向对象、内存管理、STL 底层、现代 C++(11/17/20)、模板与并发编程,最后附 C++ 候选人仍需掌握的 C 语言面试题。

可作为面试前的自查清单,也可作为学习路线图:每题答案都链接到 C++那些事 对应的完整解析。

1. 语言基础

C++ 中的 const 是什么意思?常量指针与指针常量有何区别?

const 在编译期承诺不可变。const int* p(指向常量的指针)不允许通过 p 写入但可重新指向;int* const p(常量指针)不允许重新指向但可写入;const int* const p 两者都禁止。还要能区分编译期常量与 const 声明(C++ 中默认内链、C 中外链),以及什么时候该用 constexpr。

→ 深入阅读:C++ const 常量与修饰、C++ const 家族

static 有哪些用法?

四种含义:(1) 全局/函数级符号的内链接;(2) 函数内静态局部变量,仅初始化一次、全程存活;(3) 类的静态成员变量,所有对象共享;(4) 类的静态成员函数,无需对象即可调用。面试官常追问静态成员的初始化顺序,以及 C++11 起函数局部 static 初始化的线程安全保证。

→ 深入阅读:C++ static 静态成员与生存期

struct 和 class 的区别?

只有两点:默认访问权限(struct 为 public,class 为 private)与默认继承方式。构造函数、虚函数、模板等机制两者完全一致。怎么选是约定问题,不是机制问题。

→ 深入阅读:struct vs class

指针和引用的区别?

引用必须在初始化时绑定,之后不可为空、不可换绑;指针可以为空、可重新赋值、可做指针运算。参数传递中"必然有对象"用引用,"可能没有对象"用指针。

→ 深入阅读:C++ 指针与引用

inline 就一定能内联吗?

inline 只是对编译器的提示,是否内联由成本模型决定;它更重要的作用是允许在多个翻译单元中重复定义(解决 ODR 问题)。类内定义的成员函数隐式 inline。开了 LTO 后未标记的函数也可能被内联。

→ 深入阅读:C++ inline 内联函数

sizeof 测的是什么?有哪些坑?

返回类型的字节大小(含对齐填充),不测量指针背后的堆内存(sizeof(std::string) 是对象大小不是字符数)。注意结构体对齐规则与位域的意外结果。

→ 深入阅读:C++ sizeof 运算符、C++ 位域

volatile 能用于线程同步吗?

不能。volatile 只禁止编译器缓存/合并访问,不插入内存屏障,不提供原子性与 happens-before。线程间通信用 std::atomic 和锁;volatile 用于内存映射 I/O。

→ 深入阅读:C++ volatile 关键字

explicit 关键字的作用?

禁止单参构造函数(C++11 起也包括转换运算符)的隐式类型转换,经典例子是阻止 MyVec v = 10; 通过编译。任何"参数并不代表类型本身的值"的单参构造函数都应加 explicit。

→ 深入阅读:C++ explicit 关键字

union、宏、extern 还会被问吗?

会,尤其系统/嵌入式方向:union 的内存复用与 std::variant 的现代替代;宏无作用域无类型检查(优先 constexpr 与内联函数);extern "C" 关闭名字修饰实现 C/C++ 混编。

→ 深入阅读:C++ union · C++ 宏与条件编译 · C++ extern 链接说明

2. 面向对象与多态

虚函数的实现原理?什么是 vptr 和 vtable?

每个多态类有一张编译器生成的虚函数表(vtable),每个对象携带一个隐藏的 vptr 指向其动态类型的 vtable。虚调用 = 取 vptr → 查表槽位 → 间接调用,这就是运行期开销(一次间接寻址,并非"很慢")。构造函数不能为虚;基类析构函数必须为虚。

→ 深入阅读:C++ virtual 虚函数、C++ 虚指针与虚表机制

纯虚函数与抽象类有什么限制?

纯虚函数(= 0)表示无默认实现,所属类成为抽象类不可实例化。纯虚函数可以带实现体(派生类可显式调用)。常见追问:接口类的析构函数该用 protected 非虚还是 public 虚。

→ 深入阅读:C++ 纯虚函数与抽象类

用 C 语言能实现多态吗?

能——手工 vtable:把函数指针结构体作为结构体的第一个成员,子类共享前缀布局。理解这个例子就理解了 C++ 帮你生成了什么。

→ 深入阅读:C 语言实现 C++ 多态

this 指针在不同场景下的类型?

this 是指向当前对象的指针;在 const 成员函数中变为 const T*。在构造/析构期间其动态类型就是当前正在构造的类,所以此时调用虚函数不会分派到派生类实现。

→ 深入阅读:C++ this 指针

3. 内存管理

new/delete 与 malloc/free 的区别?

new 调用构造函数、返回带类型指针、失败抛异常(new(std::nothrow) 返回空);malloc 只分配裸字节。混用(对 new 的内存调 free)是未定义行为。常见追问:placement new、operator new 重载、对齐分配。

→ 深入阅读:C++ 工具与调试系列

智能指针作为参数该怎么传?

只在需要表达所有权语义时按值传智能指针:接收所有权用 unique_ptr<T>&&,仅使用对象传裸指针或引用。按值传 shared_ptr 会白白复制一次控制块。这是最实用的一道智能指针面试题。

→ 深入阅读:智能指针作为参数

生产环境怎么排查内存泄漏?

工具复现:小场景用 Valgrind memcheck,增长分析用 Massif/heaptrack,CI 用 ASan(-fsanitize=address)。根治靠 RAII:让每种资源都有归属。

→ 深入阅读:C++ 内存泄漏排查实战

什么是 EBO?空类为什么占 1 字节?

不同对象需要不同地址,所以 sizeof(空类) 至少为 1——这会让空基类/空成员产生填充。空基类优化(EBO)让空基类子对象占零空间,C++20 的 [[no_unique_address]] 对成员做同样的事。std::tuple 不膨胀靠的就是它。

→ 深入阅读:STL EBO 空基类优化

4. STL 底层

vector 的扩容机制?什么时候迭代器失效?

几何增长(×1.5~2):容量不足时分配新缓冲区、移动/拷贝元素、释放旧区——所有迭代器和指针失效。push_back 均摊 O(1)。reserve 只扩容量,resize 才改元素个数。

→ 深入阅读:STL 源码剖析:vector、vector resize vs reverse

map 与 unordered_map 的底层和选择?

map 是红黑树:有序遍历、O(log n)、迭代器稳定。unordered_map 是桶数组哈希表:平均 O(1)、rehash 使迭代器失效但指针仍有效、需要 hash + 相等比较。按是否需要有序来选,并了解两者的最坏情况。

→ 深入阅读:STL 红黑树 · STL map 与 multimap · STL 源码剖析:unordered_map

STL 的 hashtable 是怎么实现的?

素数个桶 + 链接法;C++11 起 unordered_map 把所有元素串成单链表。要掌握 rehash 策略(load_factor/max_load_factor),以及自定义 key 类型为什么需要特化 std::hash。

→ 深入阅读:STL hashtable、手写哈希表

什么是 traits?STL 为什么需要它?

traits 是关于类型的编译期元数据(如 iterator_traits<T>::value_type),让泛型代码自行适配:同一个 std::copy 依据迭代器类别走 memmove 快路径。这是通往模板元编程的入门题。

→ 深入阅读:STL traits 技术

deque 为什么是分段的?迭代器怎么实现?

deque 把存储切成固定大小的块,用块指针映射表管理,两端 push 都是 O(1),支持随机访问但内存不连续。其迭代器做两级解引用——经典白板实现题。

→ 深入阅读:STL 源码剖析:deque

5. 现代 C++(11/17/20)

std::move 到底做了什么?

运行期什么也不做:它只是到右值引用的强制转换,让重载决议选中移动构造/赋值。被移动的对象处于"有效但未指定"状态。移动语义转移资源(缓冲区、句柄)而非复制——现代 C++ 性能的核心。

→ 深入阅读:std::move 的原理

auto 与 decltype 的推导规则和坑?

auto 按值推导会丢引用和顶层 const;decltype(expr) 给出精确声明类型不做退化。经典坑:范围 for 里用 auto 持有临时容器的引用。decltype(auto) 保留精确类型。

→ 深入阅读:C++ decltype

现代C++ 的 lambda 有哪些演进?

值/引用捕获([])、mutable、泛型 lambda(C++14)、constexpr lambda 与模板参数列表(C++20)。要懂无捕获 lambda 可转函数指针、有捕获的不行的原因。

→ 深入阅读:std::bind vs lambda、C++20 Lambda(上)

string_view 解决什么问题?怎么用错?

非持有的字符借用:传递零拷贝零分配——但悬空视图(返回临时 string 的 view)与"不保证以 \0 结尾"是两大陷阱。按值传,绝不存得比持有者久。

→ 深入阅读:C++17 string_view

variant、union、继承怎么选?

variant 是类型安全的和类型:带索引、按候选取值(std::get_if、std::visit);union 无检查;虚继承加堆间接但可开放扩展。封闭的状态集合用 variant,常配套访问者模式追问。

→ 深入阅读:C++17 std::variant

Concepts 相比 SFINAE 好在哪?

Concepts(requires)让模板约束成为一等公民:错误信息可读、参与重载决议,取代了大多数 std::enable_if/表达式 SFINAE 的机关。

→ 深入阅读:C++20 Concepts

6. 模板与泛型

什么是两阶段查找?typename 消除什么歧义?

依赖名在实例化时才查找;typename 告诉解析器依赖限定名是类型(template 说明是模板)——C++20 前在许多场景必须写。

→ 深入阅读:STL 源码剖析:typename、模板消歧

变参模板怎么处理参数包?

递归展开(基础情形 + 包展开)或 C++17 折叠表达式:(f(args), ...)。配合 Args&&... 与 std::forward 完美转发——std::make_unique 和 emplace 系列的基础。

→ 深入阅读:变参模板

7. 并发编程

std::thread 与 jthread 的区别?

jthread 析构时自动 join(析构不再 terminate),并通过 std::stop_token 支持协作式取消。C++20 代码库优先用它。

→ 深入阅读:std::thread 与 jthread、std::thread 底层实现

怎么调试死锁?

死锁源于加锁顺序不一致。用 GDB all-stop 模式看全部线程栈、TSan/Helgrind 查 happens-before 违例;修复用 std::scoped_lock 一次锁多个、锁层级设计、超时重试。

→ 深入阅读:死锁调试 · GDB 多线程 all-stop · Helgrind 检查

什么是伪共享?怎么解决?

两个线程写不同变量却落在同一条 64 字节缓存行上,一致性流量让它们互相拖慢。解法:填充/alignas(64)(或 std::hardware_destructive_interference_size)。症状是并行反而更慢——高频进阶题。

→ 深入阅读:False Sharing 与缓存行

C++17 并行 STL 什么时候有效?

std::execution::par 并行化 69 个算法;单元素计算量要盖过调度开销才划算;注意跨线程迭代器有效性,以及不带 unseq 不会向量化。访存受限循环要配合 SIMD。

→ 深入阅读:C++17 并行 STL、C++ SIMD 向量化

8. C++ 候选人必会的 C 语言面试题

系统/嵌入式方向尤其常考:

字符串专题另见姊妹篇 std::string 面试题(SSO、ABI 兼容、转换等深入问题)。

怎么用这份清单备考

  1. 先自己口述作答,再读深入文章——面试考的是讲清楚,不是背下来。
  2. 按小节顺序系统复习,或直接跳到最薄弱的方向。
  3. 每篇深入文章的示例都对应真实 STL/libstdc++ 源码,能应对"那它底层到底怎么实现的?"这类追问。

有问题或想讨论答案?去 CPlusPlusThings 提 issue,或联系作者。

评论