本工具使用的開源套件

本工具程式碼中捆綁了 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