Skip to content

Commit 9d0cee6

Browse files
committed
fix typos
1 parent f260509 commit 9d0cee6

File tree

2 files changed

+95
-28
lines changed

2 files changed

+95
-28
lines changed

docs/stl_map.md

+75-28
Original file line numberDiff line numberDiff line change
@@ -2705,6 +2705,7 @@ for (auto it = m.begin(); it != m.end(); ++it /* 进入燃烧中的1号朋友家
27052705
```cpp
27062706
for (auto it = m.begin(); it != m.end(); ++it) {
27072707
m.erase(it);
2708+
// it 已经失效!
27082709
}
27092710
```
27102711

@@ -2767,8 +2768,6 @@ print(msg);
27672768

27682769
<!-- PG109 -->
27692770

2770-
::left::
2771-
27722771
不奔溃
27732772

27742773
```cpp
@@ -2782,8 +2781,6 @@ for (auto it = m.begin(); it != m.end(); ) {
27822781
}
27832782
```
27842783

2785-
::right::
2786-
27872784
奔溃
27882785

27892786
```cpp
@@ -2889,8 +2886,8 @@ pair<iterator, bool> insert(pair<const K, V> &&kv);
28892886

28902887
pair 是一个 STL 中常见的模板类型,`pair<K, V>` 有两个成员变量:
28912888

2892-
- first:V 类型,表示要插入元素的键
2893-
- second:K 类型,表示要插入元素的值
2889+
- first:K 类型,表示要插入元素的键
2890+
- second:V 类型,表示要插入元素的值
28942891

28952892
我称之为"键值对"。
28962893

@@ -2970,8 +2967,6 @@ insert 插入和 [] 写入的异同:
29702967

29712968
例子:
29722969

2973-
::left::
2974-
29752970
```cpp
29762971
map<string, string> m;
29772972
m.insert({"key", "old"});
@@ -2983,8 +2978,6 @@ print(m);
29832978
{"key": "old"}
29842979
```
29852980

2986-
::right::
2987-
29882981
```cpp
29892982
map<string, string> m;
29902983
m["key"] = "old";
@@ -3252,6 +3245,26 @@ print(config);
32523245
```
32533246
{"delay": 211, "timeout": 985}
32543247
```
3248+
```cpp
3249+
vector<pair<string, int>> kvs = {
3250+
{"timeout", 985},
3251+
{"delay", 211},
3252+
{"delay", 666},
3253+
{"delay", 233},
3254+
{"timeout", 996},
3255+
};
3256+
map<string, int> config = {
3257+
{"timeout", 404},
3258+
};
3259+
config.insert(kvs.begin(), kvs.end());
3260+
print(config);
3261+
3262+
vector<unique_ptr<int>> v;
3263+
```
3264+
3265+
```
3266+
{"delay": 211, "timeout": 404}
3267+
```
32553268

32563269
<!-- PG127 -->
32573270

@@ -3825,7 +3838,9 @@ emplace 对于 set,元素类型是比较大的类型时,例如 `set<array<in
38253838

38263839
## try_emplace 更好
38273840

3828-
emplacec 只支持 pair 的就地构造,这有什么用?我们要的是 pair 中值类型的就地构造!这就是 try_emplace 的作用了,他对 key 部分依然是传统的移动,只对 value 部分采用就地构造。
3841+
emplace 只支持 pair 的就地构造,这有什么用?我们要的是 pair 中值类型的就地构造!这就是 try_emplace 的作用了,他对 key 部分依然是传统的移动,只对 value 部分采用就地构造。
3842+
3843+
> {{ icon.tip }} 这是观察到大多是值类型很大,急需就地构造,而键类型没用多少就地构造的需求。例如 `map<string, array<int, 1000>>`
38293844

38303845
> {{ icon.detail }} 如果想不用 try_emplace,完全基于 emplace 实现针对值 value 的就地构造需要用到 std::piecewise_construct 和 std::forward_as_tuple,非常麻烦。
38313846

@@ -3848,6 +3863,18 @@ m.try_emplace(key, arg1, arg2, ...);
38483863
m.insert({key, V(arg1, arg2, ...)});
38493864
```
38503865

3866+
后面的变长参数也可以完全没有:
3867+
3868+
```cpp
3869+
m.try_emplace(key);
3870+
```
3871+
3872+
他等价于调用 V 的默认构造函数:
3873+
3874+
```cpp
3875+
m.insert({key, V()});
3876+
```
3877+
38513878
由于 emplace 实在是憨憨,他变长参数列表就地构造的是 pair,然而 pair 的构造函数正常不就是只有两个参数吗,变长没有用。实际有用的往往是我们希望用变长参数列表就地构造值类型 V,对 K 部分并不关系。因此 C++17 引入了 try_emplace,其键部分保持 `K const &`,值部分采用变长参数列表。
38523879

38533880
我的评价是:这个比 emplace 实用多了,如果要与 vector 的 emplace_back 对标,那么 map 与之对应的一定是 try_emplace。同学们如果要分奴的话还是建议用 try_emplace。
@@ -3874,9 +3901,9 @@ m.try_emplace("key"); // MyClass()
38743901
m.try_emplace("key", 42); // MyClass(int)
38753902
m.try_emplace("key", "hell", 3.14f); // MyClass(const char *, float)
38763903
// 等价于:
3877-
m.insert({"key", {}}); // MyClass()
3878-
m.insert({"key", {42}}); // MyClass(int)
3879-
m.insert({"key", {"hell", 3.14f}}); // MyClass(const char *, float)
3904+
m.insert({"key", MyClass()}); // MyClass()
3905+
m.insert({"key", MyClass(42)}); // MyClass(int)
3906+
m.insert({"key", MyClass("hell", 3.14f)}); // MyClass(const char *, float)
38803907
```
38813908

38823909
对于移动开销较大的类型(例如 `array<int, 1000>`),try_emplace 可以避免移动;对于不支持移动构造函数的值类型,就必须使用 try_emplace 了。
@@ -3889,6 +3916,7 @@ m.insert({"key", {"hell", 3.14f}}); // MyClass(const char *, float)
38893916
// 以下两种方式效果等价,只有性能不同
38903917
m.try_emplace(key, arg1, arg2, ...); // 开销:1次构造函数
38913918
m.insert({key, V(arg1, arg2, ...)}); // 开销:1次构造函数 + 2次移动函数
3919+
m.insert(make_pair(key, V(arg1, arg2, ...))); // 开销:1次构造函数 + 3次移动函数
38923920
```
38933921

38943922
但是由于 try_emplace 是用圆括号帮你调用的构造函数,而不是花括号初始化。
@@ -4422,18 +4450,31 @@ node_type 是指向游离红黑树节点的特殊智能指针,称为节点句
44224450
```cpp
44234451
{
44244452
auto node = m.extract("fuck");
4425-
print(node.key(), node.value());
4453+
print(node.key(), node.mapped());
44264454
} // node 在此自动销毁
44274455
```
44284456

44294457
也可以做一些修改后(例如修改键值),稍后重新用 insert(node) 重新把他插入回去:
44304458

44314459
```cpp
44324460
auto node = m.extract("fuck");
4433-
nh.key() = "love";
4461+
node.key() = "love";
44344462
m.insert(std::move(node));
44354463
```
44364464

4465+
> {{ icon.tip }} 过去,通过迭代器来修改键值是不允许的:
4466+
4467+
```cpp
4468+
map<string, int> m;
4469+
auto it = m.find("fuck");
4470+
assert(it != m.end());
4471+
// *it 是 pair<const string, int>
4472+
it->first = "love"; // 错误!first 是 const string 类型
4473+
m.insert(*it);
4474+
```
4475+
4476+
> {{ icon.tip }} 因为直接修改在 map 里面的一个节点的键,会导致排序失效,破坏红黑树的有序。而 extract 取出来的游离态节点,可以修改 `.key()`,不会影响任何红黑树的顺序,他已经不在树里面了。
4477+
44374478
或者插入到另一个不同的 map 对象(但键和值类型相同)里:
44384479

44394480
```cpp
@@ -4761,10 +4802,10 @@ static void BM_Insert(benchmark::State &state) {
47614802
auto m1 = m1_init;
47624803
auto m2 = m2_init;
47634804
m2.insert(m1.begin(), m1.end());
4764-
benchmark::DoNotOptimize(m3);
4805+
benchmark::DoNotOptimize(m2);
47654806
}
47664807
}
4767-
BENCHMARK(BM_Insert);
4808+
BENCHMARK(BM_Insert)->Arg(1000);
47684809

47694810
static void BM_Merge(benchmark::State &state) {
47704811
map<string, int> m1_init;
@@ -4780,7 +4821,7 @@ static void BM_Merge(benchmark::State &state) {
47804821
benchmark::DoNotOptimize(m2);
47814822
}
47824823
}
4783-
BENCHMARK(BM_Merge);
4824+
BENCHMARK(BM_Merge)->Arg(1000);
47844825
```
47854826

47864827
merge 函数不会产生不必要的内存分配导致内存碎片化,所以更高效。但作为代价,他会清空 m2!
@@ -4944,7 +4985,9 @@ struct Student {
49444985
string sex;
49454986

49464987
bool operator<(Student const &that) const {
4947-
return name < that.name || id < that.id || sex < that.sex;
4988+
return x.name < y.name || (x.name == y.name && (x.id < y.id || (x.id == y.id && x.sex < y.sex)));
4989+
// 等价于:
4990+
return std::tie(x.name, x.id, y.sex) < std::tie(x.name, x.id, y.sex); // tuple 实现了正确的 operator< 运算符
49484991
}
49494992
};
49504993

@@ -4965,7 +5008,7 @@ struct Student {
49655008
template <>
49665009
struct std::less<Student> { // 用户可以特化标准库中的 trait
49675010
bool operator()(Student const &x, Student const &y) const {
4968-
return x.name < y.name || x.id < y.id || x.sex < y.sex;
5011+
return std::tie(x.name, x.id, y.sex) < std::tie(x.name, x.id, y.sex);
49695012
}
49705013
};
49715014

@@ -4987,10 +5030,7 @@ struct Student {
49875030

49885031
struct LessStudent {
49895032
bool operator()(Student const &x, Student const &y) const {
4990-
return x.name < y.name || (x.name == y.name && (x.id < y.id || (x.id == y.id && x.sex < y.sex)));
4991-
// 等价于:
49925033
return std::tie(x.name, x.id, y.sex) < std::tie(x.name, x.id, y.sex);
4993-
// 因为 tuple 实现了正确的 operator< 运算符
49945034
}
49955035
};
49965036

@@ -5096,8 +5136,8 @@ auto ilist = {
50965136
{985, "拳打"},
50975137
{211, "脚踢"},
50985138
};
5099-
map<int, string> m1 = ilist; // 从小到大排序
5100-
map<int, string, greater<int>> m2 = ilist;
5139+
map<int, string> m1 = ilist; // 从小到大排序
5140+
map<int, string, greater<int>> m2 = ilist; // 从大到小排序
51015141
print(m1); // {{211, "脚踢"}, {985, "拳打"}}
51025142
print(m2); // {{985, "拳打"}, {211, "脚踢"}}
51035143
```
@@ -5345,11 +5385,11 @@ const_iterator find(Kt &&k) const;
53455385

53465386
map 的比较器必须是“透明(transparent)”的,也就是 `less<void>` 这种。否则泛型版的 `find(Kt &&)` 不会参与重载,也就是只能调用传统的 `find(K const &)`。
53475387

5348-
但是 `map<K, V>` 默认的比较器是 `less<V>`,他是不透明的,比较的两边必须都是 `V` 类型。如果其中一边不是的话,就得先隐式转换为 `V` 才能用。
5388+
但是 `map<K, V>` 默认的比较器是 `less<K>`,他是不透明的,比较的两边必须都是 `K` 类型。如果其中一边不是的话,就得先隐式转换为 `K` 才能用。
53495389

53505390
这是早期 C++98 设计的失败,当时他们没想到 `find` 还可以接受 `string_view` 和 `const char *` 这类可以和 `string` 比较,但构造会廉价得多的弱引用类型。
53515391

5352-
只好后来引入了透明比较器企图,然而为了历史兼容,`map<K, V>` 默认仍然是 `map<K, V, less<K>>`。
5392+
只好后来引入了透明比较器企图力挽狂澜,然而为了历史兼容,`map<K, V>` 默认仍然是 `map<K, V, less<K>>`。
53535393

53545394
如果我们同学的编译器支持 C++14,建议全部改用这种写法 `map<K, V, less<>>`,从而用上更高效的 find、at、erase、count、contains 等需要按键查找元素的函数。
53555395

@@ -5577,6 +5617,13 @@ multimap 排序的好处是:
55775617

55785618
因为 multimap 中,一个键不再对于单个值了;所以 multimap 没有 `[]` 和 `at` 了,也没有 `insert_or_assign`(反正 `insert` 永远不会发生键冲突!)
55795619

5620+
```cpp
5621+
pair<iterator, iterator> equal_range(K const &k);
5622+
5623+
template <class Kt>
5624+
pair<iterator, iterator> equal_range(Kt &&k);
5625+
```
5626+
55805627
要查询 multimap 中的一个键对应了哪些值,可以用 `equal_range` 获取一前一后两个迭代器,他们形成一个区间。这个区间内所有的元素都是同样的键。
55815628

55825629
```cpp

examples/try_emplace.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <map>
2+
#include <cstdio>
3+
4+
int main() {
5+
struct MyClass {
6+
MyClass() { printf("MyClass()\n"); }
7+
MyClass(MyClass &&) noexcept { printf("MyClass(MyClass &&)\n"); }
8+
MyClass &operator=(MyClass &&) noexcept { printf("MyClass &operator=(MyClass &&)\n"); return *this; }
9+
};
10+
11+
std::map<int, MyClass> tab;
12+
printf("insert+make_pair的开销:\n");
13+
tab.insert(std::make_pair(1, MyClass()));
14+
printf("insert的开销:\n");
15+
tab.insert({2, MyClass()});
16+
printf("try_emplace的开销:\n");
17+
tab.try_emplace(3);
18+
// try_emplace 只有一个 key 参数时,相当于调用无参构造函数 MyClass()
19+
return 0;
20+
}

0 commit comments

Comments
 (0)