Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
---

<!-- COVERAGE_START -->
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 433/433 docs translated
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 439/439 docs translated
<!-- COVERAGE_END -->

## 这是什么项目
Expand Down
42 changes: 42 additions & 0 deletions code/examples/vol3/01_container_selection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Standard: C++20
// 容器选择:演示「按操作选容器」——按位置存 vs 按键查,呼应选择决策树
#include <iostream>
#include <list>
#include <map>
#include <unordered_map>
#include <vector>

int main() {
std::cout << "== 按位置存:顺序容器,关心在哪插删 ==\n";
std::vector<int> v;
for (int i = 0; i < 5; ++i) {
v.push_back(i); // 尾部摊还 O(1)
}
std::list<int> lt;
for (int i = 0; i < 5; ++i) {
lt.push_front(i); // 头部 O(1)
}
std::cout << "vector 尾插 5 个(尾部摊还 O(1)),list 头插 5 个(头部 O(1))\n";
std::cout << "→ 频繁头尾进出用 deque,主要尾部增长用 vector(务必 reserve)\n";

std::cout << "\n== 按键查:关联容器,关心按什么查 ==\n";
constexpr int N = 100'000;
std::map<int, int> om;
std::unordered_map<int, int> um;
for (int i = 0; i < N; ++i) {
om[i] = i;
um[i] = i;
}
std::cout << "map.find(N/2) 命中: " << (om.find(N / 2) != om.end())
<< "(O(log n) 红黑树,可有序遍历)\n";
std::cout << "unordered_map.find(N/2) 命中: " << (um.find(N / 2) != um.end())
<< "(平均 O(1) 哈希,最快)\n";
std::cout << "→ 要有序遍历用 map,只要快查用 unordered(记得 reserve)\n";

std::cout << "\n== 决策三问(挑容器先问这三件事)==\n";
std::cout << "1) 大小编译期已知且不变?→ array\n";
std::cout << "2) 按键查找?→ 有序遍历用 map/set,否则 unordered(平均 O(1))\n";
std::cout << "3) 按位置存?→ 头尾用 deque,尾部用 vector,已知位置频繁增删用 list\n";
std::cout << "拿不准就 vector:连续、尾部摊还 O(1)、接口最全,覆盖面最广的安全牌\n";
return 0;
}
File renamed without changes.
53 changes: 53 additions & 0 deletions code/examples/vol3/05_deque_list_forward_list.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Standard: C++20
// deque / list / forward_list:vector 之外的三个选择——头插都 O(1),但内存布局与 splice 各异
#include <deque>
#include <forward_list>
#include <iostream>
#include <list>

int main() {
std::cout << "== 三者头插都 O(1),但存储布局不同 ==\n";
constexpr int N = 100'000;

std::deque<int> dq;
for (int i = 0; i < N; ++i) {
dq.push_front(i); // 分段连续,头尾均 O(1)
}

std::list<int> lt;
for (int i = 0; i < N; ++i) {
lt.push_front(i); // 双向链表节点
}

std::forward_list<int> fl;
for (int i = 0; i < N; ++i) {
fl.push_front(i); // 单向链表,最省内存(无 prev 指针)
}
std::cout << "各头插 " << N << " 个,复杂度都是 O(1)\n";

std::cout << "\n== sizeof:forward_list 最省,deque 有分段控制开销 ==\n";
std::cout << "sizeof(deque<int>) = " << sizeof(dq) << '\n';
std::cout << "sizeof(list<int>) = " << sizeof(lt) << '\n';
std::cout << "sizeof(forward_list<int>) = " << sizeof(fl) << '\n';

std::cout << "\n== list::splice:O(1) 把整串节点搬过来,零拷贝 ==\n";
std::list<int> a{1, 2, 3};
std::list<int> b{10, 20};
auto it = a.begin();
++it; // 指向元素 2
a.splice(it, b); // 把 b 整个接到 a 的 it 之前,搬节点不拷贝
std::cout << "splice 后 a = ";
for (auto x : a) {
std::cout << x << ' ';
}
std::cout << "\nb 现在 size = " << b.size() << "(节点被搬走,b 空了)\n";

std::cout << "\n== forward_list 没有 size():单向链表数元素要 O(n) ==\n";
int cnt = 0;
for (auto x : fl) {
(void)x;
++cnt;
}
std::cout << "forward_list 手动数 = " << cnt << '\n';
return 0;
}
43 changes: 43 additions & 0 deletions code/examples/vol3/06_map_set.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Standard: C++20
// map / set:底层红黑树按键有序、异构查找(transparent 比较器,string_view 直接查)、extract
// 节点搬家
#include <iostream>
#include <map>
#include <string>
#include <string_view>

int main() {
std::cout << "== map 底层红黑树:按键自动有序 ==\n";
std::map<int, std::string> m;
m[3] = "three";
m[1] = "one";
m[2] = "two";
std::cout << "插入 3,1,2 后遍历(自动有序):";
for (const auto& [k, v] : m) {
std::cout << k << ':' << v << ' ';
}
std::cout << '\n';

std::cout << "\n== 异构查找:用 string_view 查 string 的 map,免构造临时 string ==\n";
std::map<std::string, int, std::less<>> sm; // std::less<> = 透明比较器
sm["apple"] = 1;
sm["banana"] = 2;
std::string_view key = "banana";
auto it = sm.find(key); // string_view 直接查,不构造临时 string
if (it != sm.end()) {
std::cout << "find(string_view) 命中: " << it->second << '\n';
}
std::cout << "(用 std::less<> 而非 std::less<std::string>,比较器才支持异构 key)\n";

std::cout << "\n== extract:把节点从一棵 map 搬到另一棵,零拷贝 ==\n";
std::map<int, std::string> a{{1, "one"}, {2, "two"}};
std::map<int, std::string> b;
auto node = a.extract(1); // 抽出节点(连带 string,不拷贝)
b.insert(std::move(node)); // 接到 b
std::cout << "extract(1) 后 a.size = " << a.size() << ", b.size = " << b.size() << '\n';
std::cout << "(extract 搬节点不拷贝 string,适合改 key、换分配器、跨 map 转移)\n";

std::cout << "\n== 复杂度:查找/插入/删除均 O(log n) ==\n";
std::cout << "要有序遍历用 map/set;只要平均 O(1) 查找用 unordered 版\n";
return 0;
}
41 changes: 41 additions & 0 deletions code/examples/vol3/07_unordered_map_set.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Standard: C++20
// unordered_map:哈希桶、rehash 触发的质数桶序列、load_factor、reserve 预撑桶
#include <iostream>
#include <unordered_map>

int main() {
std::cout << "== rehash 触发质数桶序列(bucket_count 跳变)==\n";
std::unordered_map<int, int> m;
std::size_t last = m.bucket_count();
std::cout << "初始 bucket_count = " << last << '\n';
for (int i = 0; i < 200; ++i) {
m[i] = i;
if (m.bucket_count() != last) {
std::cout << "插入 " << i << " 后 rehash: " << last << " -> " << m.bucket_count()
<< "(load_factor=" << m.load_factor() << ")\n";
last = m.bucket_count();
}
}

std::cout << "\n== 桶分布:看元素怎么落桶(链地址法)==\n";
std::unordered_map<int, int> small;
for (int i = 0; i < 10; ++i) {
small[i] = i;
}
int non_empty = 0;
for (std::size_t b = 0; b < small.bucket_count(); ++b) {
if (small.bucket_size(b) > 0) {
++non_empty;
std::cout << "bucket " << b << " 有 " << small.bucket_size(b) << " 个元素\n";
}
}
std::cout << "共 " << small.bucket_count() << " 个桶," << non_empty << " 个非空\n";
std::cout << "max_load_factor = " << small.max_load_factor()
<< "(默认 1.0,平均每桶元素数超了就 rehash)\n";

std::cout << "\n== reserve 预撑桶,避免 hot path 里反复 rehash ==\n";
std::unordered_map<int, int> reserved;
reserved.reserve(1000); // 内部按 max_load_factor 算出足够的桶
std::cout << "reserve(1000) 后 bucket_count = " << reserved.bucket_count() << '\n';
return 0;
}
File renamed without changes.
52 changes: 52 additions & 0 deletions code/examples/vol3/09_container_adapters.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Standard: C++20
// 容器适配器:stack(LIFO) / queue(FIFO) / priority_queue(堆)——priority_queue 默认最大堆,greater
// 变最小堆
#include <functional>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>

int main() {
std::cout << "== stack:LIFO,top/push/pop 全在 back 一端 ==\n";
std::stack<int> s;
for (int x : {1, 2, 3}) {
s.push(x);
}
std::cout << "push 1,2,3 后,top = " << s.top() << "(最后进的先出)\n";

std::cout << "\n== queue:FIFO,push 在 back、front/pop 在 front ==\n";
std::queue<int> q;
for (int x : {1, 2, 3}) {
q.push(x);
}
std::cout << "push 1,2,3 后,front = " << q.front() << " back = " << q.back() << '\n';

std::cout << "\n== priority_queue 默认最大堆(vector + less)==\n";
std::priority_queue<int> pq;
for (int x : {5, 1, 9, 3, 7}) {
pq.push(x);
}
std::cout << "依次 pop: ";
while (!pq.empty()) {
std::cout << pq.top() << ' ';
pq.pop();
}
std::cout << '\n';

std::cout << "\n== 换 greater 变最小堆 ==\n";
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
for (int x : {5, 1, 9, 3, 7}) {
min_pq.push(x);
}
std::cout << "依次 pop: ";
while (!min_pq.empty()) {
std::cout << min_pq.top() << ' ';
min_pq.pop();
}
std::cout << '\n';

std::cout << "\n== 复杂度:top O(1),push/pop O(log n) ==\n";
std::cout << "(push = push_back + push_heap;pop = pop_heap + pop_back;底层就是堆算法)\n";
return 0;
}
37 changes: 37 additions & 0 deletions code/examples/vol3/10_new_containers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Standard: C++26
// 新标准容器:flat_map(C++23 拍平排序 vector) / inplace_vector(C++26 定容不堆分配) / mdspan(C++23
// 多维视图)
#include <cstdio>
#include <flat_map>
#include <inplace_vector>
#include <mdspan>
#include <string>

int main() {
std::printf("== flat_map(C++23):底层排序 vector,find O(log n) 且 cache 友好 ==\n");
std::flat_map<int, std::string> fm;
fm.insert({3, "three"});
fm.insert({1, "one"});
fm.insert({2, "two"}); // O(n):维护有序要搬移
auto it = fm.find(2); // O(log n):二分查找
std::printf("find(2) = %s\n", it->second.c_str());
std::printf("有序遍历:");
for (auto [k, v] : fm) {
std::printf("%d:%s ", k, v.c_str());
}
std::printf("\n");

std::printf("\n== inplace_vector(C++26):容量编译期定死 N,元素存对象内,绝不堆分配 ==\n");
std::inplace_vector<int, 8> ipv;
for (int i = 1; i <= 5; ++i) {
ipv.push_back(i);
}
std::printf("size = %zu, capacity = %zu(无 new,放栈/静态区)\n", ipv.size(), ipv.capacity());

std::printf("\n== mdspan(C++23):一维内存的多维视图,m[i,j] 多维下标(P2128)==\n");
int raw[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 把 12 个 int 当 3 行 4 列(行优先)
std::mdspan<int, std::extents<std::size_t, 3, 4>> m(raw);
std::printf("m[1,2] = %d m[2,3] = %d rank = %zu\n", m[1, 2], m[2, 3], m.rank());
return 0;
}
44 changes: 44 additions & 0 deletions code/examples/vol3/11_initializer_lists.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Standard: C++20
// std::initializer_list:花括号背后的只读视图,以及元素无法 move 的「移动陷阱」
#include <initializer_list>
#include <iostream>
#include <vector>

class Tracked {
public:
int id;
static int copy_count;
static int move_count;

explicit Tracked(int i) : id(i) {}
Tracked(const Tracked& o) : id(o.id) { ++copy_count; }
Tracked(Tracked&& o) noexcept : id(o.id) { ++move_count; }
};
int Tracked::copy_count = 0;
int Tracked::move_count = 0;

int main() {
std::cout << "== initializer_list:编译器为 {…} 生成的只读视图 ==\n";
std::initializer_list<int> il = {1, 2, 3, 4};
std::cout << "size = " << il.size() << '\n';
int sum = 0;
for (auto x : il) {
sum += x;
}
std::cout << "sum = " << sum << '\n';

std::cout << "\n== 移动陷阱:initializer_list 元素是 const,进容器只能拷贝 ==\n";
Tracked::copy_count = 0;
Tracked::move_count = 0;
std::vector<Tracked> v{Tracked(1), Tracked(2), Tracked(3)};
std::cout << "vector{3 个 Tracked}: copies = " << Tracked::copy_count
<< ", moves = " << Tracked::move_count << '\n';
std::cout << "(元素是 const → 无法 move 出 initializer_list,只能拷贝进 vector)\n";

std::cout << "\n== 花括号初始化的重载优先级 ==\n";
std::vector<int> a{1, 2, 3}; // 走 initializer_list 构造:3 个元素 [1,2,3]
std::vector<int> b(3, 1); // 走 (count, value):3 个 1
std::cout << "a{1,2,3} = [" << a[0] << ',' << a[1] << ',' << a[2] << "] (initializer_list)\n";
std::cout << "b(3,1) = [" << b[0] << ',' << b[1] << ',' << b[2] << "] (count, value)\n";
return 0;
}
50 changes: 50 additions & 0 deletions code/examples/vol3/13_custom_allocators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Standard: C++20
// 自定义分配器:手写一个 bump(线性)分配器原型 + std::pmr 让容器在栈 buffer 里分配
#include <cstdint>
#include <iostream>
#include <memory_resource>
#include <vector>

// 最简 bump arena:指针线性前进,不回收单块,整块 reset(典型的帧/临时分配场景)
template <std::size_t N> class Arena {
alignas(std::max_align_t) std::byte buf_[N];
std::size_t offset_ = 0;

public:
void* alloc(std::size_t bytes, std::size_t align) {
std::size_t space = N - offset_;
void* ptr = buf_ + offset_;
if (std::align(align, bytes, ptr, space)) {
offset_ = static_cast<std::byte*>(ptr) - buf_ + bytes;
return ptr;
}
return nullptr; // 满了
}
std::size_t used() const { return offset_; }
static constexpr std::size_t capacity() { return N; }
};

int main() {
std::cout << "== 手写 bump arena:指针线性前进,分配 O(1) ==\n";
Arena<256> arena;
void* p1 = arena.alloc(16, alignof(int));
void* p2 = arena.alloc(32, alignof(double));
void* p3 = arena.alloc(64, alignof(std::max_align_t));
std::cout << "alloc 16/32/64 字节后,used = " << arena.used() << " / " << arena.capacity()
<< '\n';
std::cout << "(连续地址:p1=" << p1 << " p2=" << p2 << " p3=" << p3 << ")\n";

std::cout << "\n== std::pmr:让 vector 在栈上的一块 buffer 里分配,零堆分配 ==\n";
std::byte stack_buf[1024];
std::pmr::monotonic_buffer_resource mbr(stack_buf, sizeof(stack_buf));
{
std::pmr::vector<int> v(&mbr); // 这 vector 的 new/delete 走 mbr,即栈 buffer
for (int i = 0; i < 200; ++i) {
v.push_back(i);
}
std::cout << "pmr::vector 存了 " << v.size()
<< " 个 int,分配全部落在栈上 1024 字节 buffer,没碰堆\n";
}
std::cout << "(monotonic_buffer_resource 就是 bump 思路:只进不退,整块释放)\n";
return 0;
}
Loading
Loading