C++ / a working model

THE FIELD GUIDE / 163 LESSONS

一次,弄懂一个问题。

先看结论,再读原理。运行例子,最后独立回答练习。

共 163 篇 · 阅读进度仅保存在当前浏览器

从第一篇开始 →
01

语言基础 / C++11

sizeof:测量对象表示,而非运行时内容

sizeof 查询静态类型的对象表示大小,结果以 C++ 字节为单位,包含必要的填充。它通常看起来像函数调用,实际是操作数不求值的运算符;数组、引用与指针的结果必须分别理解。

约 7 分钟
02

语言基础 / C++11

sizeof 与 strlen:容量、终止符和逻辑长度

sizeof 测量类型占用,strlen 沿字符序列寻找第一个零字符,两者回答不同问题。字符串字面量包含结尾零字符,嵌入零字符会提前结束 strlen;指针本身不记录容量,也不保证指向合法字符串。

约 7 分钟
03

语言基础 / C++11

数组参数退化:声明调整与 array-to-pointer conversion

数组并不是指针,但数组形式的函数形参会被调整成指针类型,普通调用中数组实参也常转换为首元素指针。边界不会自动跟随;要保留长度,使用数组引用、容器或 C++20 的 span。

约 8 分钟
04

语言基础 / C++11

const:只读访问路径与常量对象

const 限制经由某个类型进行的修改,但只读引用不意味着底层对象永不变化。理解顶层与底层 const、成员函数的只读承诺,以及 const 与 constexpr 的区别,才能写出可靠而不过度承诺的接口。

约 8 分钟
05

语言基础 / C++11

static:存储期、链接与类成员三种语境

static 的意义取决于出现位置:局部变量获得静态存储期,命名空间实体可以获得内部链接,类的静态成员不属于某个实例。局部静态初始化的线程安全,不代表后续读写也自动线程安全。

约 8 分钟
06

语言基础 / C++11

volatile:可观察访问,不是线程同步

volatile 告诉实现相关访问具有可观察效果,不能按普通内存访问随意消除。它不提供原子性、线程间顺序或 happens-before,也不承诺绕过 CPU 缓存;线程通信应使用原子操作或锁。

约 8 分钟
07

语言基础 / C++11

内存对齐 Alignment 与结构体填充

对齐规定对象可放置的地址条件,sizeof 包含为合法布局所需的填充。alignof 查询类型要求,alignas 提高声明的对齐要求;成员大小相加不能代替布局计算,打包结构也不能代替可移植序列化。

约 8 分钟
08

语言基础 / C++11

大小端 Endianness:数值与字节顺序分离

大小端描述多字节标量在存储中的字节排列,不改变数值本身。可移植协议应显式定义字段宽度和编码顺序,再用无符号运算编解码;不要通过未对齐强转或读取未激活的联合体成员猜测端序。

约 8 分钟
09

语言基础 / C++11

class 与 struct:默认访问不同,能力相同

C++ 的 class 和 struct 都定义类类型,都支持构造、析构、继承、虚函数与模板。语言差异主要是默认成员访问和默认继承访问;用 struct 表达简单数据、用 class 维护不变量,是设计惯例而不是功能限制。

约 7 分钟
10

语言基础 / C++11

宏与 inline:文本替换不等于函数调用

函数式宏在预处理阶段替换记号,不提供参数类型、正常作用域或单次求值保证。inline 函数具有完整函数语义,主要关联多翻译单元定义规则,并不强制机器码内联;常量和计算通常优先使用 constexpr 或模板。

约 8 分钟
11

语言基础 / C++11

typedef 与 using:类型别名不是新类型

typedef 和 using 都为已有类型提供名字,不创建独立类型,也不是宏替换。using 的语法更适合复杂类型和别名模板;给指针别名添加 const 会限定整个指针类型,而不是自动限定它指向的对象。

约 7 分钟
12

语言基础 / C++11

explicit:把转换意图留在调用处

explicit 阻止构造函数或转换函数参与某些隐式转换,但仍允许直接初始化和显式转换。它适合保护单位、容量和资源包装类型;explicit operator bool 还能支持条件判断而避免意外数值转换。

约 8 分钟
13

语言基础 / C++11

extern:声明共享实体,而非重复分配

extern 常用于声明在别处定义的变量,让多个翻译单元引用同一个实体。必须区分声明和定义、作用域与链接;带初始化器的 extern 变量声明通常就是定义,头文件中的重复定义也不会被包含保护消除。

约 8 分钟
14

语言基础 / C++11

extern "C":语言链接与 C ABI 边界

extern "C" 为适用的函数和变量声明指定 C 语言链接,用于连接兼容的 C 接口。它不是把函数体切换为 C 语言,也不让任意 C++ 类型获得跨语言 ABI;名字、调用约定、布局和资源责任必须分别处理。

约 9 分钟
15

语言基础 / C++11

mutable:逻辑只读对象中的可变实现状态

mutable 允许特定非静态数据成员在所属对象为 const 时仍被修改,适合缓存、互斥量等实现细节。它不会自动提供同步,也不应掩盖逻辑状态变化;lambda 的 mutable 则控制按值捕获副本的可修改性。

约 8 分钟
16

语言基础 / C++11

四种 cast:转换意图与安全前提

static_cast、dynamic_cast、const_cast 与 reinterpret_cast 分别表达不同转换意图,但关键字本身不保证运行安全。选择前先证明数值范围、动态类型、实际可修改性,以及对象生命期、对齐和类型访问规则。

约 10 分钟
17

类与对象 / C++11

封装、继承与组合:OOP 的边界

封装维护对象的不变量,公开继承表达可替换的接口关系,组合表达拥有或使用关系。不要因为能复用几行代码就建立继承层次;先定义合法状态与调用契约,再决定是否需要运行期多态。

约 8 分钟
18

类与对象 / C++11

访问控制 Access control 与继承权限

public、protected、private 控制名字的可访问性;继承方式还控制基类接口及向上转换对外是否可用。访问检查不会替你过滤重载候选,protected 也不允许派生类通过任意基类对象访问受保护成员。

约 8 分钟
19

类与对象 / C++11

重载、覆盖、隐藏:Overload / Override / Hiding

重载在编译期从候选函数中选择签名,覆盖决定虚调用最终执行哪个实现,隐藏则发生在名字查找阶段。三者可以同时出现;使用 override 检查覆盖意图,用 using 恢复需要的基类重载集合。

约 9 分钟
20

类与对象 / C++11

构造与析构顺序 Construction order

最派生类先初始化虚基类,再按声明顺序初始化直接基类和成员,最后执行构造函数体。初始化列表的书写顺序不能改变规则;析构反向清理已完成的子对象,构造失败也依赖这一确定顺序。

约 10 分钟
21

类与对象 / C++11

析构函数、异常与 noexcept

析构负责可靠清理,不适合承担必须报告失败的业务提交。析构函数通常隐式不抛出;异常越过 noexcept 边界会终止程序,即使允许抛出,在栈展开时再次让异常逃出析构也会调用 terminate。

约 8 分钟
22

类与对象 / C++11

深浅拷贝与 Rule of Zero / Five

默认拷贝逐成员处理,是否共享底层资源取决于成员类型,而不是统一的深拷贝或浅拷贝标签。优先让标准资源类型管理所有权;只有确需自定义复制语义时,才同时设计拷贝、移动、赋值和析构。

约 10 分钟
23

类与对象 / C++11

静态与动态多态 Polymorphism、切片与 RTTI

模板让调用按编译期类型组合,虚函数让同一基类接口在运行期选择实现。按值复制为基类会切片,丢失派生部分;RTTI 能安全查询活着的多态对象,但不能修复悬空指针或替代合理的接口设计。

约 9 分钟
24

类与对象 / C++11

this、对象限定与链式接口

this 是隐式对象成员函数所操作对象的指针表达式,其指向类型随成员函数的 const 限定变化。返回 *this 可建立链式接口,但不会延长对象生命周期;静态成员函数没有 this,捕获 this 也不代表拥有对象。

约 8 分钟
25

类与对象 / C++11

虚表 Vtable:语言语义与 ABI 实现

标准规定虚调用应选择哪个最终覆盖者,却不规定虚表数量、虚指针位置或对象内存图。常见 ABI 用虚表、调整桩和 RTTI 元数据实现这些语义;理解它们有助于调试,但不可用未经保证的内存读取验证。

约 9 分钟
26

类与对象 / C++11

抽象类 Abstract class 与纯虚函数定义

抽象类不能创建完整对象,但可以拥有状态、构造函数和普通实现。纯虚表示具体派生类必须提供非纯的最终覆盖者,不等于函数不能有定义;纯虚析构在实际销毁派生对象时仍然需要可用定义。

约 8 分钟
27

类与对象 / C++14

为什么没有 virtual constructor:工厂与 clone

构造函数不能声明为虚函数;创建表达式已经决定要建立的具体类型,不能靠尚未完成的对象反向选择构造类型。运行期选型应使用工厂,按现有动态类型复制则用虚 clone,构造期间的虚调用也有阶段限制。

约 9 分钟
28

类与对象 / C++14

基类析构:public virtual 或 protected nonvirtual

如果允许通过基类指针拥有并删除派生对象,基类应提供 public virtual 析构;如果基类只是不可独立销毁的接口视图,可用 protected nonvirtual 析构阻止这种删除。选择依据是销毁契约,不是见到继承就一律加 virtual。

约 9 分钟
29

类与对象 / C++11

成员模板为何不能 virtual

成员函数模板不能声明为虚函数,它的某个特化也不会自动覆盖基类虚函数。但类模板里的普通非模板成员可以是虚函数。需要兼顾泛型入口与运行期扩展时,可把模板转换层和固定签名的虚接口分开。

约 8 分钟
30

类与对象 / C++20

空类、EBO 与 [[no_unique_address]]

空类的完整对象仍具有非零大小,以支持对象身份和数组寻址;空基类子对象则可能不占额外空间。C++20 的 no_unique_address 将重叠布局机会扩展到成员,但不保证具体 sizeof,也不取消同类型对象的身份规则。

约 8 分钟
31

内存与所有权 / C++20

内存模型与对象生存期 Object lifetime

一块地址可用的存储,不等于其中已经存在可访问的对象。判断访问是否合法,要同时检查大小、对齐、对象生存期、访问类型和边界;构造与析构决定对象的阶段,分配与释放决定底层存储的阶段。

约 10 分钟
32

内存与所有权 / C++11

指针 Pointer:地址、边界与借用

指针是有类型的值,可能指向对象、函数、尾后位置,也可能为空或无效。非空不是可解引用的证明;正确使用依赖对象仍存活、类型匹配和访问未越界,裸指针本身也不表达释放责任。

约 8 分钟
33

内存与所有权 / C++11

读懂指针声明 Pointer declarators

读复杂声明要从名字出发,按括号与声明符的结合关系分层分析。数组指针、指针数组、函数指针并不等价;const 修饰哪一层也决定能否改指针或目标,用类型别名可以让接口更清晰。

约 9 分钟
34

内存与所有权 / C++14

存储期 Storage duration 不等于内存分区

C++ 规定自动、静态、线程和动态四种存储期,不要求固定的栈、堆或可执行文件分段。变量名的作用域、名字的链接属性、对象的生存期和操作系统映射分别回答不同问题,不能混成一张地址图。

约 9 分钟
35

内存与所有权 / C++11

引用 Reference 与临时对象期限

引用提供已有对象的别名,不是可重新绑定的普通指针,也不自动拥有目标。const 引用可以在特定初始化场景延长临时对象的生存期,但这种延长不会沿参数、返回值或另一个引用任意传递。

约 9 分钟
36

内存与所有权 / C++14

参数传递 Parameter passing 与所有权契约

选参数类型时先决定函数只是读取、需要修改,还是接收所有权,再比较复制成本。小值按值,大型只读对象用 const 引用,可空借用用指针,独占所有权按值传 unique_ptr;这些类型传达的是不同契约。

约 9 分钟
37

内存与所有权 / C++14

RAII:把清理责任交给对象

RAII 把资源的获取与释放封装进拥有型对象,用确定的析构时机处理正常返回和异常展开。关键不是“资源必须在栈上”,而是每份资源有明确拥有者,构造失败不泄漏,析构不把失败继续向外抛。

约 10 分钟
38

内存与所有权 / C++14

智能指针选择 Ownership vocabulary

先考虑直接值成员和容器;确需动态生存期时,默认用 unique_ptr 表达独占拥有,仅在多个参与者都必须延长生存期时使用 shared_ptr。观察者采用引用、裸指针或 weak_ptr,不要把所有访问都升级为拥有。

约 9 分钟
39

内存与所有权 / C++11

shared_ptr:控制块与线程安全边界

shared_ptr 共享的是一组销毁责任,通常通过控制块记录拥有者、删除器等信息。控制块允许不同 shared_ptr 实例并发管理同一对象,但不保护对象内部数据,也不允许无同步地修改同一个 shared_ptr 变量。

约 11 分钟
40

内存与所有权 / C++11

weak_ptr:打破环并安全取得临时拥有权

weak_ptr 观察已有 shared_ptr 所有权组而不增加强引用数量,适合回指、订阅者和非拥有缓存。访问前直接 lock,成功得到的 shared_ptr 会保护这次使用的生存期;expired 检查本身不能预留对象。

约 10 分钟
41

内存与所有权 / C++11

malloc 与分配器 Allocator 的职责

malloc 提供未初始化存储,失败返回空指针,必须与 free 配对;它不运行 C++ 构造函数。分配器可能缓存、分桶或向系统申请映射,具体实现并非语言保证,申请字节与构造对象应当分层理解。

约 11 分钟
42

内存与所有权 / C++11

new/delete 表达式与分配函数

new 表达式通常包含取得存储和初始化对象,delete 表达式通常包含析构和释放存储;operator new/operator delete 是其中的底层分配函数,不等同于完整表达式。理解这层区别才能正确处理异常、数组和原位构造。

约 11 分钟
43

内存与所有权 / C++14

内存泄漏 Memory leaks:从责任到证据

泄漏首先是资源没有在约定时间被正确回收,不是简单观察进程内存有没有下降。用 RAII 覆盖异常路径,再结合 LeakSanitizer 的分配栈与重复工作负载区分失联分配、引用环、无界缓存以及分配器保留。

约 10 分钟
44

内存与所有权 / C++11

悬空与未初始化指针 Dangling pointers

未初始化指针没有可依赖的指针值;悬空指针则曾经指向有效对象,但目标已经销毁或失效。初始化为 nullptr 只解决起始状态,不能跟踪对象生存期;根本办法是约束借用范围并正确安排拥有关系。

约 9 分钟
45

内存与所有权 / C++11

内存错误排查 Memory safety diagnostics

把内存错误拆成空间越界、时间失效、初始化缺失、释放协议错误和并发竞争,再选择对应证据。崩溃地点往往只是后果;应追踪第一次非法访问、分配与释放栈,并以明确边界和拥有型接口修复根因。

约 11 分钟
46

现代 C++ / C++11

用 nullptr 表达空指针

nullptr 是具有独立类型的空指针字面量,不是整数零,也不是某种通用指针。它让重载选择和模板传参保留空指针语义,但不会让解引用变安全,更不能代替对象生命周期管理。

约 7 分钟
47

现代 C++ / C++14

auto 与 decltype(auto) 的推导边界

auto 让编译器推导静态类型,并不引入动态类型。是否保留引用与 const 取决于声明形式;decltype(auto) 则直接采用 decltype 规则,括号也能改变结果,尤其影响函数返回值与生命周期。

约 9 分钟
48

现代 C++ / C++11

值类别与 std::move:转换不等于搬运

左值、将亡值与纯右值描述表达式,而不是变量永久拥有的标签。std::move 只把表达式转换为可参与移动重载的形式;实际是否转移资源、源对象留下什么状态,取决于被调用类型的契约。

约 9 分钟
49

现代 C++ / C++11

转发引用与 perfect forwarding

转发引用配合模板推导记录调用者传入的值类别,std::forward 再把这份信息用于下一次调用。它适合透明包装,不是所有 T&& 的别名,也不是比 std::move 更高级的通用替代品。

约 10 分钟
50

现代 C++ / C++14

Lambda 捕获:闭包也是有生命期的对象

Lambda 创建带调用运算符的闭包对象,捕获决定它保存值还是借用外部状态。按值、按引用与初始化捕获有不同的所有权后果;把回调存起来之前,应先证明它依赖的对象能活到调用结束。

约 9 分钟
51

现代 C++ / C++11

模板推导:先匹配参数,再实例化代码

模板是一组由参数生成的声明与实现,不是把任意实参强行转换成同一种类型。理解按值与按引用推导、非类型参数以及定义可见性,能更快解释调用失败、数组退化和依赖名称错误。

约 10 分钟
52

现代 C++ / C++11

全特化、偏特化与重载不要混用

全特化为确定的模板实参提供替代定义,偏特化为一类实参模式提供实现。类模板可以偏特化,函数模板不能;函数行为的分流通常优先用重载或约束,避免误判特化参与选择的方式。

约 10 分钟
53

现代 C++ / C++17

Type traits 与 if constexpr 的编译期分支

Type traits 把类型性质表达为编译期值或类型变换;if constexpr 在模板实例化时丢弃不适用的分支。它解决的是不同类型需要不同合法表达式的问题,不会把运行期条件变成编译期事实。

约 9 分钟
54

现代 C++ / C++20

constexpr、consteval 与 constinit 各管什么

constexpr 表达常量求值的能力与变量约束,consteval 要求立即调用满足常量表达式条件,constinit 约束静态或线程存储期变量的初始化。三者并不等价,尤其 constinit 不会使变量只读。

约 9 分钟
55

现代 C++ / C++17

参数包与 fold expression:先处理空包

可变参数模板保留每个实参的类型,折叠表达式把参数包组合为一个表达式。可靠的设计需要先决定空包结果、初始值类型与求值顺序,不能把省略号当成自动安全的循环或任意长度数组。

约 9 分钟
56

现代 C++ / C++17

optional 与 variant:把状态放进类型

optional 表达一个值可能不存在,variant 表达有限候选类型中的一个。二者都管理所含对象的生命周期;安全使用的关键是先确定当前状态,再访问正确分支,而不是用魔法数或裸 union 偷渡状态。

约 10 分钟
57

现代 C++ / C++20

string_view 与 span:轻量借用不延寿

string_view 与 span 把一段连续数据的地址和范围交给调用者,不拥有数据。它们能减少复制并统一接口,但原对象销毁、重新分配或失效后,视图也会失效;只读视图同样不能逃离生命周期约束。

约 10 分钟
58

现代 C++ / C++20

Concepts 与 requires:约束可调用的接口

C++20 concepts 把模板所需的类型性质和表达式条件写到接口上。requires 表达式检查是否存在合法操作,requires 子句控制候选资格;它们改善诊断与重载选择,但不能证明运行期输入或业务语义正确。

约 10 分钟
59

现代 C++ / C++20

Ranges 与惰性 views:管道不是结果缓存

C++20 ranges 把范围作为算法输入,views 可组合筛选、映射等惰性操作。创建管道通常没有计算全部结果,重复遍历也不承诺复用结果;理解底层所有权、迭代能力与最终物化边界比管道语法更重要。

约 10 分钟
60

现代 C++ / C++20

Coroutines:暂停、恢复与协程帧所有权

C++20 协程让函数暂停后恢复,但不会自动创建线程、事件循环或后台任务。最小可用抽象也必须明确管理协程帧、完成状态与异常;下面的同步生成器用独占 RAII 所有权保证提前结束时仍能释放资源。

约 15 分钟
61

现代 C++ / C++23

C++23 expected 与按特性确认可用性

std::expected<T,E> 把成功值与失败原因作为同一个返回类型交付,适合预期内、需要调用者处理的失败。启用 C++23 模式不代表整个标准库已实现,应同时检查编译器、标准库版本和对应特性宏。

约 10 分钟
62

标准库与算法 / C++11

vector:增长、容量与失效规则

vector 提供连续存储和常数时间下标访问,但容量不等于已构造元素数。理解 reserve、resize 与重分配的区别,才能正确估算追加成本,并避免把旧指针、迭代器和尾后位置带过修改操作。

约 9 分钟
63

标准库与算法 / C++11

deque:双端操作与稳定性的边界

deque 支持常数时间随机访问和两端单元素插入,却不保证连续存储。它的关键细节是引用稳定不代表迭代器稳定:两端插入保留已有元素引用,但会使迭代器失效,删除规则还要区分发生位置。

约 8 分钟
64

标准库与算法 / C++11

list:节点稳定、splice 与局部性

list 擅长已知位置的插入、删除和节点转移,而不是快速寻找位置。splice 可以保留元素身份与迭代器,但分配器和范围有前置条件;跨列表的范围转移也不能一概声称为常数时间。

约 9 分钟
65

标准库与算法 / C++11

map 与 set:有序关联和比较器契约

map、set 按比较器维护有序键,键是否重复由比较等价性决定,而不必由 operator== 决定。理解对数查找、成员边界查询和不可随意修改的键,比记住某种树的实现名称更重要。

约 9 分钟
66

标准库与算法 / C++11

unordered:哈希、等价与 rehash

unordered 容器用哈希定位候选桶,再用相等谓词识别键。平均常数查找不等于最坏常数;预留元素规模可以减少 rehash,但引用稳定、迭代器失效和桶数策略仍须分别理解。

约 9 分钟
67

标准库与算法 / C++20

迭代器:能力类别、范围与有效性

迭代器类别描述可以执行哪些操作以及相应复杂度,不负责延长对象生命周期。区分单趟输入、多趟前向、双向、随机访问和连续迭代器,再单独检查容器修改导致的失效,才能正确组合算法。

约 10 分钟
68

标准库与算法 / C++11

算法:sort、边界查找与 erase-remove

标准算法操作范围而非容器所有权。排序需要合法的严格弱序,二分边界查找依赖分区条件,remove 只改变逻辑末尾而不缩小容器;理解这些前置条件比背函数名更能避免错误。

约 10 分钟
69

标准库与算法 / C++11

容器适配器:栈、队列与堆优先级

stack、queue 和 priority_queue 用受限接口表达访问纪律。优先队列不是有序数组:比较器定义谁排在谁之前,而 top 取比较顺序中的最大项;理解这个方向才能正确实现小顶堆和多字段优先级。

约 9 分钟
70

标准库与算法 / C++17

Allocator 与 pmr:资源选择和生命周期

分配器把容器的存储获取策略与元素管理分开。C++17 pmr 允许运行时选择内存资源,但资源并不由容器共享拥有;缓冲区、资源和使用它们的对象必须按正确顺序生存与销毁。

约 10 分钟
71

并发与同步 / C++20

线程生命周期:thread、jthread 与 stop_token

线程对象管理执行资源,不等于线程正在运行。先确定谁负责等待结束,再确定共享对象活多久;C++20 的 jthread 自动请求停止并等待,但停止仍需要工作函数合作,不能强制终止阻塞操作。

约 10 分钟
72

并发与同步 / C++17

互斥与 RAII:mutex、scoped_lock 和死锁

互斥锁保护的是一组共享状态的不变量,而不是变量名字。用 RAII 绑定锁的生命周期;同时更新多个对象时,一次获取所需锁。避免锁顺序环路,也避免在持锁期间等待线程或调用未知代码。

约 10 分钟
73

并发与同步 / C++11

条件变量:谓词、丢失通知与虚假唤醒

条件变量只负责让线程等待和重新检查,不保存事件。把真实条件存进共享状态,在同一互斥锁下修改与检查,并使用带谓词的 wait;这样通知提前发生或出现虚假唤醒,都不会破坏业务逻辑。

约 11 分钟
74

并发与同步 / C++11

原子操作:data race、RMW 与无锁边界

atomic 解决单个原子对象访问不可分割的问题,不会自动把多步业务变成事务。计数更新需要 fetch_add 等读改写操作;原子类型也不承诺一定无锁,更不保证任何基于它的算法都无等待。

约 10 分钟
75

并发与同步 / C++20

内存序:relaxed、acquire-release 与 happens-before

内存序描述原子操作如何约束周围访问。relaxed 保留原子性却不发布普通数据;读取到对应 release 写入的 acquire 才建立跨线程同步。判断正确性要画出先行发生链,不能依赖机器上看似稳定的执行顺序。

约 13 分钟
76

并发与同步 / C++11

任务结果:async、future、promise 与启动策略

future 是一次性结果的接收端,不等于后台线程。async 的启动策略决定独立执行还是延迟求值;promise 负责手动提交值或异常。既要处理结果,也要明确等待、异常传播以及关联任务何时结束。

约 11 分钟
77

构建与诊断 / C++11

从源文件到程序:Compilation 与 Linking

编译器能看见声明,不等于链接器能找到定义。按预处理、编译、汇编、链接定位错误,再用一个本地三文件项目理解静态库、动态库与 CMake 目标依赖;这些构建方式属于工具链约定,不是语言规定的文件格式。

约 13 分钟
78

构建与诊断 / C++17

ODR、头文件定义与 inline 的真正作用

头文件可以被多个翻译单元包含,但其中的定义必须满足单一定义规则。inline 主要解决跨翻译单元的定义与实体身份,不是强制展开调用;模板可见性、名字查找和构建宏的一致性同样属于接口正确性。

约 12 分钟
79

构建与诊断 / C++11

异常安全:Guarantees、noexcept 与提交点

异常安全关心失败以后对象还剩什么,而不是代码里有没有 catch。用 RAII 保住资源,用先准备再提交实现强保证,并让 noexcept 只描述真正不会向外传播异常的操作;copy-and-swap 是有成本和前提的策略,不是万能答案。

约 12 分钟
80

构建与诊断 / C++11

诊断与测量:Warnings、Sanitizers 与 Debugger

让编译警告、运行时插桩和调试器分别回答不同的问题,再以可重复的输入检验修复。必须区分未定义、未指定和实现定义行为;Sanitizer 没有报告只说明本次执行未触发已启用的检查,性能结论也必须来自独立测量。

约 14 分钟
81

书籍实践 / C++11

类不变量:让非法状态进不了对象

构造函数不仅填成员,还负责建立对象承诺;修改操作必须维护同一承诺。以有容量上限的库存为例,把范围检查放进类型内部,并在失败前保持旧值不变,比要求每个调用者自觉检查更可靠。

约 12 分钟
82

书籍实践 / C++11

chrono 单位:别把 1500 毫秒变成 1 毫秒

时间类型把数值和单位绑定在一起,count() 却只返回裸计数。通过毫秒预算和秒级接口的转换,观察向零截断、精度损失及显式单位边界;不依赖真实时钟或休眠,因此结果可重复。

约 12 分钟
83

书籍实践 / C++11

streambuf 分层:给格式化输出换一个目的地

ostream 负责把数值变成字符,streambuf 负责传递字符。用一个固定容量的自定义缓冲区接住同一套格式化操作,再制造确定的容量不足,观察设备层失败如何传成流的 badbit。

约 14 分钟
84

书籍实践 / C++11

构造失败:完整对象没析构,成员仍会清理

构造函数抛异常时,未完成的完整对象不会调用其析构函数,但已构造完成的成员会按逆序销毁。用无分配的事件日志追踪构造、抛出与清理,区分对象生命周期、成员生命周期和分配到的存储。

约 14 分钟
85

书籍实践 / C++11

auto 不等于引用:检查复制和别名的边界

auto 推导的是声明所需的类型,不会自动保留初始化表达式的引用属性。对比容器复制、引用别名和数组退化,再用类型断言固定意图,避免把“省略类型名”误当成“省掉复制”。

约 11 分钟
86

书籍实践 / C++11

值复制与共享所有权:同样的复制,不同的承诺

复制一个对象究竟复制数据,还是复制访问同一数据的入口,必须由类型接口讲清楚。对照 vector 的独立值与 shared_ptr 的共享对象,并用 weak_ptr 验证观察者不会延长资源寿命。

约 12 分钟
87

书籍实践 / C++11

过滤序列:算法重排与容器删除分开理解

算法只看到迭代器范围,不负责改变容器大小。以订单状态过滤为例,区分复制到新序列、稳定划分和删除尾区间,并观察空输入、全部淘汰以及原顺序保留的契约。

约 12 分钟
88

书籍实践 / C++11

异常安全:先准备,再提交状态

RAII 能在离开作用域时回收资源,却不会自动撤销已经写入的业务状态。用配置替换演示如何把可能失败的工作放在局部对象里,确认有效后再交换,让失败路径保持旧配置不变。

约 13 分钟
89

书籍实践 / C++11

输入解析:读完整记录,验证后再提交

从流中读出一个整数,并不证明整条记录合法。把逐行获取、字段提取、范围校验和结果提交分成阶段,让多余字段、负值与坏格式都留下可解释的失败结果,且不污染上一条有效记录。

约 13 分钟
90

书籍实践 / C++11

构造期虚调用:先准备配置,再启用策略

构造期间的虚调用不会进入尚未就绪的更派生层。通过安全的事件记录观察三个派发阶段,再区分语言允许的调用与不稳健的初始化设计,避免把 override 当成生命周期保证。

约 12 分钟
91

书籍实践 / C++11

同名 find,不同身份:先定义键的等价关系

有序集合成员 find 使用比较器定义的等价关系,通用 std::find 使用相等比较。以分桶编号为例,观察两种查找为何可以给出不同结果,并建立不会破坏容器要求的严格弱序。

约 13 分钟
92

书籍实践 / C++11

多态接口不暴露赋值:让叶子保持完整值语义

通过基类引用赋值通常只改变基类子对象,不代表完整的多态复制。把接口层赋值设为受保护,让具体叶子使用正常拷贝,以可调用性检查证明客户端不能跨越这个边界。

约 12 分钟
93

书籍实践 / C++20

谓词表达值条件,不表达第几次调用

算法可以复制谓词,调用次数也不等于容器中的位置。将稳定的阈值配置与位置操作分开,用 C++20 erase_if 删除满足值条件的元素,再安全执行一次明确的按位置删除。

约 12 分钟
94

书籍实践 / C++11

排序轻量索引:保留原数据顺序,也写清失效规则

不改变原始记录也能提供排序视图:构造位置索引,只比较源记录中的键。用平局规则得到确定结果,同时说明索引并不管理源对象的生命周期,也不会自动跟随数据修改而重新排序。

约 13 分钟
95

书籍实践 / C++17

错误边界:报告失败不等于处理失败

解析层报告它能证明的错误,业务层才决定如何响应。用一个金额解析接口区分发现、传播和翻译,保留异常的动态类型,并让失败结果不携带伪造金额。

约 12 分钟
96

书籍实践 / C++17

折叠表达式:空输入、短路与求值顺序

把多个条件组合成一个验证器时,先定义空集合和失败之后的行为,再选择折叠运算符。C++17 的逻辑与折叠保留短路,但不能让不合法的模板分支免于编译。

约 11 分钟
97

书籍实践 / C++20

Policy-based design:分离决定,不复制流程

同一个容量规则可以配合不同的超限处理方式。用成员组合与 C++20 concepts 写一个小型策略宿主,区分结构兼容、语义承诺,以及编译期配置和运行时替换。

约 14 分钟
98

书籍实践 / C++20

量纲类型:编译期阻止长度与时间相加

把长度和时间的指数放进类型,数值仍按普通算术处理。这个小型模型证明加法必须同量纲、乘法会组合量纲,并明确区分量纲、单位比例与数值安全。

约 14 分钟
99

书籍实践 / C++20

条件变量等待的是状态,不是一次通知

一次性计算交接需要同时证明状态可见、等待不丢失以及工作线程不会越过对象寿命。用互斥量保护谓词,再用 C++20 jthread 管理结束,通知早到或晚到都不会改变答案。

约 13 分钟
100

书籍实践 / C++20

Traits 适配的是表达式,Concepts 检查的是边界

同一个泛型算法可以接纳字段和成员函数不同的类型,只要 traits 提供一致操作。用 C++20 requires 把错误挡在接口处,并区分语法满足、返回类型与业务意义这三层承诺。

约 12 分钟
101

书籍实践 / C++11

从设计约束出发:用值类型守住区间不变量

D&E第1章把表达能力、运行效率与工具可获得性放在同一个工程问题中。本课据已读英文样章设计一个小型区间值类型:不依赖继承,也不追求语法炫技,而是在构造边界建立可验证的不变量。

约 9 分钟
102

书籍实践 / C++11

构造与析构期间:验证虚调用,不猜虚表布局

对象模型的实现图有助于理解成本,但不是跨编译器ABI承诺。本课从已读第5章构造语义出发,用间接虚调用记录对象生命周期中的行为,区分语言保证、历史实现模型与尚未初始化的派生成员。

约 10 分钟
103

书籍实践 / C++17

算法与配接器:先证明区间,再组合操作

读懂 STL 源码不是记住内部类名,而是分清输入区间、输出责任与调用契约。用一段完整的数据处理程序连接插入配接器、严格弱序、反向边界和成员调用,并说明旧式函数配接器如何安全改写。

约 15 分钟
104

OSTEP / C11

前言

本书以费曼讲义为灵感,围绕虚拟化、并发与持久性三大主题展开,介绍问题驱动的章节结构、时间线、对话等教学手法,并说明免费获取方式、课程安排以及面向教师和学生的实践建议。

约 8 分钟
105

OSTEP / C11

本书开篇对话

开场师生对话点明书名灵感来自物理讲座笔记,把操作系统浓缩为虚拟化、并发与持久性三大支柱,并给出听课、反复读笔记加真实编程项目的学习路径,同时说明对话段落用于跳出叙述进行主动思考。

约 8 分钟
106

OSTEP / C11

操作系统导论

本章概述操作系统如何通过虚拟化将有限的物理硬件转变为易于使用的抽象,同时充当资源管理器和标准接口提供者。

约 8 分钟
107

OSTEP / C11

关于虚拟化的对话

师生通过轻松对话揭示操作系统如何把单一物理CPU变成多个虚拟CPU,让每个程序都以为自己独占处理器。

约 8 分钟
108

OSTEP / C11

抽象:进程

本章介绍进程作为运行程序的操作系统抽象,解释CPU虚拟化、进程状态组成以及如何从程序创建进程。

约 8 分钟
109

OSTEP / C11

插曲:进程 API

本插曲介绍 UNIX 进程创建与控制的核心接口。fork 复制当前进程并让父子从同一返回点继续但返回值不同,wait 让父进程阻塞直至子进程结束,exec 族则用全新可执行映像替换当前进程。三者组合既简洁又极具表达力。

约 8 分钟
110

OSTEP / C11

机制:受限直接执行

操作系统通过受限直接执行高效虚拟化CPU:用户程序直接在处理器上运行以获得速度,同时依靠硬件模式切换和陷阱指令让内核始终掌握控制权。

约 8 分钟
111

OSTEP / C11

调度:引言

本章建立思考调度策略的基本框架,先列出一组简化工作负载假设,再引入周转时间作为核心性能指标,随后考察先进先出与最短作业优先两类早期算法及其适用边界。

约 8 分钟
112

OSTEP / C11

调度:多级反馈队列

多级反馈队列(MLFQ)根据作业在运行中表现出的行为动态调整优先级,从而在缺乏作业长度先验知识的情况下,同时改善交互作业的响应时间和长作业的周转时间。

约 8 分钟
113

OSTEP / C11

调度:比例份额

本章介绍一种按指定比例分配处理器时间的调度思路,核心是给作业发放票券并用随机抽奖决定下一次运行者,同时说明若干票券操作技巧以及为何该方法实现简单却只保证概率公平。

约 8 分钟
114

OSTEP / C11

多处理器调度(进阶)

随着多核芯片普及,操作系统必须在多个CPU间分配线程。本章用原创视角讲解缓存层次带来的一致性难题、共享数据仍需锁保护的原因,以及调度器如何利用缓存亲和性来减少迁移开销。

约 8 分钟
115

OSTEP / C11

CPU虚拟化的总结对话

本对话回顾操作系统如何通过硬件机制与谨慎策略实现CPU虚拟化,强调控制权保持、调度权衡以及实际系统中的工程现实。

约 8 分钟
116

OSTEP / C11

内存虚拟化对话

对话指出CPU虚拟化完成后,内存虚拟化才是真正的难点。用户程序发出的每个地址都是虚拟的,操作系统在硬件协助下将其映射为物理地址,从而让每个进程都以为自己拥有一块巨大、私有且连续的内存。这样做既让编程更轻松,也实现了进程间的隔离与保护。后续将从基址/界限寄存器讲起,逐步引入TLB和多级页表。

约 8 分钟
117

OSTEP / C11

抽象:地址空间

本章介绍操作系统如何将物理内存抽象为每个进程私有的地址空间,使多个程序能同时驻留内存并安全运行,同时保持对程序员透明。

约 8 分钟
118

OSTEP / C11

插曲:内存API

C程序依赖栈上的自动分配和堆上的显式请求来管理数据寿命。理解malloc与free的正确配对以及常见误用,是写出健壮软件的关键。

约 8 分钟
119

OSTEP / C11

机制:地址转换

本章介绍硬件地址转换如何让操作系统高效灵活地虚拟化内存。通过在每次访存时由硬件将虚拟地址映射到物理地址,进程获得从零开始的私有连续空间假象,同时操作系统保持隔离与保护。

约 8 分钟
120

OSTEP / C11

分段

分段用多组独立的基址与界限寄存器把代码、堆、栈分别放到物理内存的不同位置,从而避免稀疏地址空间中的空洞浪费物理页。

约 8 分钟
121

OSTEP / C11

空闲空间管理

本章讲解内存分配器面对可变大小空闲区域时的核心难题,重点分析外部碎片成因以及分割、合并、头部记录等基础机制如何帮助维持可用连续空间。

约 8 分钟
122

OSTEP / C11

分页:引言

分页将虚拟地址空间和物理内存都切成固定大小的页与页框,彻底避开分段的外部碎片。每个进程用私有页表记录映射,硬件仅需把虚拟页号换成物理框号即可完成转换。

约 8 分钟
123

OSTEP / C11

分页:更快的转换(TLB)

没有硬件转换缓存,分页会太慢。TLB利用局部性,使大多数地址转换在几个周期内完成,而不是需要内存访问。

约 8 分钟
124

OSTEP / C11

分页:缩小页表

线性页表会吞噬大量内存。本章用全新表述探讨增大页、分页与分段混合等压缩方法,并指出由此引入的内部碎片与硬件检查代价。

约 8 分钟
125

OSTEP / C11

超越物理内存:机制

操作系统借助较慢的辅助存储作为交换区,并在页表项中增加存在位,从而在物理内存不足时仍为多个大地址空间进程提供透明的虚拟内存假象。缺页由软件处理程序完成调入。

约 8 分钟
126

OSTEP / C11

超越物理内存:策略

物理内存不够用时,操作系统必须挑选页面换出到磁盘。本章介绍如何设计置换策略以降低缺页次数,并以无法实现的最优算法作为比较基准,同时分析简单的FIFO方法及其局限。

约 8 分钟
127

OSTEP / C11

完整虚拟内存系统

本章通过VAX/VMS和Linux两个实例,说明如何将页表设计、TLB管理、页面替换以及其他性能、安全与功能特性组合成一个能在从嵌入式设备到超级计算机上运行的完整虚拟内存系统。

约 8 分钟
128

OSTEP / C11

内存虚拟化总结对话

师生对话梳理虚拟内存核心心智模型:用户程序只接触虚拟地址,TLB让转换变得可行,页表结构需灵活适配稀疏空间,交换机制揭示真实硬件约束。目标是能独立诊断系统异常行为。

约 8 分钟
129

OSTEP / C11

关于并发的对话

教授与学生通过多人争抢桌上桃子的日常场景引入并发思想,指出无协调的同时抓取会造成冲突,而排队虽能保证人人有份却牺牲了速度。理想方案必须同时满足正确与高效。随后将类比映射到多线程程序:线程如同独立行动者,共享内存位置就像那些桃子,必须加以协调。操作系统课程关注此主题,是因为内核既要向应用提供锁等同步原语,其自身作为最早的并发程序也必须极其小心地管理内部数据。

约 8 分钟
130

OSTEP / C11

并发:简介

本章用全新视角介绍线程:同一进程内部可以同时存在多条独立执行流,它们共享整块地址空间。每条线程自带程序计数器和寄存器,切换时不必更换页表。每个线程还拥有私有栈。引入线程主要是为了利用多核真正并行,以及在部分线程等待I/O时让其余线程继续前进。

约 8 分钟
131

OSTEP / C11

插曲:线程 API

本插曲概述 POSIX 线程库的核心调用,用于启动新执行流、等待它们结束以及用互斥锁保护共享数据。接口设计兼顾易用性与灵活性,后续章节会用大量例子展开锁与条件变量。

约 8 分钟
132

OSTEP / C11

锁允许程序员保护临界区,确保共享数据的更新以原子方式发生,从而避免并发线程之间的竞态条件。

约 8 分钟
133

OSTEP / C11

基于锁的并发数据结构

本章探讨如何向常见数据结构添加锁以实现线程安全,同时分析简单锁定方案的性能局限以及通过近似技术提升可扩展性的方法,重点以计数器为例。

约 8 分钟
134

OSTEP / C11

条件变量

条件变量让线程能够高效地休眠直到某个共享条件变为真,从而避免无意义的自旋。它必须与互斥锁配合,通过wait原子释放锁并睡眠、signal唤醒等待者,并依靠显式状态变量防止信号丢失。

约 8 分钟
135

OSTEP / C11

信号量

信号量用一个整数计数器加上阻塞与唤醒原语来协调线程。初始化值决定它充当互斥锁还是事件通知器。本章用独立示例说明 wait/post 语义、二元用法以及父子线程排序,并给出可编译的 C 演示。

约 8 分钟
136

OSTEP / C11

常见并发问题

本章分析并发软件中反复出现的缺陷模式,重点区分死锁与非死锁问题,后者主要包括原子性假设失败和执行顺序颠倒。通过同步原语可有效缓解这些风险,提升多线程代码的可靠性。

约 8 分钟
137

OSTEP / C11

基于事件的并发(进阶)

本章介绍不依赖线程构建并发服务器的方法。程序围绕事件循环运行,一次只处理一个到达的事件,从而获得对调度的完全控制并消除锁的需求。关键限制是处理程序绝不能执行会阻塞的操作。

约 8 分钟
138

OSTEP / C11

并发编程总结对话

本总结探讨并发执行带来的思维挑战,并强调通过简化设计和成熟模式来编写可靠的并发程序。

约 8 分钟
139

OSTEP / C11

持久性主题对话

本原创对话通过新鲜类比阐述操作系统如何确保信息在关机或故障后依然存在,揭示持久存储背后的额外工作与设计趣味。

约 8 分钟
140

OSTEP / C11

I/O 设备

本章讲解操作系统如何将输入输出设备融入整体系统,涵盖层次化总线架构、设备对外接口与内部实现、基于寄存器的轮询协议,以及用中断实现计算与I/O重叠的方法。

约 8 分钟
141

OSTEP / C11

硬盘驱动器

本章讲解硬盘如何以扇区数组形式持久保存数据,盘片磁道磁头的几何布局,以及寻道与旋转延迟如何决定访问成本。调度策略通过重排请求显著提升吞吐。

约 8 分钟
142

OSTEP / C11

廉价磁盘冗余阵列(RAID)

RAID通过将多块廉价磁盘组织成阵列,在保持对主机系统完全透明的前提下同时提升容量、吞吐和容错能力。本章介绍其对外接口、故障假设、三维评估方法以及最基础的条带化组织。

约 8 分钟
143

OSTEP / C11

插曲:文件与目录

本章用原创视角讲解操作系统如何把持久设备虚拟成文件与目录两层抽象,并展示UNIX风格的创建、读写与删除接口如何把内部inode编号隐藏在人类可读路径之后。

约 8 分钟
144

OSTEP / C11

文件系统实现

本章通过一个极简的 vsfs 示例展示如何在纯软件层面设计磁盘上的核心结构来管理文件,重点讲解超级块、位图、inode 表与数据区的分工,以及系统调用如何映射到这些结构上。

约 8 分钟
145

OSTEP / C11

局部性与快速文件系统

原始UNIX文件系统把磁盘当成随机存储器来用,inode远离数据、空闲空间碎片化、块太小,实际带宽只有磁盘能力的百分之几。快速文件系统通过柱面组(块组)和局部性启发式,把相关文件和元数据放在一起,让寻道变短、顺序传输变长,性能因此大幅提升。

约 8 分钟
146

OSTEP / C11

崩溃一致性:FSCK 与日志

文件系统在磁盘上维护 inode、位图和数据块,一次操作常需多次写入。崩溃可能使状态只部分更新,产生不一致。本章用原创讲解介绍 fsck 事后扫描修复,以及日志(预写日志)如何用少量开销实现快速恢复。

约 8 分钟
147

OSTEP / C11

日志结构文件系统

日志结构文件系统通过内存缓冲全部更新(含元数据)并以大段顺序写入空闲磁盘区域,应对缓存增大后的写主导流量,逼近峰值带宽并缓解RAID小写惩罚。

约 8 分钟
148

OSTEP / C11

基于闪存的固态硬盘

本章介绍NAND闪存如何构成现代固态硬盘,重点讲解页编程前必须整块擦除的物理限制、单元磨损问题,以及这些特性如何影响存储系统的设计与实现。

约 8 分钟
149

OSTEP / C11

数据完整性与保护

本章探讨在硬件不可靠时如何保证写入存储的数据日后能原样读回。内容涵盖局部磁盘故障(潜在扇区错误与静默损坏)、冗余恢复手段以及校验和检测技术,并强调空间与时间开销的权衡。

约 8 分钟
150

OSTEP / C11

持久性总结对话

本对话回顾了操作系统持久性存储的核心难点:崩溃后数据必须存活、更新过程需要可靠恢复,以及磁盘调度、RAID、校验和与设备感知文件系统设计。这些思想在闪存时代依然适用。

约 8 分钟
151

OSTEP / C11

分布式系统对话

师生对话引入分布式系统的基本概念,说明多机协作的不可靠性以及复制与重试等应对手段,为后续分布式文件系统章节做铺垫。

约 8 分钟
152

OSTEP / C11

分布式系统

分布式系统把多台机器经网络连成整体服务。单机、磁盘和链路会故障,但冗余能让用户感觉系统几乎永不中断。通信本质不可靠,必须依靠校验和、确认与重传来构建可用协议。

约 8 分钟
153

OSTEP / C11

网络文件系统(NFS)

本章介绍早期成功的分布式文件系统 NFS,重点讲解客户端-服务器模型如何实现数据共享与透明访问,以及 NFSv2 如何通过完全无状态的协议实现服务器崩溃后的瞬间恢复。

约 8 分钟
154

OSTEP / C11

安德鲁文件系统(AFS)

本章探讨AFS如何借助客户端本地磁盘上的全文件缓存以及服务器主动回调,显著降低服务器负担并实现高可扩展性,同时对比其与NFS轮询模型的差异以及两个版本协议的演进。

约 8 分钟
155

OSTEP / C11

分布式系统总结对话

本总结以轻松对话形式回顾分布式系统的核心思想。组件故障不可避免,但通过部署大量磁盘或机器可以掩盖许多故障。重试等简单机制对瞬时问题非常有效。协议中交换的精确位串决定了系统如何应对故障以及能否良好扩展。对话以幽默方式结束,强调持续学习的重要性。

约 8 分钟
156

OSTEP / C11

关于安全的对话

本对话介绍操作系统安全,强调与可靠性的不同,因为存在故意的对手。它涵盖保护机密、完整和可用资源的需求,以及应对聪明持久攻击者的挑战。

约 8 分钟
157

OSTEP / C11

操作系统安全简介

本章介绍操作系统安全的重要性,解释为何操作系统作为所有计算的基础必须受到保护,并讨论实现安全所面临的挑战。

约 8 分钟
158

OSTEP / C11

身份认证

操作系统必须可靠识别每个进程背后的主体才能正确执行安全策略。本章讨论身份如何通过进程继承以及登录时的初始绑定附加到进程上。

约 8 分钟
159

OSTEP / C11

访问控制

本章介绍操作系统如何将既定安全策略转化为对每次资源请求的允许或拒绝判定,重点讨论参考监视器的职责、主体与客体模型,以及访问控制列表和能力两种实现路径在效率与灵活性上的差异。

约 8 分钟
160

OSTEP / C11

密码学

操作系统无法保护已经离开其硬件管辖范围的数据。密码学借助密钥把明文变成密文,使未授权者即便拿到比特也无法还原含义或进行可控篡改。本章用原创讲解介绍对称加密模型、密钥保密的决定性作用,以及哈希如何提供完整性检测。

约 8 分钟
161

OSTEP / C11

分布式系统安全

本章探讨分布式系统特有的安全挑战:单机操作系统无法管辖远程主机,也无法完全信任中间网络。密码或公钥认证加上证书颁发机构签发的证书,是在不可信环境中建立身份并保护通信的核心手段。

约 8 分钟
162

OSTEP / C11

虚拟机

虚拟机监视器在硬件与操作系统之间插入透明抽象层,让多个客户操作系统同时运行并各自以为独占机器。本附录讲解其历史背景、当代用途以及CPU虚拟化的关键机制。

约 8 分钟
163

OSTEP / C11

管程

本附录介绍管程如何将共享数据与访问操作封装为单一模块并自动提供互斥,同时通过条件变量实现等待与唤醒,并对比Hoare与Mesa两种语义在实际系统中的差异。

约 8 分钟