C++ 是一门庞大且复杂的语言。为了帮你高效复习,我将 C++ 基础知识体系化为 7 大核心模块。这份清单涵盖了从语法基础到现代 C++(C++11/14/17/20)的关键特性,适合作为面试准备或项目开发的自查表。


1. 基础语法与数据类型

这是 C++ 的基石,重点在于理解“底层”行为。

  • 基本类型: int, float/double, char, bool, void。注意各类型在不同平台下的字节大小取值范围
  • 类型修饰符: const(只读)、volatile(防优化)、signed/unsignedstatic(静态存储期/局部可见性)。
  • 引用 vs 指针:
    • 指针: 可为空、可重新赋值、有二级指针、需手动管理内存。
    • 引用: 必须初始化、不可更改绑定、无空引用、更安全。
  • 类型转换:
    • C风格:(type)value(不安全)。
    • C++风格:static_cast(编译期安全转换)、dynamic_cast(运行时多态转换)、const_cast(去const)、reinterpret_cast(二进制重解释,危险)。
  • 作用域与生命周期: 全局、局部、块级作用域;栈对象自动销毁 vs 堆对象手动销毁。

2. 面向对象编程 (OOP)

C++ 的核心范式,面试和工程中的重中之重。

  • 封装: public/protected/private 访问控制;friend 友元机制。
  • 继承: 单继承、多继承(注意菱形继承问题及虚继承 virtual 解决方案)。
  • 多态:
    • 编译期多态: 函数重载、运算符重载、模板。
    • 运行期多态: 虚函数 (virtual)、纯虚函数 (=0)、抽象类。
    • ⚠️ 关键点: 基类析构函数必须声明为 virtual,否则通过基类指针删除派生类对象会导致未定义行为(内存泄漏)。
  • 构造与析构:
    • 构造函数初始化列表 vs 函数体内赋值(性能差异)。
    • 拷贝构造函数、移动构造函数。
    • 拷贝赋值运算符、移动赋值运算符。
    • Rule of Five / Rule of Zero: 如果定义了析构/拷贝/移动中的任何一个,通常需要定义全部五个;或者使用智能容器让编译器自动生成。

2.1 多态
多态的实现是基于虚函数virtual
C++ 的虚函数表(Virtual Table,简称 vtable)机制是实现运行时多态(Runtime Polymorphism)的核心底层技术。当你在 C++ 中使用 virtual 关键字时,编译器通常会自动生成 vtable 和相关的指针来支持动态绑定。

以下是该机制的深度解析:

1. 核心概念

  • vtable (虚函数表): 本质上是一个静态数组(或类似结构),存储在程序的只读数据段中。每个包含虚函数的类都有自己独立的一张 vtable。表中存储了该类所有虚函数的函数指针
  • vptr (虚表指针): 一个隐藏的指针成员,被编译器自动插入到每个包含虚函数的类的对象实例中。它指向该对象所属类的 vtable。

2. 内存布局模型

类的 vtable 结构

假设有一个基类 Base 和一个派生类 Derived

class Base {
public:
    virtual void funcA();
    virtual void funcB();
    void nonVirtual(); // 不在vtable中
};

class Derived : public Base {
public:
    void funcA() override; // 重写
    virtual void funcC();  // 新增虚函数
};

内存中的 vtable 大致如下:

Base::vtable Derived::vtable
[0] &Base::funcA [0] &Derived::funcA (被覆盖)
[1] &Base::funcB [1] &Base::funcB (继承)
[2] &Derived::funcC (新增)

关键点Derived 的 vtable 中,funcA 的位置与 Base 中相同,但指向的是 Derived::funcA 的实现。这保证了通过基类指针调用时能正确索引。

对象的内存布局

当你创建 Derived d; 时,对象内存通常如下(以单继承为例):

+------------------+
| vptr             | -----> Derived::vtable
+------------------+
| Base 成员变量     |
+------------------+
| Derived 成员变量  |
+------------------+

3. 虚函数调用过程

当你执行 basePtr->funcA() 时,CPU 实际执行的步骤是:

  1. 从对象起始地址读取 vptr
  2. 通过 vptr 找到对应的 vtable
  3. 根据 funcA 在表中的固定偏移量(offset)取出函数指针。
  4. 跳转到该函数指针指向的地址执行代码。

伪代码表示:

// basePtr->funcA() 等价于:
(*(basePtr->vptr)[OFFSET_OF_funcA])(basePtr);

4. 复杂场景处理

多重继承 (Multiple Inheritance)

如果一个类继承了多个带虚函数的基类,对象中会有多个 vptr,分别指向不同基类子对象的 vtable。

+------------------+
| vptr (Base1)     | -----> Base1_vtable / Derived_vtable_for_Base1
+------------------+
| Base1 成员        |
+------------------+
| vptr (Base2)     | -----> Base2_vtable / Derived_vtable_for_Base2 (可能带偏移调整)
+------------------+
| Base2 成员        |
+------------------+
| Derived 自有成员  |
+------------------+

当通过 Base2* 指针调用虚函数时,编译器可能需要使用 Thunk 技术来调整 this 指针,使其正确指向 Derived 对象的完整起始地址。

虚继承 (Virtual Inheritance)

为了解决菱形继承问题,虚继承引入了额外的间接层。vtable 中不仅包含虚函数指针,还可能包含虚基类偏移量(vbptr/vbtable),用于在运行时定位共享的虚基类子对象。这使得虚继承的对象布局和调用开销比单继承/多重继承更大。

5. 性能与开销

方面 说明
空间开销 每个含虚函数的类多一张 vtable;每个对象多一个 vptr(通常 8 字节/64位系统)
时间开销 每次虚函数调用增加 1-2 次内存间接寻址(读 vptr → 读 vtable → 跳转)
内联优化 虚函数通常无法内联,因为编译器在编译期不知道实际调用的目标函数(除非能静态推断出具体类型)
缓存友好性 vtable 访问可能导致 cache miss,尤其在大量不同类型对象混合调用时

6. 重要注意事项

  • 非标准规定: vtable 是编译器的实现细节,C++ 标准并未规定必须使用 vtable(尽管几乎所有主流编译器都这么做)。
  • ABI 兼容性: 不同编译器(甚至同一编译器的不同版本/选项)生成的 vtable 布局可能不同,这就是为什么跨编译器链接 C++ 代码经常出问题。Itanium C++ ABI 试图标准化这一布局。
  • 构造/析构期间: 在构造函数和析构函数执行期间,vptr 会逐步更新。不要在构造/析构函数中调用虚函数,因为此时 vptr 可能尚未指向最终派生类的 vtable,导致行为不符合预期。
  • RTTI 关联: typeiddynamic_cast 通常也依赖 vtable 中的额外信息(如 typeinfo 指针)来实现运行时类型识别。

总结

vtable 机制是用空间换时间的经典设计:通过在对象中嵌入指针、在类中维护函数指针表,将“调用哪个函数”的决策从编译期推迟到运行期,从而实现了面向对象编程中最核心的多态特性。理解它有助于写出更高效的 C++ 代码,并在调试复杂继承问题时快速定位根源。

3. STL 标准模板库

不要重复造轮子,熟练掌握 STL 是 C++ 工程师的基本素养。

分类 常用组件 关键知识点
容器 vector, deque, list, array 内存布局、扩容机制(vector 1.5x/2x)、迭代器失效条件
关联容器 map/set, unordered_map/set 红黑树 vs 哈希表、查找复杂度 O(logN) vs O(1)
适配器 stack, queue, priority_queue 底层容器选择、优先队列的比较器
算法 sort, find, transform, accumulate Lambda 配合、自定义比较器、时间复杂度
迭代器 Input/Output/Forward/Bidirectional/RandomAccess 迭代器类别决定可用算法

⚠️ 避坑指南: 永远不要在遍历容器时直接 erase,应使用 erase-remove 惯用法或 C++20 的 std::erase_if

4. 内存管理

C++ 区别于其他高级语言的核心难点。

  • 栈 vs 堆: 栈由编译器管理(快、小),堆由程序员管理(慢、大)。
  • 原始指针: new/delete, new[]/delete[](必须配对使用)。
  • 智能指针(现代 C++ 必用):
    • std::unique_ptr:独占所有权,零开销,推荐默认使用。
    • std::shared_ptr:引用计数共享所有权,注意循环引用问题。
    • std::weak_ptr:解决 shared_ptr 循环引用,不增加引用计数。
  • RAII: 资源获取即初始化。将资源生命周期绑定到对象生命周期,确保异常安全。
  • 内存对齐: alignof, alignas,结构体填充规则。

5. 模板与泛型编程

C++ 最强大的特性之一,也是编译错误的重灾区。

  • 函数模板 & 类模板: 模板参数推导、特化(全特化/偏特化)。
  • SFINAE: “Substitution Failure Is Not An Error”,std::enable_if
  • Concepts (C++20): 约束模板参数,替代 SFINAE,大幅提升可读性和错误信息质量。
  • 可变参数模板: template<typename... Args>,完美转发 std::forward
  • 编译期计算: constexpr, consteval (C++20), if constexpr

6. 现代 C++ 核心特性 (C++11 ~ C++20)

如果你还在写 C++98 风格的代码,请务必更新知识库。

  • C++11: auto, nullptr, 范围for循环, Lambda, std::move, 右值引用, override/final, enum class, std::thread
  • C++14: 泛型Lambda (auto参数), std::make_unique, 返回值推导。
  • C++17: std::optional/variant/any, 结构化绑定 auto [x,y] = ..., if constexpr, 并行算法, std::string_view
  • C++20: Modules (取代头文件), Coroutines (协程), Ranges (惰性求值管道), Concepts, std::format, 三路比较 <=>
  • C++23/26 展望: std::print, std::expected, 反射提案等。

7. 并发编程

  • 线程管理: std::thread, std::jthread (C++20, 自动join)。
  • 同步原语: std::mutex, std::lock_guard, std::unique_lock, std::scoped_lock (C++17, 防死锁)。
  • 条件变量: std::condition_variable,注意虚假唤醒。
  • 原子操作: std::atomic,内存序 (memory_order)。
  • 异步: std::future/promise, std::async

更多推荐