一文梳理 C++ 所有核心知识点
前言:C++ 的定位与学习难点
现在的 3A 游戏、手机操作系统内核、Chromium 浏览器内核、高频交易系统、自动驾驶、AI 框架 PyTorch 底层均由 C++ 开发。
C++ 核心生态位:性能极致敏感、每一纳秒都需要优化的底层场景。
为什么 C++ 难学
- 四十余年发展历史,标准文档超两千页,特性繁杂;
- 语言把内存、硬件控制权完全交给开发者,自由度高但坑极多;
- 学习曲线陡峭,底层细节需要手动管理。
C++ 工程师优势
游戏引擎、音视频、自动驾驶、嵌入式、量化交易、底层架构岗位高度依赖 C++,技术壁垒高,经验越久竞争力越强。
本文搭建完整 C++ 知识地图,所有特性对应要解决的实际问题,无需死记硬背。
第 1 章 一个程序是怎么跑起来的?
1.1 编译型语言核心区别
cpp
运行
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
- Python:解释执行,边翻译边运行,灵活但持续存在翻译开销;
- C++:预编译→编译→汇编→链接,一次性转为 CPU 机器码,运行无额外开销,同等计算任务比 Python 快几十至数百倍。
编译四大阶段(面试必考)
-
预处理 Preprocessing
处理
#
开头指令:
#include
复制头文件全部文本、
#define
纯文本替换;预处理后代码会大幅膨胀。
-
编译 Compilation
语法校验、类型检查,C++ 代码转为汇编代码;少分号、类型不匹配、未定义变量等报错均在此阶段(编译错误)。
-
汇编 Assembly
汇编翻译为二进制机器码,生成
.o
目标文件;每个
.cpp
独立生成目标文件,文件间互相隔离。
-
链接 Linking
合并多个目标文件 + 第三方库,生成最终可执行程序;跨文件函数调用、全局符号统一寻址。
- 经典报错:
undefined reference 声明函数但未实现,编译通过、链接失败。
1.2 main 函数与基础变量
main是程序唯一入口,return 0向操作系统标记程序正常退出。
- C++ 是静态强类型语言:变量声明时必须指定类型,类型运行不可变更。
表格
| 类型 |
示例 |
占用字节 |
说明 |
| int |
int age = 18; |
4 |
整数,范围约 ±21 亿 |
| long long |
long long big = 1e18; |
8 |
超大整数 |
| double |
double price = 9.9; |
8 |
默认浮点数,高精度 |
| float |
float ratio = 0.5f; |
4 |
单精度浮点,节省内存 |
| char |
char c = 'A'; |
1 |
单字符,底层本质数字 |
| bool |
bool ok = true; |
1 |
布尔真假 |
新手三大坑
- 整数溢出:int 超出范围会静默溢出变成负数,无运行报错;
- 整数除法:
5 / 2 = 2,小数直接截断,需要浮点结果必须强转;
- 未初始化局部变量:栈变量默认是随机垃圾值,不会自动置 0,声明务必初始化。
标准 IO 流
- 控制台:
std::cin >> x 输入,std::cout << x 输出,箭头代表数据流向;
- 文件读写:
std::ifstream / std::ofstream,API 与控制台流统一;
- 设计思想:流(stream),将所有 IO 抽象为数据流。
1.3 控制流
if/for/while/switch/函数基础控制逻辑与通用编程语言一致,不再赘述;C++ 真正核心特性集中在内存、面向对象、模板、现代 C++ 等章节。
第 2 章 内存:C++ 的命根子
内存模型是 C++ 所有高级特性的底层地基。
2.1 程序运行四大内存分区
程序加载后操作系统分配内存,分为 4 大区域:
- 代码区:只读,存放编译后的机器指令;
- 全局 / 静态区:存放全局变量、
static静态变量,生命周期与程序一致;
- 栈 Stack:系统自动管理,函数局部变量存放处;
- 堆 Heap:开发者手动申请释放,大容量长期数据存放处。
栈(自动管理)
- 每调用一个函数生成栈帧,局部变量全部在栈帧内;函数执行完毕自动回收内存;
- 分配速度极快:仅移动栈顶指针,单 CPU 指令完成;
- 两大限制:
- 空间极小(通常几 MB),超大数组直接栈溢出(Stack Overflow);
- 生命周期绑定函数,函数退出变量立刻销毁。
堆(手动管理 new/delete)
- 空间充足(GB 级别),生命周期完全由开发者控制;
- 语法:cpp
int* p = new int(42); // 单个堆变量
delete p; // 释放单个变量
int* arr = new int[100];// 堆数组
delete[] arr; // 数组必须带[]释放
手动内存三大经典故障
- 内存泄漏:
new后忘记delete,内存长期占用,服务长时间运行会 OOM;
- 悬空指针:内存
delete后继续访问,内存内容随机,行为未定义;
- 重复释放:同一块内存多次
delete,程序直接崩溃。
C++ 80% 的线上 bug 均来自裸指针手动堆内存管理,第 4 章 RAII 与智能指针为根治方案。
2.2 指针
定义:内存每个字节拥有唯一地址(门牌号),指针是专门存储内存地址的变量,不存储数据本身。
int a = 10;
int* p = &a; // &取地址:p存储a的内存地址
*p = 20; // *解引用:通过地址修改原变量,此时a=20
- 64 位系统下所有指针固定占用 8 字节,与指向数据大小无关;
- 核心价值:传递地址代替完整拷贝,大幅降低大对象传参性能损耗。
指针危险分类
- 空指针 nullptr:不指向任何有效内存,未判空直接解引用程序崩溃;
- 野指针:声明未初始化,存储随机地址,崩溃无规律,排查难度极高;
- 悬空指针:指向内存已被
delete释放。
数组与指针关系
数组名在绝大多数场景退化为指向首元素的指针;arr[i]等价于*(arr + i),下标本质内存偏移,因此数组从 0 开始计数。
内存对齐
编译器会将结构体成员按 4/8 字节边界对齐,成员间填充空白内存,sizeof(struct)结果通常大于所有成员大小之和。
2.3 引用(安全版指针)
引用是变量的别名,底层基于指针实现,但语法强制安全约束:
int a = 10;
int& r = a; // r是a的别名
r = 20; // 修改r等价修改a
引用三大强制规则(杜绝野 / 空指针问题):
- 声明时必须初始化;
- 生命周期内不能更换绑定对象;
- 不存在空引用。
2.4 函数传参三种方式
void f1(std::string s); // 传值:完整拷贝,大对象性能差
void f2(std::string* s); // 传指针:无拷贝,需判空、解引用
void f3(std::string& s); // 传普通引用:无拷贝,可修改原对象
void f4(const std::string& s); // const常量引用:无拷贝、只读,日常首选
传参决策口诀
- 小型基础类型(int/double):直接传值;
- 大容器 / 自定义对象:默认
const &常量引用;
- 需要修改原对象:普通引用;
- 参数允许为空指针场景:使用裸指针。
2.5 const 关键字
const代表只读约束,编译器强制校验:
- 修饰变量:常量,不可修改;
- 修饰函数参数:承诺函数内部不修改外部数据;
- 修饰成员函数:承诺函数不会修改对象成员状态。
本章总结
栈自动回收、堆手动管理;指针存地址、引用是别名;函数传参优先const引用;C++ 半数高级特性都是为解决手动堆内存痛点而生。
第 3 章 面向对象:C++ 的半壁江山
C++ 最初名称为C with Classes,类是 C++ 对比 C 语言最核心扩展。
3.1 类与对象
类 = 图纸;对象 = 根据图纸实例化的实体。
class Player {
private:
// 私有成员:外部无法直接访问
int hp = 100;
int atk = 15;
public:
// 公开接口
void attack(Player& target) { target.takeDamage(atk); }
void takeDamage(int dmg) {
hp -= dmg;
if (hp < 0) hp = 0;
}
bool isDead() const { return hp == 0; }
};
Player p1, p2; // 实例化两个独立玩家对象
p1.attack(p2);
struct vs class 唯一区别
struct:成员默认public,仅用于纯数据结构体;
class:成员默认private,用于带行为、封装逻辑的业务对象。
3.2 封装(面向对象第一支柱)
核心思想:隐藏内部私有数据,仅暴露受控公开接口。
- 优势:所有数据修改统一走固定入口,统一增加校验、日志、业务逻辑,bug 排查范围缩小;
friend友元:少量场景开放私有成员访问,慎用,过多友元破坏封装。
3.3 构造函数、析构函数、五法则
构造函数
- 与类同名、无返回值,对象创建时自动调用,保证对象初始化完整;
- 初始化列表:
: hp(h), atk(a),优先于函数体内赋值,const/ 引用成员只能用列表初始化。
析构函数
~类名(),对象销毁时自动执行,负责释放堆内存、关闭文件、断开网络等资源清理;栈对象离开作用域无论正常返回 / 异常抛出,析构必然执行。
拷贝构造、拷贝赋值
使用已有对象初始化新对象时触发:Player p2 = p1;
编译器默认生成浅拷贝:仅复制指针地址,多个对象指向同一块堆内存,析构重复释放崩溃。
三法则 / 五法则 / 零法则
- 三法则:手动实现析构、拷贝构造、拷贝赋值其中一个,另外两个必须手动实现;
- 五法则(C++11):新增移动构造、移动赋值;
- 零法则(现代 C++ 最佳实践):成员全部使用智能指针、标准容器,无需手动实现内存管理相关函数。
this 指针 /static 成员
this:成员函数内置隐藏指针,指向当前调用函数的对象;
static成员:归属类本身,所有对象共享同一份数据,用于全局计数、全局配置。
3.4 继承:代码复用
// 基类(父类):所有角色通用属性行为
class Character {
public:
void move();
void takeDamage(int dmg);
protected:
int hp = 100;
};
// 子类(派生类)复用父类逻辑,扩展独有功能
class Player : public Character {};
class Boss : public Character {};
- 语义:
is-a关系(Boss 是一种角色),抽取公共逻辑避免代码复制;
- 访问权限:
public:外部、子类均可访问;
private:仅本类内部可用,子类不可访问;
protected:外部不可访问,子类可访问。
最佳实践
优先组合(对象包含成员变量),少用继承;继承层级不宜过深。
多重继承与菱形继承
- C++ 支持一个子类同时继承多个父类;
- 菱形继承问题:多层继承出现两份基类副本,访问成员二义性;
- 解决方案:虚继承
class B : virtual public A;
- 实战规范:多重继承仅用于纯抽象接口,带数据成员的类只用单继承。
3.5 多态(面向对象灵魂)
同一接口,不同子类实现不同行为,运行时自动匹配对应逻辑。
class Character {
public:
virtual void attack() { std::cout << "普通攻击"; }
virtual ~Character() {} // 虚析构
};
class Boss : public Character {
public:
void attack() override { std::cout << "全屏大招!!"; }
};
Character* c = new Boss();
c->attack(); // 输出:全屏大招,多态生效
3.6 虚函数表 vtable(底层原理,大厂面试高频)
- 含虚函数的类编译器自动生成虚函数表,存储所有虚函数地址;
- 每个对象内置隐藏
vptr指针,指向自身类的虚函数表;
- 调用虚函数流程:对象 vptr→查表→调用对应子类函数;运行时动态绑定;
- 性能代价:一次查表 + 间接跳转,对象多 8 字节 vptr;C++ 哲学:不为不用的特性付费,函数默认非虚。
3.7 纯虚函数、虚析构、重载 vs 重写
纯虚函数 & 抽象类
virtual void attack() = 0;
- 含纯虚函数的类为抽象类,禁止实例化对象;
- 强制所有子类必须实现该接口,等同于其他语言
interface。
虚析构函数
基类指针delete子类对象时,基类析构不加virtual,仅执行基类析构,子类资源泄漏。
背诵口诀:基类析构不虚,子类资源必漏。
重载 vs 重写
- 重载 overload:同一作用域,函数同名、参数列表不同,编译期静态绑定;
- 重写 override:子类覆盖父类虚函数,签名完全一致,运行时动态绑定;C++11 推荐加
override关键字校验签名。
第 4 章 RAII 与智能指针:驯服内存的终极答案
4.1 RAII(C++ 最重要设计思想)
全称:资源获取即初始化(Resource Acquisition Is Initialization)
核心逻辑
将资源生命周期绑定到栈对象生命周期:
- 构造函数申请资源(new、打开文件、加锁、建立连接);
- 析构函数自动释放资源;
- 栈对象无论函数正常退出、异常抛出,析构必然执行,资源绝不会泄漏。
RAII 适用范围:堆内存、文件句柄、网络 socket、互斥锁、数据库连接等所有系统资源。
4.2 三大智能指针(C++11 标准)
基于 RAII 封装堆内存,现代 C++ 禁止裸new/delete。
unique_ptr(默认首选,独占所有权)
#include <memory>
auto p = std::make_unique<Player>(100, 15);
p->attack(target);
- 独占资源,禁止拷贝,仅支持
std::move转移所有权;
- 零运行时开销,等效手写 new/delete;
- 适用场景:绝大多数单一所有者资源。
shared_ptr(共享所有权,引用计数)
auto c1 = std::make_shared<Config>();
auto c2 = c1; // 复制共享资源
- 内部原子引用计数,每复制计数 + 1,销毁计数 - 1,计数归零释放内存;
- 代价:原子锁性能损耗,不做默认选择;
- 适用场景:多模块共享同一份数据,无法确定最后使用者。
weak_ptr(解决 shared_ptr 循环引用)
- 不增加引用计数,仅观测
shared_ptr资源;
- 循环引用场景:A 持有 B 的 shared_ptr,B 持有 A 的 shared_ptr,计数永远不为 0,内存泄漏;
- 使用
lock()转为临时shared_ptr,资源已销毁返回空。
最佳实践
创建智能指针优先make_unique/make_shared,相比裸 new:单次内存分配、异常安全、性能更好。
本章总结
现代 C++ 尽量不手写new/delete;资源统一 RAII 管理;指针默认unique_ptr,共享场景用shared_ptr,循环引用搭配weak_ptr。
第 5 章 模板与 STL:不重复造轮子
5.1 模板:泛型编程,类型参数化
解决重复编写仅类型不同的重复逻辑:
template <typename T>
T myMax(T a, T b) {
return a > b ? a : b;
}
myMax(3, 5); // T=int
myMax(1.2, 3.4); // T=double
- 编译器编译期根据调用类型生成对应函数代码,零运行时开销;
- 支持函数模板、类模板;
- 进阶:模板特化、模板元编程(编译期计算)。
5.2 STL 标准模板库
三大核心组件:容器、迭代器、算法。
常用容器
- vector 动态数组(默认容器)
- 内存连续,支持随机下标访问 O (1);尾部追加均摊 O (1);
- 扩容:空间不足时申请 2 倍新内存,拷贝所有元素,扩容后迭代器 / 指针全部失效;
- 缺点:中间插入删除 O (n),元素整体搬迁。
- string:字符专用 vector,封装字符串拼接、查找、切片;
- list 双向链表
- 任意位置插删 O (1),无随机下标;
- 内存碎片化,CPU 缓存不友好,实际开发使用频率低;
- map 有序红黑树键值对:增删查 O (logn),遍历自动按键排序;
- unordered_map 哈希表:增删查平均 O (1),无序,算法刷题高频。
容器适配器(封装基础容器,限制接口)
stack:后进先出栈;
queue:先进先出队列;
priority_queue:优先队列(堆结构)。
迭代器
泛化指针,统一容器遍历接口;范围 for 循环底层封装迭代器:
std::vector<int> v = {3,1,4};
for (int x : v) std::cout << x;
通用算法
标准库内置排序、查找、计数、反转等高性能通用算法,适配所有容器:
std::sort(v.begin(), v.end());
auto it = std::find(v.begin(), v.end(), 4);
std::reverse(v.begin(), v.end());
STL 算法经过工业级优化,开发中禁止手动重复实现基础数据结构。
第 6 章 现代 C++(C++11 及以后,企业主流标准)
2011 年 C++11 大幅革新,业界现代 C++ 默认指代 C++11/14/17/20。
6.1 auto 类型推导
编译器自动推导变量类型,简化超长模板类型书写,编译期确定类型,静态类型无运行时开销。
// 旧写法
std::map<std::string, std::vector<int>>::iterator it = m.begin();
// 现代写法
auto it = m.begin();
// 范围for标配
for (const auto& x : v)
6.2 Lambda 匿名函数
就地定义回调逻辑,搭配 STL 算法使用:
// vector降序排序
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});
捕获列表规则:
[x] 值捕获,拷贝变量;
[&x] 引用捕获;
[=] 全部外部变量值捕获;
[&] 全部外部变量引用捕获。
安全提醒:lambda 生命周期长于当前函数时,禁止引用捕获局部变量,会产生悬空引用。
6.3 移动语义 & 右值引用 T&&
问题
大 vector 直接赋值会完整深拷贝,性能损耗极大;临时对象拷贝后直接销毁,资源完全浪费。
移动解决方案
直接转移底层内存指针,不拷贝元素,纳秒级完成:
std::vector<int> a = makeBigVector();
std::vector<int> b = std::move(a);
std::move仅标记对象可被移动,不执行任何拷贝;
- 移动构造函数真正接管资源,原对象变为空合法壳子;
- 左值:有名字、可取地址的普通变量;右值:临时对象、字面量、move 标记对象;
T&&右值引用仅绑定右值。
配套优化 API
emplace_back:直接在容器内存构造对象,省去移动 / 拷贝;
std::forward完美转发:模板中完整保留参数左 / 右值属性,make_unique底层依赖。
6.4 C++11/17/20 实用语法糖
-
nullptr:类型安全空指针,淘汰NULL/0;
-
enum class强类型枚举,禁止隐式转 int,避免枚举混淆;
-
=default / =delete:显式生成默认函数 / 禁用拷贝构造;
-
结构化绑定 C++17:快速解包 pair/map;
for (auto& [key, val] : mapData)
-
constexpr:编译期常量计算,运行时零开销;
-
std::optional C++17:安全表达 “空值”,替代魔法值如 - 1;
-
std::string_view C++17:只读字符串切片,无内存拷贝;
-
C++20 核心新特性:
- concepts:模板参数约束,编译报错更友好;
- ranges:算法管道链式调用;
- 协程:同步写法实现异步;
- modules:替代传统头文件包含。
现代 C++ 总纲
让正确、安全、高性能的写法成为最简单写法:智能指针替代裸 new,auto 简化类型,移动消除拷贝,lambda 简化回调。
第 7 章 并发:榨干多核 CPU
C++11 将多线程纳入标准库,跨平台统一并发 API。
7.1 线程基础
#include <thread>
// 新建线程执行任务
std::thread t([]{ heavyCompute(); });
t.join(); // 主线程阻塞等待子线程完成
7.2 并发核心痛点:数据竞争
多线程同时读写同一变量,counter++分为读、加、写三步,线程交替执行会丢失修改结果,bug 复现极难。
7.3 三大同步工具
1. std::mutex 互斥锁 + lock_guard(RAII 锁)
std::mutex mtx;
{
std::lock_guard<std::mutex> lock(mtx); // 构造上锁,析构自动解锁
counter++;
}
- 锁范围尽量缩小,减少串行等待;
- 死锁预防:所有线程按固定顺序获取多把锁。
2. std::atomic 原子变量
硬件级不可分割操作,单变量计数无需锁,性能远高于 mutex:
std::atomic<int> counter{0};
counter++; // 线程安全
3. std::async / std::future
高层异步封装,无需手动管理线程:
auto fut = std::async(std::launch::async, [](){ return calc(); });
// 主线程执行其他逻辑
int res = fut.get(); // 获取异步结果,阻塞等待计算完成
其他并发组件
condition_variable条件变量:生产者消费者模型,线程等待唤醒;
- 线程池:项目通用方案,复用常驻线程,减少线程频繁创建销毁开销。
并发心法
尽量不共享可变数据;必须共享:单变量用atomic,复杂逻辑用mutex,锁全程 RAII 管理。
第 8 章 工程化:企业真实项目规范
8.1 头文件与源文件分离
.h/.hpp头文件:仅放函数、类声明(接口菜单);
.cpp源文件:存放实现逻辑;
- 头文件防护:
#pragma once,防止重复包含重定义;
- 规范:头文件禁止
using namespace std;,避免全局命名污染。
8.2 命名空间 namespace
解决第三方库函数 / 类重名冲突,标准库全部位于std命名空间。
namespace mylib {
void log();
}
mylib::log();
8.3 构建系统 CMake
大型工程无法手动编译,CMake 编写CMakeLists.txt,一次配置跨 Windows/Linux/macOS 编译。
8.4 项目标配工具
- 单元测试:GoogleTest / Catch2,自动化回归;
- 包管理器:vcpkg / Conan,一键安装第三方 C++ 库,免去手动编译源码;
8.5 异常处理 try/catch/throw
用于文件不存在、网络断开等运行时异常;栈对象遵循 RAII,异常抛出过程自动析构,保证资源安全。
行业区分:游戏、嵌入式追求极致性能,部分团队禁用异常,改用错误码 /std::expected。
全文总结:C++ 完整逻辑链 & 学习路线
特性因果链
- 追求极致性能 → 直接暴露内存、引入指针、手动堆管理;
- 手动内存易泄漏 / 悬空 → 设计 RAII、智能指针自动管理资源;
- 大型程序代码混乱 → 封装、继承、多态面向对象体系;
- 重复编写同逻辑不同类型代码 → 模板 + STL 泛型编程;
- 老式 C++ 语法繁琐不安全 → C++11 现代语法简化、优化性能;
- 多核 CPU 无法充分利用 → 标准多线程并发库;
- 多人协作大型工程 → 头文件规范、命名空间、CMake 构建。
循序渐进学习路线
- 基础语法 + 指针内存,手绘内存图吃透底层;
- 面向对象三大特性,重点掌握虚函数、RAII;
- STL 容器、算法实战刷题,熟练 vector/unordered_map;
- 现代 C++ 改造旧代码:智能指针、auto、lambda、移动语义;
- 并发编程 + 工程工具:CMake、单元测试、内存检测工具;
- 完整实战项目:音视频播放器、迷你服务端、小游戏引擎。