本工具使用的开源库

本工具代码中捆绑了 1 个开源库。

C++ 速查 — 简明参考

C++17/20 语法、标准库、类、模板与最常用惯用法速查手册,覆盖约 80% 日常场景。

C++

C++ C++17 / C++20

ISO C++ (GCC / Clang / MSVC) · 多范式(OOP · 泛型 · 函数式) · 静态 · 强类型 · 名义类型

学习路径

先跟着「Hello World 与构建环境」跑通编译流程 → 熟悉变量、类型、流程控制与函数 → 用标准库容器(vector/map)与字符串 → 理解 RAII 与智能指针(mem 节)→ 再看 OOP 与模板 → 最后按需查线程、网络、正则与构建调试。FAQ 节适合回头避坑。

1.Hello World 与构建环境

从零开始编译、运行并组织一个 C++ 程序:工具链、命令行参数、多文件与 C++20 模块。

最小程序

每个 C++ 程序从 main() 开始执行,返回 0 表示成功。std::cout 是标准输出流,用 << 送入内容。

1
2
3
4
5
6
#include <iostream>
int main() {
std::cout << "Hello, world!\n";
return 0;
}

编译与运行

用 g++ 或 clang++ 编译:-std 指定 C++ 标准版本,-Wall -Wextra 开启警告,-o 指定输出文件名,再 ./app 运行。

1
2
3
4
5
6
7
8
// $ g++ -std=c++17 -Wall -Wextra main.cpp -o app
// $ ./app
// clang++ 参数相同;MSVC 用 cl /EHsc main.cpp
#include <iostream>
int main() {
std::cout << "Built with C++17\n";
return 0;
}

CMake 构建

CMake 是跨平台主流构建系统:CMakeLists.txt 声明项目与目标,-B 指定构建目录,--build 编译,产物在 build/ 下。

1
2
3
4
5
6
7
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(app LANGUAGES CXX)
add_executable(app main.cpp)
target_compile_features(app PRIVATE cxx_std_17)
// $ cmake -B build && cmake --build build

命令行参数

main 的 argc 是参数个数,argv 是参数字符串数组,argv[0] 恒为程序名。程序从 argv[1] 开始读取用户参数。

1
2
3
4
5
6
7
#include <iostream>
int main(int argc, char* argv[]) {
std::cout << "argc=" << argc << '\n';
for (int i = 0; i < argc; ++i)
std::cout << "argv[" << i << "]=" << argv[i] << '\n';
return 0;
}

退出码

main 的返回值传给操作系统作为进程退出码:0 表示成功,非 0 表示错误类别。用 EXIT_SUCCESS / EXIT_FAILURE 更可读。

1
2
3
4
5
#include <cstdlib>
int main() {
if (!work()) return 1; // 非 0 = 失败
return EXIT_SUCCESS; // 等价 return 0
}

多文件编译

声明放头文件(#pragma once 防重复包含),定义放 .cpp,使用处 #include。编译时把各 .cpp 一起传给编译器链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// math.h —— 声明
#pragma once
int add(int a, int b);
// math.cpp —— 定义
#include "math.h"
int add(int a, int b) { return a + b; }
// main.cpp —— 使用
#include <iostream>
#include "math.h"
int main() {
std::cout << add(2, 3) << '\n';
}
// $ g++ -std=c++17 math.cpp main.cpp -o app

C++20 模块

模块替代头文件:export module 声明模块、export 导出符号、import 导入,编译更快、无宏污染,但仍处推广期。

1
2
3
4
5
6
7
8
9
10
11
// math.cppm —— 模块
module;
#include <cmath>
export module math;
export int sq(int x) { return x * x; }
// main.cpp —— 导入
import math;
#include <iostream>
int main() { std::cout << sq(5) << '\n'; }
// $ g++ -std=c++20 -fmodules-ts math.cppm main.cpp -o app

命名空间

命名空间组织符号避免冲突。用命名空间限定(geo::area)访问;using namespace 一次引入全部符号易产生歧义,优先 using std::cout 精确引入。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
namespace geo {
constexpr double pi = 3.14159;
double area(double r) { return pi * r * r; }
}
int main() {
std::cout << geo::area(1.0) << '\n';
using std::cout; // 精确引入
cout << geo::pi << '\n';
}

2.变量与常量

声明与类型推导、const/constexpr、引用、结构化绑定、作用域与 C++ 风格类型转换。

auto 类型推导

auto 根据初始化器推导类型,必须立即初始化。推导会去掉引用与顶层 const,需要时显式写 const auto&。

1
2
3
4
auto x = 42; // int
auto d = 3.14; // double
auto s = std::string("hi");
const auto& r = x; // 保留 const + 引用

const 与 constexpr

const 表示值不可变;constexpr 保证编译期求值,可用于数组大小、模板参数。C++20 后 constexpr 函数可含更多逻辑。

1
2
3
4
const int n = 42; // 运行期或编译期常量
constexpr int k = 42; // 必须是编译期常量
constexpr int sq(int x) { return x * x; }
static_assert(sq(6) == 36); // 编译期校验

引用

引用是已存在对象的别名:声明必须初始化、永远指向同一对象。作参数传引用可避免拷贝并允许修改原值。

1
2
3
4
5
int x = 1;
int& r = x; // 引用是别名,不能为空、不能改绑
r = 2; // 修改的是 x
const int& c = x; // 只读视图
void bump(int& v) { ++v; } // 传引用改原值

结构化绑定

C++17 把 pair/tuple/结构体/数组解包成命名变量。map 迭代里解包 key/value 是最常用场景。

1
2
3
4
5
6
std::pair<int, double> p{1, 2.5};
auto [a, b] = p; // 解包到命名变量
std::map<std::string, int> m;
for (const auto& [key, val] : m) {
// 直接用 key / val
}

内联变量

C++17 前头文件里的非 constexpr 全局变量会引发重复定义错误;inline 变量允许多个编译单元共享同一对象。

1
2
3
4
5
// header.h
struct Config {
static inline int threads = 4; // 头文件中定义
};
inline constexpr int kMax = 100; // 常用作头文件常量

mutable 成员

const 成员函数中也能修改 mutable 成员,典型用于缓存、调用计数、互斥锁等「逻辑上不改变对象」的状态。

1
2
3
4
struct Counter {
int get() const { ++n_; return n_; }
mutable int n_ = 0; // const 函数也能改
};

decltype

decltype 取表达式的声明类型。decltype((x)) 加括号推导为引用,是常见陷阱;后置返回类型常用于模板函数。

1
2
3
4
5
int x = 1;
decltype(x) y = 2; // int
decltype((x)) r = x; // int&,多一层括号 = 引用
// 后置返回类型:返回类型依赖参数
template <typename T> auto twice(T v) -> decltype(v + v) { return v + v; }

作用域与遮蔽

C++ 支持块作用域遮蔽外层同名变量。避免遮蔽全局名 —— 用 ::n 可显式访问全局,但可读性差。

1
2
3
4
5
6
7
8
#include <iostream>
int n = 1; // 全局
int main() {
int n = 2; // 遮蔽全局
{ int n = 3; } // 块作用域,离开即销毁
std::cout << n << '\n'; // 2
std::cout << ::n << '\n'; // 1,显式访问全局
}

字面量后缀

后缀指定字面量类型:LL 长整型、f 浮点、s 转 std::string。0b 二进制前缀与 ' 数字分隔符提升可读性。

1
2
3
4
5
int a = 0b1010; // 二进制 (C++14)
int b = 1'000'000; // 数字分隔符
long long c = 5LL;
double d = 3.14f; // float
auto s = "hi"s; // std::string(using std::string_literals)

C++ 风格转换

static_cast 编译期语义转换;dynamic_cast 运行期多态转换(指针失败返 nullptr、引用抛 bad_cast);const_cast 去除 const 须确保原对象非 const。

1
2
3
4
5
6
double d = 3.7;
int a = static_cast<int>(d); // 语义转换
const double cd = 3.7;
double& ref = const_cast<double&>(cd); // 去 const(慎用)
// dynamic_cast:下行多态转换
Derived* dp = dynamic_cast<Derived*>(base_ptr);

3.数据类型

内置类型、枚举、结构体、模板类型与 std::optional/variant/any 等现代工具类型。

内置类型

内置类型按大小与有无符号区分。<cstdint> 提供定宽类型 int32_t/uint64_t,跨平台行为一致。

1
2
3
4
5
6
7
#include <cstdint>
int32_t i = -1; // 定宽有符号整型
uint64_t u = 0; // 定宽无符号整型
float f = 1.5f; // 32 位浮点
double d = 2.5; // 64 位浮点
bool b = true;
char c = 'A';

枚举类

enum class 比裸 enum 更安全:值必须用作用域限定访问,且不会隐式转 int,避免名字泄漏与整型误用。

1
2
3
4
enum class Color { Red, Green, Blue };
Color c = Color::Red; // 必须带作用域
int n = static_cast<int>(c); // 0,显式转换
enum class Flag : uint8_t { A = 1, B = 2 }; // 指定底层类型

结构体

struct 默认成员公有,适合打包无行为的数据。带默认值的成员可用花括号聚合初始化一行建好对象。

1
2
3
4
5
struct Point {
double x = 0.0; // 默认成员初始化
double y = 0.0;
};
Point p{1.0, 2.0}; // 聚合初始化

联合体

union 让多个成员共享同一块内存、同一时刻只保存一个。标准 C++ 用 std::variant 更安全,union 一般只用于底层内存复用。

1
2
3
4
5
6
union Value {
int i;
double d; // 同一内存,一次只含一个成员
};
Value v;
v.d = 3.14; // 写入 d,i 的旧值失效

模板类型

模板把类型参数化,编译器按用到的类型生成对应实例,是泛型编程与标准库容器的基础。

1
2
3
4
5
template <typename T> struct Box {
T value;
};
Box<int> b1{42};
Box<double> b2{3.14}; // 同一模板不同实例

类型别名

using 声明类型别名比 typedef 更清晰且能定义模板别名。别名不产生新类型,只是同类型的另一个名字。

1
2
3
4
5
using Id = unsigned long; // 类型别名
using PairInt = std::pair<int, int>;
template <typename T>
using Ptr = std::shared_ptr<T>; // 模板别名
Ptr<int> p = std::make_shared<int>(1);

optional 可选值

std::optional 表达「可能有值也可能没有」的返回值,替代哨兵值或 out 参数。上下文布尔判断检查存在性,* 取内容。

1
2
3
4
5
6
7
8
#include <optional>
std::optional<int> div(int a, int b) {
if (b == 0) return std::nullopt; // 无值
return a / b;
}
if (auto v = div(6, 2)) {
int n = *v; // 解包
}

variant 类型联合

std::variant 是类型安全的联合体,任一时刻持有类型列表中的一个。std::get 取指定类型(不符抛 bad_variant_access),std::get_if 返回指针不抛异常。

1
2
3
4
5
#include <variant>
std::variant<int, double, std::string> v;
v = 3.14; // 当前持有 double
double d = std::get<double>(v);
if (auto* s = std::get_if<std::string>(&v)) { } // 安全访问

any 任意类型

std::any 可存任意类型,运行时再取回。适合异构容器或插件传参,但有类型擦除开销,尽量用 variant。

1
2
3
#include <any>
std::any a = 42;
int n = std::any_cast<int>(a); // 类型不符抛 bad_any_cast

pair 与 tuple

pair 存两个值、tuple 存任意多个。结构化绑定把元素解包成命名变量,比 std::get 按索引取更可读。

1
2
3
4
5
std::pair<int, int> p{1, 2};
auto [a, b] = p; // 解包
std::tuple<int, double, char> t{1, 2.5, 'x'};
auto [i, d, c] = t;
std::get<1>(t); // 按索引取 2.5

4.指针与数组

裸指针、智能指针、数组与引用 —— 谁拥有内存、何时用哪一种。

裸指针

裸指针存对象的地址,* 解引用、& 取地址。裸指针不拥有内存,需要配对 new/delete 或交给智能指针。

1
2
3
4
5
int x = 10;
int* p = &x; // 指向 x 的地址
*p = 20; // 通过指针修改 x
if (p == nullptr) { } // 判空
int* q = nullptr; // 空指针

指针与数组

数组名在表达式中退化为指向首元素的指针,p+i 是地址偏移、*(p+i) 取值。这是继承自 C 的底层行为。

1
2
3
4
int arr[4] = {1, 2, 3, 4};
int* p = arr; // 数组名退化为首元素指针
std::cout << *(p + 2); // 3,指针算术
// arr[i] 等价于 *(arr + i)

nullptr 空指针

nullptr 是类型安全的空指针常量,替代 NULL 或 0。解引用空指针是未定义行为,使用前务必判空。

1
2
3
4
5
int* p = nullptr;
if (p == nullptr) { /* 安全分支 */ }
void f(int* p) {
if (!p) return; // 判空后再用
}

new 与 delete

new 分配堆内存并返回指针,delete 释放;new[] 必须配 delete[]。手动管理易泄漏,优先 RAII 容器或智能指针。

1
2
3
4
int* p = new int(5); // 动态分配单个
delete p; // 释放
int* arr = new int[10]; // 动态数组
delete[] arr; // 必须配对 []

unique_ptr 独占指针

unique_ptr 独占所有权,离开作用域自动 delete。make_unique 异常安全;转移所有权用 std::move,复制被禁止。

1
2
3
4
5
#include <memory>
std::unique_ptr<int> p = std::make_unique<int>(42);
*p = 43; // 像裸指针一样用
// p 离开作用域自动释放,无需 delete
std::unique_ptr<int> q = std::move(p); // 转移所有权

shared_ptr 共享指针

shared_ptr 引用计数共享所有权,计数归零才释放。避免循环引用导致泄漏 —— 用 weak_ptr 打破环。

1
2
3
4
std::shared_ptr<int> a = std::make_shared<int>(10);
std::shared_ptr<int> b = a; // 引用计数 +1
// 最后一个引用离开作用域时自动释放
long use = a.use_count(); // 2

weak_ptr 弱引用

weak_ptr 不拥有对象、不影响计数,lock() 临时提升为 shared_ptr(对象已释放则返回空)。专用于打破循环引用。

1
2
3
4
5
std::shared_ptr<int> sp = std::make_shared<int>(1);
std::weak_ptr<int> wp = sp; // 不增加计数
if (auto lk = wp.lock()) { // 提升为 shared_ptr
// 使用 lk;sp 被释放时 lock() 返回空
}

引用 vs 指针

引用语法更直接且保证非空,作参数优先用引用;需要「可能无对象」或「要重新指向别处」时用指针。

1
2
3
4
5
void inc(int& v) { ++v; } // 引用:非空、直接
void inc2(int* v) { if (v) ++*v; } // 指针:可空
int x = 1;
inc(x); // x 变 2
inc2(&x); // x 变 3

const 指针

读法从右往左:int* const 指针本身不可变、const int* 所指不可变。混用是最常见的声明混淆点。

1
2
3
4
int x = 1, y = 2;
int* const p = &x; // 指针 const,不能改指向
const int* q = &x; // 指向 const,不能改值
const int* const r = &x; // 两者都 const

void* 指针

void* 是「指向未知类型内存」的指针,必须显式转换回具体类型才能解引用。C 风格 API 用它传任意数据,C++ 里尽量避免。

1
2
3
void* raw = malloc(64); // 不透明内存
int* p = static_cast<int*>(raw); // 需显式转换
free(raw);

5.流程控制

if/for/while/switch 与 C++17 起 if/switch 的初始化语句。

if / else

if/else 按条件选择执行路径。C++17 起 if 可带初始化语句(见 if-init)。

1
2
3
4
5
6
7
if (n > 0) {
// 正数分支
} else if (n == 0) {
// 零分支
} else {
// 负数分支
}

for 循环

经典 for 由初始化、条件、步进三段组成。计数循环优先用 ++i,语义上减少一次临时拷贝。

1
2
3
4
for (int i = 0; i < 5; ++i) {
// 0..4
}
for (int i = 4; i >= 0; --i) { } // 递减

范围 for

范围 for(C++11)遍历容器元素,避免手写下标与边界错误。需要改元素用 auto&,只读用 const auto&。

1
2
3
4
std::vector<int> v{1, 2, 3};
for (const auto& x : v) { } // 只读
for (auto& x : v) { ++x; } // 修改元素
for (const auto& [k, val] : m) { } // 解包 map

while / do-while

while 先判断条件再进入循环体;do-while 至少执行一次。适合循环次数未知、依赖运行期条件的场景。

1
2
3
int i = 0;
while (i < 3) { ++i; } // 先判断后执行
do { ++i; } while (i < 3); // 先执行后判断,至少一次

switch / case

switch 按整型/枚举值分发,比 if 链更清晰。每个 case 必须 break(或 return),否则会贯穿落到下一分支。

1
2
3
4
5
switch (cmd) {
case 1: run(); break;
case 2: run2(); break;
default: help(); break; // 兜底
}

break 与 continue

continue 跳过本轮剩余代码进入下一轮;break 立刻退出整个循环。双层循环里可用标签跳外层。

1
2
3
4
5
for (int i = 0; i < 10; ++i) {
if (i % 2) continue; // 跳过奇数
if (i > 6) break; // 终止循环
// 只处理 0,2,4,6
}

if 初始化语句

C++17 起 if/switch 可声明初始化变量,作用域限制在分支内,避免变量泄漏到外层,是 find+check 的惯用写法。

1
2
3
4
5
6
if (auto it = m.find("key"); it != m.end()) {
// it 作用域限制在 if 内
}
switch (int v = parse(); v) {
case 0: break;
}

三元运算符

三元运算符一行表达 if/else 赋值。两个分支类型须兼容;逻辑复杂时写 if 更可读。

1
2
int max = (a > b) ? a : b; // 条件 ? 真值 : 假值
std::string s = ok ? "yes" : "no";

goto 标签

goto 直接跳到标签行,破坏结构化控制流,几乎总能用 flag 或 return 替代。唯一合理场景是跳出多层嵌套循环。

1
2
3
4
5
6
7
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (bad(i, j)) goto done; // 跳出双层
}
}
done:
// 仅用于打破多层嵌套,勿滥用

6.函数与 Lambda

声明与定义、重载、传参、lambda 与函数模板、变参模板与递归。

声明与定义

声明告诉编译器签名,定义提供实现。声明可放头文件多次包含不报错,定义每个程序只能有一份。

1
2
3
4
5
6
// 声明(原型):参数类型即接口
int add(int a, int b);
// 定义:实现
int add(int a, int b) {
return a + b;
}

函数重载

函数重载让同名函数按实参类型分派,编译器选最匹配版本。不可仅靠返回类型区分重载。

1
2
3
4
void print(int v) { }
void print(const std::string& s) { }
print(1); // 选 int 版
print("hi"); // 选 string 版

默认参数

默认参数让调用方省略尾部实参。默认值只能从最右侧开始连续提供;声明与定义只能一处给默认值。

1
2
3
void greet(const std::string& name, int times = 1) { }
greet("hi"); // times=1
greet("hi", 3); // times=3

传参方式

按值传参拷贝整个对象;按 const& 传避免拷贝且不可改;按 & 可改原值;&&(右值引用)接收临时对象以便转移资源。

1
2
3
4
void read(const std::string& s); // 只读:const& 避免拷贝
void write(std::string& s); // 修改原值
void copy(std::string s); // 需要副本
void own(std::string&& s); // 转移所有权

返回值

现代 C++ 返回临时值靠返回值优化(RVO/NRVO)零拷贝。绝不要返回局部对象的地址或引用(悬垂)。

1
2
3
4
5
std::string make() { return "abc"; } // NRVO 消除拷贝
auto [a, b] = std::pair<int, int>{1, 2}; // 返回 pair 解包
int& at(std::vector<int>& v, size_t i) {
return v[i]; // 返回引用
}

Lambda 表达式

lambda 是匿名函数对象:[] 捕获列表 + 参数 + 函数体。配合 <algorithm> 是写回调、比较器的主流方式。

1
2
3
4
auto add = [](int a, int b) { return a + b; };
int r = add(1, 2);
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; }); // 降序比较器

Lambda 捕获

[=] 按值捕获全部、[&] 按引用捕获全部,[x] 捕获指定变量。捕获引用时要保证原变量在 lambda 存活期间有效。

1
2
3
4
5
int base = 10;
auto f = [base](int x) { return base + x; }; // 按值
auto g = [&base](int x) { return base + x; }; // 按引用
auto h = [=]() {}; // 全按值
auto k = [&]() {}; // 全按引用

函数模板

函数模板按调用实参推导类型生成实例,一个实现服务所有类型。类型必须支持用到的运算符。

1
2
3
4
template <typename T>
T max_of(T a, T b) { return (a > b) ? a : b; }
int m = max_of(3, 5); // 推导为 int
double d = max_of(2.5, 1.5); // 推导为 double

变参模板

参数包 ...Args 接收任意数量实参,折叠表达式 (args + ...) 用二元运算符展开,是 printf 式自定义函数的新式替代。

1
2
3
4
5
template <typename... Args>
int sum(Args... args) {
return (args + ...); // C++17 折叠表达式
}
int total = sum(1, 2, 3, 4); // 10

递归

递归是函数调用自身,基准条件结束递归。深递归消耗调用栈,注意栈溢出与重复计算的优化。

1
2
3
4
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}

7.字符串

std::string 基础、拼接、查找替换、格式化、字符串流与数值互转。

std::string 基础

std::string 是可变、可增长的字符容器,内存自动管理。[] 下标越界未定义,用 at() 会抛异常。

1
2
3
4
5
6
#include <string>
std::string s = "hello";
s += " world"; // 拼接
size_t len = s.size(); // 长度
char c = s[0]; // 下标访问
bool empty = s.empty();

字符串字面量

"..." 字面量默认是 const char*;加 s 后缀转 std::string;string_view 是只读非拥有视图,作参数避免拷贝。

1
2
3
4
auto a = "plain"; // const char*
auto b = "u8"s; // std::string(using std::string_literals)
std::string_view v = "view"; // 只读视图
std::string_view::size_type n = v.size();

拼接与预分配

大量拼接时用 reserve 预分配减少内存重分配。多个 + 会产生临时对象,热点代码注意复用缓冲区。

1
2
3
4
5
6
std::string s = "a";
s.append("b").append("c"); // 链式追加
s.push_back('d');
s += 'e';
std::string out;
out.reserve(a.size() + b.size()); // 预分配

子串与查找

substr(pos, len) 取子串;find/rfind 找首个匹配位置,找不到返回 std::string::npos。

1
2
3
4
5
6
std::string s = "hello world";
s.substr(6, 5); // "world"
s.find("world"); // 6
s.find('o'); // 第一个 o
s.rfind('o'); // 从右找
if (s.find("x") == std::string::npos) { }

替换

成员 replace(pos, len, str) 按位置替换;<algorithm> 的 std::replace 按值批量替换所有匹配字符。

1
2
3
std::string s = "a-b-c";
s.replace(1, 1, "+"); // 位置 1 起替换 1 个字符
std::replace(s.begin(), s.end(), '-', '_'); // 批量替换

格式化输出

std::format(C++20)按占位符格式化,编译期检查参数个数,替代 sprintf 与 cout 拼接。:04d 补零、:.2f 保留两位小数。

1
2
3
4
#include <format> // C++20
std::string s = std::format("{}-{:04d}", 42, 7);
double d = std::format("{:.2f}", 3.14159);
// s = "42-0007",d = "3.14"

字符串流

stringstream 把内存字符串当流用:ostringstream 拼接、istringstream 按 >> 解析,格式化与反解析的灵活工具。

1
2
3
4
5
6
#include <sstream>
std::ostringstream oss;
oss << "x=" << 42 << " y=" << 3.5;
std::string s = oss.str();
std::istringstream iss("10 20");
int a, b; iss >> a >> b;

字符串与数值互转

stoi/stod/stoll 把字符串解析为数值,std::to_string 反向转换。解析失败或溢出会抛 std::invalid_argument/out_of_range。

1
2
3
4
int i = std::stoi("42"); // 转 int
int j = std::stoi("ff", nullptr, 16); // 十六进制
double d = std::stod("3.14");
std::string s = std::to_string(42); // "42"

原始字符串

R"(...)" 原始字符串内反斜杠不做转义,写正则、路径、多行文本免去转义噩梦。内容含 )" 时用 R"tag(...)tag"。

1
2
3
std::string re = R"(\w+@\w+\.\w+)"; // 免转义正则
std::string path = R"(C:\tmp\file)"; // 反斜杠原样保留
std::string tag = R"tag(<b>bold</b>)tag"; // 自定义定界

遍历字符

范围 for 逐字符遍历最简洁;需要修改用 char&。begin/end 迭代器方式兼容旧代码与算法库。

1
2
3
for (char c : s) { } // 逐字符(值)
for (char& c : s) { c = 'x'; } // 逐字符(可改)
for (auto it = s.begin(); it != s.end(); ++it) { }

8.容器与算法

vector/array/map/set/优先队列与排序、查找、ranges 算法。

vector 动态数组

vector 是动态数组,尾部增删 O(1)、随机访问 O(1)。频繁 push_back 用 reserve 预留容量;不要头部插入(O(n))。

1
2
3
4
5
6
std::vector<int> v;
v.push_back(1); // 尾部追加
v.emplace_back(2); // 就地构造
v.pop_back(); // 删除尾部
int x = v[0]; // 越界未定义;at() 抛异常
v.reserve(100); // 预分配

std::array 定长数组

std::array 是栈上定长数组的现代封装:有 size()/begin()/end(),可参与算法库。定长用 array,变长用 vector。

1
2
3
std::array<int, 4> a{1, 2, 3, 4}; // 栈上定长
int n = a.size(); // 编译期已知
std::sort(a.begin(), a.end());

map 有序字典

std::map 是有序键值对(红黑树),按键排序、查找 O(log n)。不排序时用 unordered_map(平均 O(1))。

1
2
3
4
5
std::map<std::string, int> m;
m["a"] = 1; // 键存在则覆盖
m.insert_or_assign("b", 2); // C++17
auto it = m.find("a");
if (it != m.end()) { } // 查存在性

unordered_map 哈希表

unordered_map 哈希表,平均 O(1) 查找、无序。注意 operator[] 对不存在键会插入零值,只查询用 find 或 at()。

1
2
3
4
5
#include <unordered_map>
std::unordered_map<int, int> m;
m[1] = 10; // 哈希桶查找
int v = m[1]; // 键不存在会插入默认值
if (m.find(1) != m.end()) { }

set 集合

set 存有序去重集合,insert/erase/find 均 O(log n);unordered_set 是哈希版本(无序、O(1))。contains()(C++20)判存在。

1
2
3
4
5
std::set<int> s{3, 1, 2};
s.insert(4); // 自动去重 + 排序
s.erase(1);
if (s.contains(2)) { } // C++20 判存在
std::unordered_set<int> us; // 哈希版本

deque 与 list

deque 双端队列头尾 O(1);list 链表中间插入 O(1) 但随机访问 O(n)。容器选择看访问模式。

1
2
3
4
5
6
std::deque<int> dq; // 两端增删 O(1)
dq.push_front(1);
dq.push_back(2);
std::list<int> lst; // 双向链表
lst.push_back(3);
lst.insert(lst.begin(), 0); // 中间插入 O(1)

优先队列(堆)

priority_queue 是堆:push O(log n)、top 取最值 O(1)。默认最大堆;最小堆需指定 greater 比较器。

1
2
3
4
5
6
#include <queue>
std::priority_queue<int> pq; // 默认最大堆
pq.push(3); pq.push(1); pq.push(2);
int top = pq.top(); // 3
pq.pop();
// 最小堆:priority_queue<int, vector<int>, greater<int>>

排序

std::sort 随机访问容器原地排序 O(n log n)。自定义比较器返回「a 是否排在 b 前」。list 用成员 sort()。

1
2
3
4
std::sort(v.begin(), v.end()); // 升序
std::sort(v.begin(), v.end(), std::greater<int>()); // 降序
std::sort(v.begin(), v.end(),
[](const Item& a, const Item& b) { return a.price < b.price; });

查找

无序集合用 std::find 线性查找;已排序容器用 binary_search/lower_bound 二分 O(log n)。find 返回迭代器,用 != end() 判断命中。

1
2
3
4
5
auto it = std::find(v.begin(), v.end(), 42);
if (it != v.end()) { }
std::sort(v.begin(), v.end()); // 二分前先排序
auto lo = std::lower_bound(v.begin(), v.end(), 50);
bool ok = std::binary_search(v.begin(), v.end(), 42);

ranges 管道视图

C++20 ranges 用 | 管道组合惰性视图:filter 过滤、transform 映射,不拷贝、不分配,表达式式处理序列。

1
2
3
4
5
#include <ranges>
std::vector<int> v{1, 2, 3, 4, 5};
auto even = v | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
for (int x : even) { } // 4, 16

9.动态内存与所有权

RAII、移动语义、智能指针的取舍与异常安全 —— C++ 资源管理的核心。

RAII 资源获取即初始化

RAII 把资源的获取放在构造函数、释放放在析构函数,对象销毁时自动释放,异常安全、无需显式清理。

1
2
3
4
5
6
7
8
// RAII:资源生命周期绑定对象生命周期
class File {
FILE* f_;
public:
File(const char* path) { f_ = fopen(path, "r"); }
~File() { if (f_) fclose(f_); } // 析构自动释放
};
{ File f("a.txt"); } // 离开作用域自动关闭

make_unique

make_unique 构造 unique_ptr,new+构造一步完成,即使构造抛异常也不泄漏裸指针。数组版本 make_unique<T[]>(n)。

1
2
3
auto p = std::make_unique<Widget>(args...); // 异常安全
auto q = std::make_unique<int[]>(10); // 动态数组
// 等价 new Widget(args...) 但更安全

make_shared

make_shared 一次分配对象与控制块,比 new 再包 shared_ptr 少一次分配。C++20 起控制块不浪费对象大小对齐。

1
2
3
std::shared_ptr<Big> sp = std::make_shared<Big>();
// 一次分配同时容纳对象与控制块
std::weak_ptr<Big> wp = sp; // 配合弱引用

移动语义

std::move 把左值转成右值引用,触发移动而非拷贝 —— 转移内部指针、源对象被置空,避免深拷贝大数组。

1
2
3
std::vector<int> big(1000000);
auto moved = std::move(big); // 转移所有权,big 变空
// std::move 不搬数据,只是把 big 标记为「可被窃取」

移动构造

移动构造窃取源对象资源并把源置空,比拷贝快。声明为 noexcept 便于 vector 扩容时用移动而非拷贝。

1
2
3
4
5
struct Buffer {
int* data;
Buffer(Buffer&& other) noexcept
: data(other.data) { other.data = nullptr; }
};

返回值优化

编译器省略多余的拷贝/移动构造,返回值直接构造到目标。C++17 起保证按值返回的临时对象零拷贝。

1
2
3
4
5
std::vector<int> make() {
std::vector<int> v(1000);
return v; // NRVO:直接构造到目标
}
auto v = make(); // 零拷贝

打破循环引用

父子树里父持 shared_ptr、子回指父用 weak_ptr,否则互相持有永不释放。lock() 临时提升验证对象存活。

1
2
3
4
5
6
7
8
9
10
struct Node;
struct Parent {
std::shared_ptr<Node> child;
};
struct Node {
std::weak_ptr<Parent> parent; // 回指用 weak
void refresh() {
if (auto p = parent.lock()) { p->update(); }
}
};

noexcept 承诺

noexcept 声明函数不抛异常,是 vector 扩容选用移动构造的依据。承诺后实际抛出会 std::terminate 终止程序,不确定时别写。

1
2
3
4
5
void parse() noexcept { } // 承诺不抛
std::vector<int> v(1000);
void run() noexcept {
v.push_back(1); // 危险:push_back 可能抛 bad_alloc
}

手动管理的泄漏

手动 new/delete 遇到中途异常或提前 return 就会泄漏。RAII(智能指针/容器)把清理交给析构,异常安全。

1
2
3
4
5
6
7
// BAD:中间 return 或异常就泄漏
int* p = new int(5);
use(p);
delete p;
// GOOD:RAII 自动清理
auto sp = std::make_unique<int>(5);
use(sp.get());

10.面向对象

类、访问控制、构造与析构、继承多态、纯虚接口、友元与运算符重载。

类定义

class 把数据与操作封装在一起。成员默认私有(与 struct 相反),通过公有方法访问私有数据。

1
2
3
4
5
6
7
8
9
class Account {
public:
void deposit(double n) { balance_ += n; }
double balance() const { return balance_; }
private:
double balance_ = 0;
};
Account a;
a.deposit(100);

访问限定符

public 公开、protected 派生可见、private 仅自身。默认 class 私有、struct 公有。

1
2
3
4
5
6
7
8
class Widget {
public: // 任何人可访问
int visible = 0;
protected: // 自身与派生类
int semi = 0;
private: // 仅自身
int hidden = 0;
};

构造函数

构造函数与类同名,用初始化列表 : x(x_) 初始化成员(比函数体赋值更高效)。无参数版本称默认构造。

1
2
3
4
5
6
7
struct Vec2 {
double x, y;
Vec2() : x(0), y(0) {} // 默认构造
Vec2(double x_, double y_) : x(x_), y(y_) {} // 参数构造
};
Vec2 a; // 默认
Vec2 b{1.0, 2.0}; // 参数

委托构造

委托构造让一个构造函数调用同类另一个构造,避免重复初始化逻辑。被委托者必须已声明。

1
2
3
4
5
6
class Point {
int x_, y_;
public:
Point() : Point(0, 0) {} // 委托给下面的
Point(int x, int y) : x_(x), y_(y) {} // 目标构造
};

析构函数

析构函数在对象销毁时执行,是 RAII 释放资源的地方。基类析构必须 virtual,否则 delete 派生类时只析构基类部分。

1
2
3
4
5
6
class ScopedLock {
std::mutex& m_;
public:
explicit ScopedLock(std::mutex& m) : m_(m) { m_.lock(); }
~ScopedLock() { m_.unlock(); } // 自动解锁
};

继承

继承让派生类复用基类接口与实现。通过基类指针/引用调用 virtual 函数发生运行时多态分派。

1
2
3
4
5
6
struct Animal { virtual void speak() const { } };
struct Dog : Animal {
void speak() const override { /* bark */ }
};
Animal* a = new Dog();
a->speak(); // 多态:调用 Dog 版本

虚函数与抽象类

virtual 声明可重写函数,纯虚(=0)使类成为抽象类不可实例化。含 virtual 的类析构也应 virtual。

1
2
3
4
5
6
7
8
struct Shape {
virtual double area() const = 0; // 纯虚 → 抽象类
virtual ~Shape() = default; // 基类析构 virtual
};
struct Circle : Shape {
double r_;
double area() const override { return 3.14 * r_ * r_; }
};

override 与 final

override 声明「我确实在重写基类虚函数」,签名不匹配会编译报错而不是静默新建函数;final 禁止进一步重写。

1
2
3
4
5
struct Base { virtual void f(int) { } };
struct Derived : Base {
void f(int) override; // 明确声明重写
void g() final; // 禁止再被派生重写
};

纯虚接口

纯虚函数 = 0 定义接口不提供实现,含纯虚的类是抽象类,派生类必须实现才能实例化。类似 Java 的 interface。

1
2
3
4
5
6
7
8
struct Logger {
virtual void log(const std::string&) = 0;
virtual ~Logger() = default;
};
struct ConsoleLogger : Logger {
void log(const std::string& msg) override { }
};
std::unique_ptr<Logger> l = std::make_unique<ConsoleLogger>();

友元

friend 允许指定类或函数访问私有成员,打破封装。应谨慎用于紧密耦合场景(运算符重载、内部迭代器)。

1
2
3
4
5
class Secret {
int code_ = 42;
friend class Reveal; // Reveal 可访问私有
friend int read(const Secret&);
};

运算符重载

运算符重载让自定义类型支持 + - << 等语法。保持直觉语义:+ 不修改操作数、<< 是输出。滥用降低可读性。

1
2
3
4
5
6
struct Money {
double v;
Money operator+(const Money& o) const { return {v + o.v}; }
};
Money a{1.5}, b{2.0};
Money c = a + b; // 调用 operator+

11.错误处理

异常捕获、标准异常层级、自定义异常、noexcept 与 optional/expected 的无异常路径。

try / catch

try 块内抛出的异常被 catch 捕获。优先捕获 std::exception& 基类,必要时按具体类型分 catch;不捕获则沿调用栈传播。

1
2
3
4
5
try {
risky();
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << '\n';
}

按引用捕获

按 const& 捕获保留多态类型且零拷贝;按值捕获会切片(派生异常被截断成基类)。catch(...) 捕获一切但拿不到信息。

1
2
3
4
5
// GOOD:按 const& 捕获,避免切片
catch (const std::runtime_error& e) { }
// BAD:按值捕获会切片
catch (std::runtime_error e) { }
// catch (...) 捕获一切,但无类型信息

标准异常层级

标准异常继承自 std::exception。特定类型分支放前面、std::exception& 兜底放最后,按最具体优先匹配。

1
2
3
4
catch (const std::bad_alloc& e) { } // 分配失败
catch (const std::out_of_range& e) { } // 越界
catch (const std::invalid_argument& e) { } // 非法参数
catch (const std::exception& e) { } // 兜底

自定义异常

自定义异常继承 std::runtime_error(或其兄弟),用 using 继承构造函数,what() 已带消息,捕获时按基类捕获即可。

1
2
3
4
5
class MyError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
throw MyError("config missing");

noexcept 与异常

noexcept 承诺不抛异常,vector 扩容、move 时依赖它。实际抛出会 std::terminate 直接终止程序,不确定时别写。

1
2
3
4
5
void parse() noexcept { } // 承诺不抛
std::vector<int> v(1000); // vector 操作可能抛
void run() noexcept {
v.push_back(1); // 危险:push_back 可能抛
}

optional 表达失败

对「预期可能失败」的操作返回 std::optional 比抛异常更轻量,调用方显式处理无值分支。异常留给意外错误。

1
2
3
4
5
std::optional<double> sqrt_opt(double x) {
if (x < 0) return std::nullopt;
return std::sqrt(x);
}
if (auto r = sqrt_opt(-1.0)) { } else { /* 无值分支 */ }

expected 携带错误

std::expected(C++23)同时携带值或错误描述,比 optional 多带错误信息、比异常更可预测,适合解析校验类代码。

1
2
3
4
5
6
#include <expected> // C++23
std::expected<int, std::string> parse(const std::string& s) {
try { return std::stoi(s); }
catch (...) { return std::unexpected("bad number"); }
}
if (auto v = parse("42")) { int n = *v; }

异常安全

异常安全靠 RAII:把清理放进局部对象的析构,无论正常返回还是抛异常都执行,避免资源泄漏或状态不一致。

1
2
3
4
5
6
7
struct Guard {
~Guard() { /* 回滚或清理 */ }
};
void op() {
Guard g; // 任何退出路径都执行析构
do_something_that_may_throw();
}

errno 与 C 交互

errno 是 C 时代的全局错误码,被下一个失败调用覆盖且线程不安全。C++ 优先用异常,errno 仅与 C 函数交互时立即读取。

1
2
3
if (fopen("a.txt", "r") == nullptr) {
std::cerr << "errno=" << errno << '\n'; // 立即读
}

12.文件与流 I/O

读写文件、逐行读取、格式化输出、filesystem 路径与二进制文件。

写入文件

ofstream 打开文件写入,<< 格式化输出。析构时自动关闭;显式 close() 能尽早捕获写入失败。默认覆盖已存在文件。

1
2
3
4
#include <fstream>
std::ofstream out("out.txt");
out << "hello " << 42 << '\n'; // 格式化写入
out.close(); // 显式关闭

读取文件

ifstream 打开文件读取,getline 逐行读入。循环条件是 getline 的返回值:读到 EOF 或失败时结束。

1
2
3
4
5
std::ifstream in("data.txt");
std::string line;
while (std::getline(in, line)) { // 逐行读
// 处理 line
}

读行 vs 读词

getline 读整行、>> 读单个词并跳过空白。混用时 >> 后残留的换行需 ignore() 清掉,否则 getline 读到空串。

1
2
3
4
5
std::string line;
std::getline(std::cin, line); // 直到换行
int n;
std::cin >> n; // 跳过空白读词
std::cin.ignore(); // 丢弃残留换行

格式化控制

<iomanip> 的 setw/setprecision/fixed 控制输出格式,列宽对齐与小数位控制在打印表格时常用。

1
2
3
4
#include <iomanip>
std::cout << std::setw(8) << std::left << "name";
std::cout << std::fixed << std::setprecision(2) << 3.14159;
// 3.14

内存流落盘

ostringstream 先在内存拼好再一次性写入文件,避免多次磁盘 IO;解析输入先用 istringstream 校验。

1
2
3
std::ostringstream oss;
oss << "id=" << 7;
std::ofstream("log.txt") << oss.str();

filesystem 目录

<filesystem>(C++17)处理路径与目录:exists/create_directories/遍历。比字符串拼路径更健壮,分隔符自动适配。

1
2
3
4
5
#include <filesystem>
namespace fs = std::filesystem;
fs::exists("a.txt");
fs::create_directories("out/sub"); // 递归建目录
for (auto& e : fs::directory_iterator(".")) { }

路径分解

fs::path 处理路径分解与拼接,跨平台自动用正确分隔符。filename/extension/parent_path 取路径组成部分。

1
2
3
4
5
fs::path p = "dir/sub/file.txt";
p.filename(); // "file.txt"
p.extension(); // ".txt"
p.parent_path(); // "dir/sub"
p.replace_extension(".md");

二进制文件

二进制模式 std::ios::binary 不转换换行符,write/read 按字节块读写。结构体写盘注意内存对齐与端序兼容。

1
2
3
4
5
std::ofstream out("data.bin", std::ios::binary);
int v = 12345;
out.write(reinterpret_cast<const char*>(&v), sizeof(v));
std::ifstream in("data.bin", std::ios::binary);
in.read(reinterpret_cast<char*>(&v), sizeof(v));

标准输入校验

cin >> 失败会把流置为 fail,之后所有读取失效。clear() 清状态、ignore() 丢脏数据后再继续。读取后总是校验 cin 状态。

1
2
3
4
5
6
int n;
std::cin >> n;
if (!std::cin) {
std::cin.clear(); // 清除 fail 状态
std::cin.ignore(); // 丢弃脏数据
}

13.常见坑

十组 C++ 经典反例(BAD)与正解(GOOD)—— 红绿配色,逐条对照。

有符号无符号混算

有符号与无符号混合比较时,有符号自动转无符号,-1 变成巨大无符号数。比较前显式转换或统一符号。

1
2
3
4
5
// BAD:-1 被转成巨大无符号数
int x = -1;
if (x < 0u) { /* 恒假 */ }
// GOOD:显式转有符号再比
if (static_cast<int>(0u) > x) { }

返回悬垂引用

返回局部变量的引用或指针是悬垂引用,函数返回后内存失效,使用是未定义行为。返回值或提升对象生命周期。

1
2
3
4
// BAD:返回局部对象引用
int& bad() { int x = 1; return x; }
// GOOD:返回值(RVO 零拷贝)
int good() { int x = 1; return x; }

基类析构非 virtual

通过基类指针 delete 派生对象时,基类析构必须 virtual,否则派生部分的析构不执行、资源泄漏。

1
2
3
4
5
// BAD:派生析构不执行
struct Base { ~Base() { } };
// GOOD:virtual 保证完整析构
struct Base2 { virtual ~Base2() = default; };
delete base_ptr; // 只有 Base2 版本安全

整数除法取整

两个整数相除结果仍是整数(截断),赋值给 double 前就丢了小数。至少把一边转浮点再除。

1
2
3
4
5
6
// BAD:2/3 结果是 0
int a = 2, b = 3;
double r = a / b; // 0.0
// GOOD:先把一边转浮点
int n2 = static_cast<int>(a);
double r2 = static_cast<double>(a) / b; // 0.666...

循环拼接字符串

s = s + x 每次拷贝整个已有串生成新临时对象,O(n²);reserve + += 就地追加为 O(n)。大数据量拼接性能差一个量级。

1
2
3
4
5
6
7
// BAD:循环里反复拷贝整串
std::string s;
for (int i = 0; i < 10000; ++i) s = s + "x";
// GOOD:预分配 + 就地追加
std::string t;
t.reserve(10000);
for (int i = 0; i < 10000; ++i) t += "x";

using namespace std

头文件里 using namespace std 让每个包含它的文件暴露全部 std 符号,产生歧义。精确 using 或直接 std:: 限定。

1
2
3
4
5
// BAD:污染所有包含者
using namespace std;
// GOOD:只引入需要的,且放实现文件
using std::string;
using std::cout;

i++ 还是 ++i

i++ 返回旧值(需要拷贝/临时),++i 直接递增。对 int 无差别,但自定义类型/迭代器上 ++i 少一次拷贝。

1
2
3
4
// BAD:不需要旧值时白拷贝
for (int i = 0; i < n; i++) { }
// GOOD:迭代器场景差异明显
for (int i = 0; i < n; ++i) { }

const 正确性

只读参数用 const&,语义自文档且避免拷贝。const 正确性让编译器帮你拦下误改,也让调用方知道函数不改参数。

1
2
3
4
// BAD:只读参数忘写 const
void dump(std::string s);
// GOOD:const& 明确只读意图
void dump(const std::string& s);

宏代替函数

宏不参与类型检查、不遵守作用域、易产生副作用重复求值。能写 const/constexpr 函数或模板就优先。

1
2
3
4
// BAD:宏无类型检查
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// GOOD:constexpr 函数有类型检查
constexpr int max_of(int a, int b) { return a > b ? a : b; }

循环按值拷贝

范围 for 按值会拷贝每个元素;大对象用 const auto& 只读、auto& 修改。数据小且需要副本时才按值。

1
2
3
4
// BAD:每个元素拷贝一次
for (auto item : items) { use(item); }
// GOOD:只读引用
for (const auto& item : items) { use(item); }

14.线程与并发

std::thread、互斥锁、原子操作、条件变量与异步任务。

创建线程

std::thread 创建新线程执行可调用对象(函数/lambda)。线程对象销毁时必须已 join 或 detach,否则 std::terminate。

1
2
3
4
5
6
#include <thread>
std::thread t([] {
// 新线程执行体
std::cout << "hello from thread\n";
});
// 必须 join() 或 detach()

join 与 detach

join 阻塞当前线程等子线程结束;detach 让子线程独立运行,分离后无法再 join,且其访问的对象须保持存活。

1
2
3
4
5
std::thread t(worker);
t.join(); // 阻塞等待线程结束
// 或
// t.detach(); // 分离,后台运行
if (t.joinable()) t.join(); // 判后再 join

mutex 互斥锁

mutex 互斥锁保护共享数据,同一时刻只允许一个线程持有。手动 lock/unlock 易遗漏,优先 lock_guard/unique_lock。

1
2
3
4
5
6
#include <mutex>
std::mutex m;
int shared = 0;
m.lock();
++shared;
m.unlock(); // 异常时不执行会死锁

lock_guard 自动加锁

lock_guard 用 RAII 管理锁:构造加锁、析构解锁,任何退出路径(含异常)都保证解锁,是加锁的标准方式。

1
2
3
4
5
std::mutex m;
{
std::lock_guard<std::mutex> g(m); // 构造加锁
++shared; // 临界区
} // 析构解锁

原子操作

std::atomic 对基本类型提供原子操作,多线程下免锁修改。fetch_add/load/store 都是原子操作,比互斥锁开销小。

1
2
3
4
5
#include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1); // 原子递增
int cur = counter.load(); // 原子读
counter.store(42); // 原子写

条件变量

condition_variable 让线程等待条件:wait 释放锁并阻塞、notify 唤醒。wait 必须带谓词防虚假唤醒。

1
2
3
4
5
6
7
8
9
std::mutex m;
std::condition_variable cv;
bool ready = false;
// 生产者
{ std::lock_guard<std::mutex> g(m); ready = true; }
cv.notify_one();
// 消费者
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return ready; });

async / future

std::async 启动异步任务返回 future,get() 阻塞取结果。比手写 thread+共享变量简单;可指定 launch::async 强制新线程。

1
2
3
4
#include <future>
auto fut = std::async(std::launch::async,
[] { return compute(); });
int result = fut.get(); // 阻塞等待结果

thread_local

thread_local 变量每个线程一份独立副本,天然免锁缓存、计数、临时状态。线程结束销毁,适合线程私有数据。

1
2
3
4
5
thread_local int cache = 0; // 每线程独立副本
int get() {
if (!cache) cache = compute(); // 每线程算一次
return cache;
}

数据竞争

两个线程同时读写同一非原子变量是数据竞争(未定义行为,结果不可预测)。用 atomic 或 mutex 同步。

1
2
3
4
5
6
7
// BAD:并发读写非原子变量
int shared = 0;
// 线程 A: ++shared 线程 B: ++shared
// → 结果可能 1 而非 2
// GOOD:原子或互斥锁保护
std::atomic<int> safe{0};
safe.fetch_add(1);

并行算法

C++17 <execution> 给算法加执行策略:par 并行、unseq 向量化。大数据量自动利用多核,前提是元素无共享可变状态。

1
2
3
4
5
#include <execution>
std::vector<int> v(1000);
std::sort(std::execution::par, v.begin(), v.end());
std::transform(std::execution::par_unseq,
v.begin(), v.end(), v.begin(), [](int x) { return x * 2; });

15.网络(Socket)

POSIX socket 创建、监听、连接、收发与超时控制(跨平台网络建议用 Boost.Asio 或 libcurl)。

创建套接字

POSIX socket 是网络 I/O 底基:socket() 创建套接字,AF_INET IPv4、SOCK_STREAM TCP。返回文件描述符,-1 表示失败。

1
2
3
4
#include <sys/socket.h>
#include <netinet/in.h>
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { /* errno 给出原因 */ }

绑定与监听

bind 把套接字绑定到端口,listen 开始监听。htons 把主机字节序转网络字节序。sockaddr_in 是 IPv4 地址结构。

1
2
3
4
5
6
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
bind(fd, (sockaddr*)&addr, sizeof(addr));
listen(fd, 16); // 监听队列

accept 接受连接

accept 取出队列中的客户端连接返回新套接字,每个连接一个 fd。原监听 fd 继续 accept 下一个连接。

1
2
3
4
5
6
while (true) {
int client = accept(fd, nullptr, nullptr);
if (client < 0) continue;
handle(client); // 新连接
close(client);
}

客户端连接

客户端 socket()+connect() 连到服务器地址。inet_pton 把点分十进制 IP 转二进制地址。connect 失败返回 -1。

1
2
3
4
5
6
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
connect(fd, (sockaddr*)&addr, sizeof(addr));

发送与接收

send/recv 在 TCP 套接字上收发字节。recv 返回 0 说明对端关闭、-1 是错误。TCP 是流式协议,需自定报文边界。

1
2
3
4
5
std::string msg = "hello";
send(fd, msg.data(), msg.size(), 0);
char buf[1024];
int n = recv(fd, buf, sizeof(buf), 0);
// n == 0 对端关闭;n < 0 出错

域名解析

getaddrinfo 把主机名+服务解析成可 connect 的地址链表,自动处理 IPv4/IPv6。替代手写 inet_pton+端口的推荐方式。

1
2
3
4
5
#include <netdb.h>
struct addrinfo* res;
getaddrinfo("example.com", "80", nullptr, &res);
// res 链表逐项尝试 connect
freeaddrinfo(res);

超时控制

SO_RCVTIMEO 给 recv 设超时,超时返回 -1 且 errno=EWOULDBLOCK。阻塞式 recv 因此变得可控,避免永久挂死。

1
2
3
4
#include <sys/time.h>
struct timeval tv{3, 0}; // 3 秒
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// recv 超过 3 秒 → -1,errno = EWOULDBLOCK

极简 HTTP 请求

HTTP 请求是文本协议:请求行 + 头 + 空行。演示直接发 socket 报文;生产环境优先用 libcurl(自动处理重定向、TLS、压缩)。

1
2
3
4
5
6
std::string req =
"GET / HTTP/1.1\r\n"
"Host: example.com\r\n"
"Connection: close\r\n\r\n";
send(fd, req.data(), req.size(), 0);
// 读响应:状态行 + 头 + 按 Content-Length 读体

16.时间与日期

chrono 的 duration / time_point、两种时钟、格式化、睡眠与耗时测量。

duration 时长

duration 表示时间长度,类型携带单位:seconds/milliseconds/microseconds。字面量后缀 s/ms/us 直观,duration_cast 跨单位转换。

1
2
3
4
5
#include <chrono>
using namespace std::chrono_literals;
auto d = 3s + 500ms; // 3.5 秒
std::chrono::seconds s = 90s;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(s);

time_point 时刻

time_point 是时间轴上的某个时刻,由时钟+duration 偏移组成。时间点加减 duration 得到新时刻,两个时刻相减得 duration。

1
2
3
4
using namespace std::chrono;
auto now = system_clock::now(); // 当前时刻
auto past = now - 1h; // 一小时前
auto elapsed = now - past; // 时长

system_clock

system_clock 对应系统墙钟,可与 time_t 互转(to_time_t/from_time_t),用于展示日历时间、日志时间戳。受系统改时影响。

1
2
3
auto tp = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(tp);
// t 可用于 strftime / put_time 格式化

steady_clock

steady_clock 单调递增、不受系统时间调整影响,是计时基准(测耗时、超时判断)。展示日历时间才用 system_clock。

1
2
3
4
5
auto t0 = std::chrono::steady_clock::now();
// 测量耗时用 steady_clock
work();
auto t1 = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0);

格式化时间

put_time 按 strftime 格式把 time_t 格式化成本地时间字符串。%Y-%m-%d %H:%M:%S 最常用。localtime 返回静态缓冲区、非线程安全。

1
2
3
4
#include <iomanip>
auto tp = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(tp);
std::cout << std::put_time(std::localtime(&t), "%Y-%m-%d %H:%M:%S");

线程睡眠

sleep_for 睡指定时长,sleep_until 睡到指定时刻。阻塞当前线程,主线程睡眠会冻结 UI,只在后台/测试用。

1
2
3
4
5
#include <thread>
using namespace std::chrono_literals;
std::this_thread::sleep_for(500ms);
std::this_thread::sleep_until(
std::chrono::steady_clock::now() + 2s);

测量耗时

计时惯例:start 记起点、结束减起点得 duration、duration_cast 换算单位再 count() 取值。steady_clock 保证不受改时影响。

1
2
3
4
5
auto start = std::chrono::steady_clock::now();
heavy_work();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
std::cout << "elapsed " << ms << " ms\n";

C 风格时间互转

time_t 是秒级时间戳,gmtime 转 UTC 结构、localtime 转本地结构,strftime 格式化。注意这些函数用静态缓冲区、非线程安全。

1
2
3
4
5
std::time_t t = std::time(nullptr);
std::tm* utc = std::gmtime(&t);
std::tm* local = std::localtime(&t);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d", local);

时区

C++20 的 zoned_time 带时区输出时间,自动处理夏令时。旧标准库无时区,需依赖系统 localtime 或第三方库。

1
2
3
4
5
#include <chrono>
using namespace std::chrono;
auto tp = system_clock::now();
auto z = current_zone();
std::cout << zoned_time(z, tp) << '\n'; // 带时区输出

17.进程与信号

system、fork/exec/wait 子进程、环境变量、信号处理与管道取输出(POSIX 概念)。

system 执行命令

system() 调用 shell 执行字符串命令,简单但不安全(命令注入、shell 差异)。需要捕获输出或参数时用 fork+exec 或 popen。

1
2
3
#include <cstdlib>
int code = std::system("ls -l"); // 执行 shell 命令
// 非 0 通常表示失败,具体语义依赖平台

fork 复制进程

fork 复制当前进程为子进程,返回两次:父进程得子 pid、子进程得 0。-1 表示失败。fork 后子进程从 fork 处继续执行。

1
2
3
4
5
6
7
#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
// 子进程
} else if (pid > 0) {
// 父进程,pid 是子进程号
} else { /* fork 失败 */ }

exec 替换程序

exec 系列在当前进程内加载新程序替换自身。fork+exec 组合启动外部命令。exec 成功不返回、失败返回 -1,子进程要处理失败路径。

1
2
3
4
5
pid_t pid = fork();
if (pid == 0) {
execl("/bin/echo", "echo", "hi", nullptr);
_exit(1); // 只有 exec 失败才到这里
}

wait 等待子进程

waitpid 等待指定子进程结束并取退出状态。WIFEXITED 判断正常退出、WEXITSTATUS 取退出码。不 wait 的子进程会变僵尸进程。

1
2
3
4
5
pid_t pid = fork();
if (pid == 0) { /* 子进程工作 */ _exit(0); }
int status;
waitpid(pid, &status, 0);
bool ok = WIFEXITED(status) && WEXITSTATUS(status) == 0;

环境变量

getenv 读环境变量(不存在返回 nullptr)、setenv 设置、unsetenv 删除。环境变量是父进程传给子进程的简单配置通道。

1
2
3
4
#include <cstdlib>
const char* home = getenv("HOME"); // 无则 nullptr
setenv("APP_ENV", "prod", 1); // 1 = 覆盖已有
unsetenv("APP_ENV");

信号处理

signal 注册信号处理器,SIGINT(Ctrl+C)、SIGTERM(kill 默认)等。信号处理函数中只能做异步安全操作,别用堆分配/IO。

1
2
3
#include <csignal>
void handler(int sig) { /* 尽快处理,别做重活 */ }
std::signal(SIGINT, handler); // Ctrl+C 触发

sigaction 可靠信号

sigaction 比 signal 更可靠:可设信号屏蔽与标志。SA_RESTART 让被信号中断的阻塞调用自动恢复。生产代码优先 sigaction。

1
2
3
4
5
struct sigaction sa{};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGTERM, &sa, nullptr);

exit 与 _exit

exit 终止进程并运行 atexit 清理函数、刷新缓冲;_exit 直接终止不清理。fork 后的子进程应 _exit 避免重复清理父资源。

1
2
3
exit(0); // 执行 atexit 钩子后退出
_exit(0); // 立即退出,不清理
return 0; // main 返回 = exit(0)

popen 管道取输出

popen 执行命令并管道读其输出("r" 读 / "w" 写输入),比 system 能捕获输出。pclose 关闭并等待,返回值含退出状态。

1
2
3
4
FILE* p = popen("ls -l", "r");
char buf[256];
while (fgets(buf, sizeof(buf), p)) { /* 处理每行 */ }
int code = pclose(p);

18.正则表达式

std::regex 的匹配、搜索、替换、捕获组、标志与原始字符串写法。

基础匹配

std::regex 默认用 ECMAScript 语法。regex_match 要求整个字符串匹配、regex_search 找子串。R"()" 原始字符串免转义。

1
2
3
#include <regex>
std::regex re(R"(\d{3}-\d{4})"); // 匹配 123-4567
bool m = std::regex_match("123-4567", re); // true

整体匹配与分组

regex_search 找第一个匹配子串,smatch 保存整体(m[0])与捕获组(m[1]…)。regex_match 要求整串匹配、适合校验格式。

1
2
3
4
5
6
7
std::regex re(R"((\d{4})-(\d{2})-(\d{2}))");
std::smatch m;
std::string s = "date: 2026-08-02";
if (std::regex_search(s, m, re)) {
std::cout << m[0] << '\n'; // 2026-08-02
std::cout << m[1] << '\n'; // 2026 第一组
}

遍历所有匹配

循环 regex_search 从上一匹配的结尾继续,遍历所有匹配。m.suffix().first 是匹配后剩余部分起点。或直接用 regex_iterator。

1
2
3
4
5
6
7
8
std::regex re(R"(\d+)");
std::smatch m;
std::string s = "a1 b22 c333";
auto it = s.cbegin();
while (std::regex_search(it, s.cend(), m, re)) {
std::cout << m[0] << ' '; // 1 22 333
it = m.suffix().first;
}

替换

regex_replace 全局替换所有匹配。替换模板里 $& 是整段匹配、$1 引用捕获组。需要按位置替换时用 regex_iterator。

1
2
3
4
std::regex re(R"(\d+)");
std::string s = "v1.2.3";
std::string out = std::regex_replace(s, re, "[$&]");
// v[1].[2].[3]

迭代器批量提取

sregex_iterator 把「找所有匹配」封装成迭代器,遍历即得所有结果,比手写 while regex_search 简洁,适合批量提取。

1
2
3
4
5
6
std::regex re(R"(\d+)");
std::string s = "a1 b22 c333";
std::sregex_iterator it(s.begin(), s.end(), re);
for (; it != std::sregex_iterator{}; ++it) {
std::cout << it->str() << ' '; // 1 22 333
}

捕获组

捕获组用圆括号分组,smatch 按下标访问。(?:...) 是非捕获组只做逻辑分组不占编号。命名组 (?<name>...) 用 m["name"]。

1
2
3
4
5
6
std::regex re(R"((\w+)@(\w+)\.(\w+))");
std::smatch m;
if (std::regex_match("[email protected]", m, re)) {
std::cout << m[1] << ' ' << m[2] << '.' << m[3];
// a b.com
}

匹配标志

std::regex 构造器第二参数是标志:icase 忽略大小写、multiline 让 ^/$ 匹配行首尾、ECMAScript/extended 选语法方言。多个用 | 组合。

1
2
3
std::regex re("hello", std::regex::icase); // 忽略大小写
std::regex re2("^a.*b$", std::regex::multiline); // 按行 ^ $
std::regex re3("x+", std::regex::extended); // POSIX 语法

原始字符串写正则

R"(...)" 里反斜杠原样保留,写正则与路径不转义。带 )" 的串用 R"tag(...)tag" 自定义定界。始终优先原始字符串写正则。

1
2
3
4
// 原始字符串免转义:再也不用 \\d
std::regex re(R"(\w+@\w+\.\w+)");
// 传统写法容易数错反斜杠
std::regex re2("\\w+@\\w+\\.\\w+");

正则异常

非法正则表达式在构造时抛 std::regex_error,捕获它避免程序崩溃。高代价编译的正则可复用对象避免每次重编。

1
2
3
4
5
try {
std::regex r("([unclosed"); // 非法正则
} catch (const std::regex_error& e) {
std::cerr << e.what() << '\n';
}

19.构建与调试

编译标志、Makefile / CMake、格式化、sanitizer、调试器与性能分析。

常用编译标志

常用标志:-std 选标准、-O 优化级别、-g 调试信息、-Wall -Wextra 警告、-Werror 警告即错误。多文件先 -c 分编译再链接。

1
2
3
// $ g++ -std=c++20 -O2 -Wall -Wextra -g main.cpp -o app
// -pthread 链接线程库(用 std::thread 时必须)
// $ g++ -c a.cpp -o a.o 只编译不链接

Makefile

Makefile 声明目标与依赖,make 按依赖自动重编。$< 第一个依赖、$@ 目标、$^ 全部依赖。规则行必须 Tab 缩进。

1
2
3
4
5
6
7
8
9
# Makefile
CXX = g++
CXXFLAGS = -std=c++20 -Wall -Wextra
app: main.o math.o
\t$(CXX) $^ -o $@
main.o: main.cpp math.h
\t$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
\trm -f app *.o

CMake

CMake 声明式构建,跨平台生成 Makefile/VS 工程。target_link_libraries 链接库,find_package 找依赖。构建在独立 build/ 目录。

1
2
3
4
5
6
7
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(server LANGUAGES CXX)
add_executable(server main.cpp)
find_package(Threads REQUIRED)
target_link_libraries(server PRIVATE Threads::Threads)
# $ cmake -B build && cmake --build build -j

pkg-config 查库参数

pkg-config 查询库的编译/链接参数,$(pkg-config ...) 命令替换注入命令行,避免手写路径。链接第三方库的标准方式。

1
2
3
// $ pkg-config --cflags --libs openssl
// -I/usr/include/openssl -lssl -lcrypto
// $ g++ main.cpp $(pkg-config --cflags --libs openssl) -o app

clang-format 格式化

clang-format 自动格式化代码,.clang-format 文件统一风格。ColumnLimit 控制行宽、SortIncludes 排序头文件。提交前跑一遍保持一致。

1
2
3
4
5
6
# .clang-format
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
SortIncludes: true
# $ clang-format -i src/*.cpp

Sanitizer 检测

编译期加 sanitizer 在运行期捕获内存/并发错误:address 查越界泄漏、undefined 查未定义行为、thread 查数据竞争。测试必开。

1
2
3
4
// $ g++ -fsanitize=address,undefined -g main.cpp -o app
// ASan:越界 / 泄漏 / use-after-free
// $ g++ -fsanitize=thread -g main.cpp -o app
// TSan:数据竞争

gdb 调试器

gdb 是命令行调试器,-g 编译注入符号。break 断点、run 启动、bt 看栈、print 查值。UI 调试器(VSCode/CLion)底层也是它。

1
2
3
4
5
6
7
// $ g++ -g main.cpp -o app
// $ gdb ./app
// (gdb) break main
// (gdb) run
// (gdb) print x
// (gdb) bt
// (gdb) next / step

valgrind 内存检测

valgrind 检测运行期内存问题:未初始化读、越界、泄漏、双释放。--leak-check=full 输出每个泄漏的调用栈。速度慢,跑测试子集即可。

1
2
3
// $ valgrind --leak-check=full ./app
// ==12345== 20 bytes in 1 blocks are definitely lost
// $ valgrind --tool=helgrind ./app // 检测数据竞争

静态库打包

ar 把多个 .o 打包成静态库 lib*.a,-L 指定库目录、-l 指定库名(省略 lib 前缀与 .a 后缀)。静态库打进可执行文件、无运行时依赖。

1
2
3
// $ ar rcs libmath.a math1.o math2.o
// $ g++ main.cpp -L. -lmath -o app
// 运行无需 libmath.a 单独分发

性能分析

gprof/perf 剖析热点函数。g++ -pg 注入采样、运行后 gprof 输出每个函数的调用次数与耗时占比,定位优化热点。

1
2
3
// $ g++ -pg main.cpp -o app
// $ ./app && gprof app gmon.out
// 输出各函数调用次数与耗时占比

官方链接

直达官方文档与资源。

关于本速查

本页是 C++17/C++20 的自包含速查手册,覆盖语言核心、标准模板库(STL)与构建工具链在真实项目中约 80% 的常见用法。内容偏向现代惯用法:智能指针(unique_ptr / shared_ptr)、移动语义、auto 类型推导、范围 for、结构化绑定,以及 C++20 的 concepts 与 ranges。C++ 由 Bjarne Stroustrup 于 1985 年创建,兼具 C 的高性能与高层抽象能力,是系统编程、游戏引擎与高性能计算领域的基石语言。 19 个章节各自聚焦一个主题:基础语法、变量、类型与指针、控制流、函数、字符串、集合、内存管理、面向对象、错误处理、输入输出、常见误区、并发、网络、时间、进程、正则与构建工具。每个小节都配有「概念介绍 + 可直接复制的代码片段」,方便查阅与实验。 所有代码与文字均在浏览器本地渲染,无任何数据离开你的设备。权威参考见 cppreference 与 ISO C++ 标准草案。

版本 2.1.0