第48章 C++速查手册(Cheat Sheet)

第48章 C++速查手册(Cheat Sheet)

前面 47 章把该讲的知识点都讲完了。但真正写代码的时候,你不会去翻一章 3000 行的教程——你需要的是一张表:类型多大、格式符怎么写、报错是什么意思、这个容器该不该选。

本章就是为此准备的。它不教你新东西,只帮你快速回忆和快速查证。

✅ 本章的可靠性说明:本章所有"实测"结论都来自 Apple clang 21(libc++)+ -std=c++23/-std=c++26,在 macOS(Apple 芯片)上运行得出。其中类型大小、库可用性这类结论换了平台就会变,请注意表头标注。


48.1 编译与运行速查

1
2
3
4
# 最常用的三条命令
clang++ -std=c++23 -Wall -Wextra -O2 -o app main.cpp   # 编译
./app                                                  # 运行
clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined -o app_dbg main.cpp   # 带检查的调试版
目的clang++g++说明
指定标准-std=c++23-std=c++23C++26 早期实现用 -std=c++26 或 -std=c++2c
打开警告-Wall -Wextra同再加 -Wpedantic 检查语言扩展
优化-O0 / -O2 / -O3 / -Os同调试用 -O0 -g,发布用 -O2
调试信息-g同配合 lldb/gdb
地址检查-fsanitize=address同抓越界、悬垂指针、泄漏
未定义行为检查-fsanitize=undefined同抓整数溢出、空指针解引用
线程检查-fsanitize=thread同抓数据竞争(不能和 address 一起用)
只做语法检查-fsyntax-only同不生成目标文件,检查最快
只看预处理结果-E同排查宏展开问题
生成汇编-S同看编译器到底生成了什么
链接库-lfoo -L/path同顺序敏感:被依赖的库放后面
显示完整命令-###-v看编译器实际调用了什么

💡 一条经验:永远先加 -Wall -Wextra。编译器免费帮你看代码,别浪费。


48.2 类型速查:大小、范围与格式化

本机实测大小(macOS / Apple 芯片 / 64 位)

类型大小范围(近似)printfstd::format
char1−128 ~ 127(是否有符号是实现定义)%c{:c}
signed char / unsigned char1−128127 / 0255%hhd / %hhu—
short2−32768 ~ 32767%hd—
int4±2.1×10⁹%d{:d}
unsigned40 ~ 4.29×10⁹%u{:d}
long8±9.2×10¹⁸%ld—
long long8±9.2×10¹⁸%lld—
float4约 7 位有效数字%f{:.2f}
double8约 15~16 位有效数字%f{}
long double8(macOS)同 double(x86 Linux 上常为 16)%Lf—
bool1true/false—(用 %d 打印){}
std::size_t8无符号%zu—
std::ptrdiff_t8有符号%td—
指针8—%p{}

格式化要点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <cstdio>
#include <format>
#include <iostream>

int main() {
    // printf:靠"格式符"对齐类型,写错了是 UB(不会报错,只会出怪结果)
    std::printf("%d %u %zu %lld %.2f %c %s %p\n",
                -1, 1u, sizeof(int), 1LL, 3.14159, 'A', "text", (void*)nullptr);

    // std::format(C++20):靠"类型"检查,写错了编译期就报错
    std::cout << std::format("{}\n", 42);                     // 42
    std::cout << std::format("{:d} {:x} {:#x}\n", 255, 255, 255);   // 255 ff 0xff
    std::cout << std::format("{:.2f} {:>8} {:08.3f}\n", 3.14159, "hi", 3.14159);
    // 3.14       hi 0003.142
    std::cout << std::format("{:L}\n", 1234567);              // 按 locale 加千位分隔符
}
想要的效果printfstd::format
十进制整数%d{:d}
十六进制%x / %#x{:x} / {#x}
保留两位小数%.2f{:.2f}
右对齐宽度 8%8d{:>8}
补零到 8 位%08d{:08d}
本地化数字无直接支持{:L}

⚠️ 提醒:%zu 对应 size_t,%lld 对应 long long。类型和格式符不匹配是未定义行为——很多"printf 输出乱码"的锅都在这里。写 C++ 时优先用 std::format 或 std::cout,让类型检查帮你挡枪。


48.3 声明、初始化与关键字

四种初始化形式

1
2
3
4
int a = 3.14;      // ① 拷贝初始化:可能发生隐式窄化(3.14 → 3),不推荐
int b(3.14);       // ② 直接初始化:同样可能窄化
int c{3.14};       // ③ 列表初始化:❌ 编译错误(防止窄化)——推荐
int d{};           // ④ 值初始化:d == 0

结论:优先用 {}(列表初始化)。 它最大的优点是不允许窄化,其次能避免"最令人头疼的解析"(Type x(); 是函数声明,不是变量定义)。

常量关键字对照

关键字含义何时求值典型用途
const“我保证不修改”运行期(可能是常量)参数、成员、局部只读
constexpr“可以在编译期求值”尽可能编译期常量、constexpr 函数、数组大小
consteval“必须在编译期求值”只能编译期编译期检查、生成数据
constinit“必须在编译期完成初始化”初始化期避免静态初始化顺序问题
1
2
3
4
constexpr int n = 10;              // 编译期常量
const int m = 20;                  // 可能只是运行期常量
consteval int sq(int x) { return x * x; }
static_assert(sq(5) == 25);

类型推导

写法结果
auto x = expr;按值推导,会丢掉引用和顶层 const
auto& y = expr;推导为引用
const auto& z = expr;万能"只读引用",不产生拷贝
decltype(expr)完全保留类型和值类别(decltype((x)) 得到引用)
decltype(auto)用 decltype 的规则推导返回类型(常用于完美转发)
1
2
3
4
5
int v = 1;
auto   a = v;        // int
auto&  b = v;        // int&
decltype(v)  c = v;  // int
decltype((v)) d = v; // int&(多了一层括号)

结构化绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <map>
#include <iostream>
#include <string>
#include <tuple>

int main() {
    std::pair<int, std::string> p{1, "one"};
    auto [id, name] = p;                 // 拷贝
    auto& [id2, name2] = p;              // 引用,改 id2 就是改 p.first

    std::map<std::string, int> m{{"a", 1}};
    for (const auto& [key, value] : m) {   // 最常用的写法
        std::cout << key << " = " << value << '\n';
    }

    std::cout << id << ' ' << name << ' ' << id2 << ' ' << name2 << '\n';
}

48.4 运算符优先级(由高到低)

级别运算符结合性
1::左
2a++ a--、f()、a[]、a.b、a->b左
3++a --a、+a -a、! ~、(T) 强制转换、*a、&a、sizeof、new、delete、co_await右
4.* ->*(成员指针)左
5* / %左
6+ -左
7<< >>左
8<=>左
9< <= > >=左
10== !=左
11&(按位与)左
12^左
13``
14&&左
15`
16?:、= 及复合赋值、throw、co_yield右
17,左

💡 别背。记住两条就够了:

  1. 乘除 > 加减 > 移位 > 比较 > 位运算 > 逻辑运算 > 赋值;
  2. 不确定就加括号。a & 0xF == 0 会先算 ==(比较优先级高于按位与),这是个经典陷阱。

48.5 类型转换

转换用途危险度
static_cast<T>(x)数值转换、上行转型、void*↔T*、调用 explicit 构造函数低(编译期检查)
dynamic_cast<T*>(p)多态类型的下行转型/交叉转型,失败返回 nullptr(引用版抛 bad_cast)低(需 RTTI)
const_cast<T>(x)去掉/加上 const高(改真正常量对象是 UB)
reinterpret_cast<T>(x)重新解释位模式最高(几乎不保证可移植)
C 风格 (T)x依次尝试上述几种别用,看不出意图
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
double d = 3.9;
int i = static_cast<int>(d);          // 3(截断,不是四舍五入)

struct Base { virtual ~Base() = default; };
struct Derived : Base { int v = 1; };

void f(Base* b) {
    if (auto* p = dynamic_cast<Derived*>(b)) {   // 失败返回 nullptr
        (void)p->v;
    }
}

隐式转换的常见顺序:整型提升 → 整型转换 → 浮点转换 → 浮点-整型互转 → 指针转换 → 布尔转换 → 用户定义转换。

⚠️ 两个高频坑:

  1. 有符号/无符号混用:-1 < 1u 是 false,因为 -1 被转成了很大的无符号数;开着 -Wall 一般会警告 -Wsign-compare。
  2. explicit 构造函数:单参数构造函数默认可以隐式转换,容易埋雷,记得加 explicit。

48.6 指针、引用与智能指针

你想表达的所有权用什么
独占所有权std::unique_ptr<T>(std::make_unique<T>(...))
共享所有权std::shared_ptr<T>(std::make_shared<T>(...))
观察共享对象、不延长生命周期std::weak_ptr<T>(用 lock() 临时提升)
不拥有,但可能为空裸指针 T*
不拥有,且一定不为空引用 T&(参数传递首选)
1
2
3
4
5
6
7
8
9
#include <memory>

void use(const Widget& w);                     // 只读借用:const 引用
void maybe(Widget* w) { if (w) use(*w); }      // 可能为空:指针

auto owned = std::make_unique<Widget>();       // 独占
auto shared = std::make_shared<Widget>();      // 共享
std::weak_ptr<Widget> weak = shared;           // 观察
if (auto locked = weak.lock()) { /* 还活着 */ }
对比项指针 T*引用 T&
可以为空✅❌
可以改指向✅❌
必须初始化❌✅
支持指针算术✅❌
作为参数含义“可能没有”“一定有”

💡 经验法则:函数参数能用 const T& 就用它;**需要表达"可能没有"**才用指针;需要拥有就用智能指针。裸的 new/delete 在现代 C++ 里应该绝迹。


48.7 标准库头文件索引

你想做的事头文件
输入输出流<iostream> <fstream> <sstream> <iomanip>
格式化输出<format>(C++20)、<print>(C++23)
字符串<string> <string_view>
容器<vector> <array> <deque> <list> <forward_list> <map> <set> <unordered_map> <unordered_set> <stack> <queue>
算法与数值<algorithm> <numeric> <ranges>
智能指针与内存<memory> <memory_resource> <scoped_allocator>
可选值/错误<optional> <variant> <any> <expected>(C++23)
时间与随机<chrono> <random>
文件系统<filesystem>
并发<thread> <mutex> <condition_variable> <atomic> <future> <latch> <barrier> <semaphore>
协程<coroutine> <generator>(C++23,本机暂无)
位运算<bit> <cstdint>
类型特征<type_traits> <concepts>
编译期辅助<version> <source_location>

48.8 容器选择表

容器结构随机访问中间插入/删除查找内存连续性
vector动态数组✅ O(1)❌ O(n)线性 O(n)✅
array定长数组✅ O(1)❌线性✅
deque分段连续✅ O(1)两端 O(1)线性❌
list双向链表❌✅ O(1)(已知位置)线性❌
forward_list单向链表❌✅ O(1)线性❌
map / set红黑树❌O(log n)✅ O(log n),有序❌
unordered_map / unordered_set哈希表❌平均 O(1)✅ 平均 O(1),无序❌
flat_map / flat_set(C++23)排序的连续存储✅O(n)✅ O(log n),缓存友好✅
stack / queue / priority_queue容器适配器❌——取决于底层容器

一句话决策

graph TD
    A["要存一堆元素"] --> B{"需要按位置随机访问吗?"}
    B -->|需要| C{"大小固定吗?"}
    C -->|固定| D["std::array"]
    C -->|可变| E["std::vector(默认首选)"]
    B -->|不需要| F{"需要按 key 查找吗?"}
    F -->|需要| G{"需要有序遍历吗?"}
    G -->|需要| H["std::map / std::set"]
    G -->|不需要| I["std::unordered_map / unordered_set"]
    F -->|不需要| J{"频繁在中间插入删除?"}
    J -->|是| K["std::list(但要先想想真的需要吗)"]
    J -->|否| E

    style E fill:#ccffcc
    style K fill:#ffcccc

💡 最重要的一条:默认用 std::vector。它的连续内存带来的缓存友好性,通常比"理论复杂度更优"重要得多。链表看起来很美好,实际用起来经常更慢。


48.9 算法与 lambda 速查

常用算法(<algorithm>)

目的算法
查找find find_if count count_if binary_search lower_bound upper_bound equal_range
排序sort stable_sort partial_sort nth_element
修改fill copy copy_if transform replace remove remove_if unique
判断all_of any_of none_of equal is_sorted
最值min_element max_element clamp
数值(<numeric>)accumulate reduce iota inner_product partial_sum
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <algorithm>
#include <iostream>
#include <numeric>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> v{5, 2, 9, 1, 7};
    std::ranges::sort(v);                     // C++20 的 ranges 版,直接传容器

    auto evens = v | std::views::filter([](int x){ return x % 2 == 0; });  // 惰性视图
    for (int x : evens) std::cout << x << ' ';      // 2
    std::cout << '\n';

    std::cout << std::accumulate(v.begin(), v.end(), 0) << '\n';   // 24
}

lambda 语法速查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
auto f1 = []{ return 1; };                          // 无参数
auto f2 = [](int x){ return x * 2; };               // 有参数
auto f3 = [](int x) -> long { return x; };          // 指定返回类型
int n = 10;
auto f4 = [n](int x){ return x + n; };              // 按值捕获
auto f5 = [&n](int x){ n += x; };                   // 按引用捕获
auto f6 = [=, &n]{ return n; };                     // 其余按值,n 按引用
auto f7 = [p = std::move(ptr)]{ return *p; };       // 初始化捕获(C++14)
auto f8 = [](auto x){ return x; };                  // 泛型 lambda(C++14)
auto f9 = [](this auto&& self, int x){ return x; }; // 显式对象参数(C++23)

48.10 类:特殊成员函数与继承

Rule of 0 / 3 / 5

原则含义
Rule of 0不写任何特殊成员函数,靠成员自己管理资源——首选
Rule of 3如果要写析构函数、拷贝构造、拷贝赋值中的任意一个,通常三个都要写
Rule of 5再加上移动构造、移动赋值(写 = 支持移动语义)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Rule of 5 的手写签名
class Resource {
public:
    Resource();                                        // 构造
    ~Resource();                                       // 析构
    Resource(const Resource&);                         // 拷贝构造
    Resource& operator=(const Resource&);              // 拷贝赋值
    Resource(Resource&&) noexcept;                     // 移动构造
    Resource& operator=(Resource&&) noexcept;           // 移动赋值
};

显式声明(= default / = delete)

1
2
3
4
5
6
7
class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;             // 禁止拷贝
    NonCopyable& operator=(const NonCopyable&) = delete;
    void legacy() = delete("请改用 newApi()");             // C++26:删除时给出理由
};

继承要点

要点说明
virtual 析构多态基类必须有虚析构,否则 delete 基类指针是 UB
override重写虚函数必须写,写错了编译器会告诉你
final禁止继续继承/重写
dynamic_cast只有当基类至少有一个虚函数时才能用(需要 RTTI)
继承构造函数using Base::Base; —— 注意:其他基类必须能默认构造,否则被继承的构造函数会被隐式删除

48.11 模板与 concepts 速查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <concepts>
#include <iostream>
#include <vector>

// 函数模板 + 约束(C++20)
template <std::integral T>
T twice(T x) { return x * 2; }

// 可变参数模板 + 折叠表达式(C++17)
template <typename... Ts>
auto sum(const Ts&... xs) { return (xs + ... + 0); }

// if constexpr:编译期分支(C++17)
template <typename T>
void describe(const T& v) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "整数: " << v << '\n';
    } else {
        std::cout << "其他类型\n";
    }
}

// 可简写的约束(C++20)
void only_ints(std::integral auto x) { (void)x; }

int main() {
    std::cout << twice(21) << '\n';      // 42
    std::cout << sum(1, 2, 3) << '\n';   // 6
    describe(7);
}
术语说明
模板参数推导从实参推出 T;注意推导时不会发生隐式转换(max(1, 2.0) 推不出来)
显式实例化template class Foo<int>;
特化template<> struct Foo<int> { ... };
SFINAE替换失败不是错误(老式约束手段,现在优先用 concepts)
concepts给类型加"要求",错误信息会友好得多
折叠表达式(args + ...)、(... + args)、(args + ... + init)、(init + ... + args)

48.12 编译错误解码器

下表是你在本书各章最可能撞见的错误,以及它们真正的意思:

编译器说通常意味着怎么修
use of undeclared identifier 'std'忘了 #include 相关头文件加 #include <iostream> 等
no member named 'cout' in namespace 'std'少包含 <iostream>同上
expected ';' after ...上一行少了分号(错误位置常指向下一行)看上一行结尾
expected unqualified-id括号/大括号不配对,或语句写在了函数外面检查配对与位置
no matching function for call to 'f'参数类型/个数不对看候选函数列出的签名
function definition is not allowed here在函数里面定义了函数把内层函数移到外面
undefined reference to 'f'链接错误:只有声明没有定义,或没链接库补定义 / 加 -l;模板定义要放头文件
multiple definition of 'x'头文件里定义了非 inline 的全局对象/函数加 inline,或改为声明放头、定义放源
invalid use of incomplete type只前置声明了类型就用了它(例如 unique_ptr<T> 的析构)补 #include;unique_ptr 的析构要在能看到完整类型处
constexpr variable must be initialized by a constant expression用来初始化的表达式不是编译期常量检查是否用了非 constexpr 函数、goto、异常等
consteval if is always true in an immediate context在 consteval 函数里用了 if consteval把函数改成 constexpr,或删掉 if consteval
'nodiscard' attribute cannot be applied to types把 [[nodiscard]] 写在了 lambda 参数表后面改用具名的函数对象(见 27.7)
hex escape sequence out of range十六进制转义超出一个字节(如 "\x{20AC}")想写"字符"要用 \u{...}(见 27.20)
character too large for enclosing character literal type字符字面量装不下该码点(如 char c = '\u{20AC}')用字符串或 char32_t
cannot form a reference to 'void'把 void 当成了类型用检查模板实参 / 函数返回类型
comparison between two arrays is ill-formed in C++26直接比较了两个数组用 std::array/std::equal(见 29.18)
warning: unused variable变量声明了没用用 [[maybe_unused]]、(void)x,或直接删掉
error: 'x' file not found缺头文件:第三方库或标准库尚未实现装依赖,或用特性测试宏分支

💡 读错误的三条法则:

  1. 从第一个错误开始修。后面的错误常常是它引起的连锁反应(“级联错误”)。
  2. 看列号指向的那个符号,不要只看行。
  3. 模板错误先看最后一行的 “in instantiation of”,它会告诉你是哪次实例化引爆的。

48.13 四种"不那么规则"的行为

类别含义例子
未定义行为(UB)标准不做任何保证,编译器可以任意处理越界访问、有符号整数溢出、解引用空指针、读取未初始化值
未指定行为(Unspecified)标准给出多种可能,实现自己选,但不必说明函数实参求值顺序、a[i] = i++ 里 i 的求值顺序
实现定义行为(Implementation-defined)实现必须选一个并写进文档sizeof(int)、char 是否有符号、右移负数
错误行为(Erroneous,C++26)C++26 新引入的中间类别:不要求诊断,但要求"不许乱来"读取未初始化的 int(UB 被"降级"为错误行为)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 1. 有符号溢出 = UB;无符号溢出 = 良好定义(按 2^n 取模)
unsigned u = 0u - 1u;      // ✅ 良好定义:UINT_MAX
// int i = INT_MAX; i += 1;   // ❌ UB

// 2. UB 最可怕的地方:编译器会"假设它不会发生",从而删掉你的检查
int f(int* p) {
    int x = *p;      // 如果 p 是空的,这里是 UB
    if (p == nullptr) return 0;   // 编译器可能把这一行整个删掉!
    return x;
}

⚠️ 送你一句话:UB 不是"运行时会崩",而是"编译器可以假设它永远不会发生"。 所以 UB 的真实表现往往是"你的判空代码被优化掉了"。


48.14 C++ 版本特性速查

版本语言标准库
C++11auto、nullptr、lambda、右值引用/移动语义、constexpr、enum class、可变参数模板<thread> <mutex> <unordered_*> <array> <tuple> <chrono> <random>
C++14泛型 lambda、auto 返回类型推导、变量模板、[[deprecated]]<shared_mutex>(shared_timed_mutex;shared_mutex 本体是 C++17)、std::make_unique
C++17结构化绑定、if constexpr、折叠表达式、类模板实参推导(CTAD)、内联变量、[[nodiscard]]<optional> <variant> <any> <string_view> <filesystem> 并行算法
C++20concepts、ranges、协程、modules、三路比较 <=>、consteval、指定初始化器<ranges> <span> <bit> <format> <numbers> <source_location> <latch> <semaphore>
C++23显式对象参数(deducing this)、if consteval、多维下标、static operator()/[]、auto(x)、[[assume]]、size_t 的 z 后缀、\N{...}/\u{...} 转义、UTF-8 改进<expected> <mdspan> <print> <flat_map> <flat_set> <stacktrace> <generator> <stdfloat> <spanstream>
C++26反射(^^)、契约(contract_assert/pre/post)、包索引、= delete("理由")、[[indeterminate]]、Unicode 标识符、数组比较变错误<meta> <hive> <inplace_vector> <simd> <execution>、std::optional<T&>、std::function_ref

📌 注意:上表是"标准里有什么",不等于"你的编译器实现了什么"。见下一节。


48.15 本机实测:Apple clang 21 + libc++ 的可用性

语言特性(-std=c++23 / -std=c++26 实测):

特性状态
C++11~C++20 语言特性✅ 基本齐全
显式对象参数、if consteval、多维下标、static operator()/[]✅
\N{...}、\u{...}、\x{...} 定界转义✅(需 -std=c++23)
包索引、结构化绑定作为条件、= delete("理由")✅(需 -std=c++26)
[[indeterminate]]⚠️ 语法接受但属性被忽略(无效果)
契约 contract_assert、反射 ^^、#embed❌ 未实现
constexpr 异常、constexpr placement new❌ 未实现

标准库头文件(-std=c++23 实测):

可用 ✅缺失 ❌
<expected> <mdspan> <print> <format> <flat_map> <flat_set> <latch> <barrier> <semaphore> <source_location> <numbers> <bit> <span><stacktrace> <generator> <stdfloat> <spanstream> <bytes> <simd> <hive> <inplace_vector> <syncstream>(头文件在,但 std::osyncstream 没实现)

⚠️ <execution> 的情况比较特殊:头文件存在,但只有基本的执行策略类型名, std::execution::par 等并行策略没有实现,写成 std::sort(std::execution::par, ...) 会报 no member named 'par' in namespace 'std::execution'。

⚠️ <execution> 是个陷阱:头文件存在,能 #include 成功,但里面没有 std::execution::par / par_unseq / unseq。只检查"头文件在不在"会得出错误结论,必须实际用一下才知道。这说明:用 echo '#include <xxx>' | clang++ -fsyntax-only - 只能验证头文件,验证不了符号。

缺失的具体实体:std::move_only_function、std::function_ref、std::execution::par(及 seq/unseq/par_unseq)、std::views::as_const —— 这些在本机 libc++ 里都还没有。

💡 怎么自己验证:

  • 查头文件在不在:echo '#include <stacktrace>' | clang++ -std=c++23 -x c++ -fsyntax-only -
  • 查符号在不在(更严格,能抓出 <execution> 这种"空壳头文件"): echo '#include <execution> int main(){ auto p = std::execution::par; (void)p; }' | clang++ -std=c++23 -x c++ -fsyntax-only -
  • 查特性宏:#include <version> 然后 #ifdef __cpp_lib_xxx

这两种方式比任何教程都可靠——包括本章。


48.16 交卷前的自检清单

写完一段 C++ 代码,花 30 秒过一遍这张表:

  • 编译时开着 -Wall -Wextra,没有任何警告?
  • 所有 #include 都写了?(不要靠"别的头文件恰好带进来了")
  • 常量用了 constexpr 而不是魔法数字?
  • 参数用了 const T&(除非确实需要拷贝或修改)?
  • 资源用 RAII 管理,没有裸 new/delete?
  • 多态基类有 virtual ~Base()?
  • 重写虚函数写了 override?
  • 没有"有符号/无符号比较"?
  • 没有返回局部变量的引用/指针?
  • 容器默认选了 std::vector(而不是无脑 list)?
  • 迭代器失效的风险考虑过了?(vector 扩容会让迭代器失效)
  • 用 -fsanitize=address,undefined 跑过一遍测试?
  • 代码里的"未初始化变量"都没有(C++26 开始属于错误行为)?

🎯 最后:速查表能帮你回忆,但不能替你思考。真正让代码变好的,是理解每个选择背后的权衡——而这正是前面 47 章在做的事。祝你写代码顺利!


本章完