@@ -2705,6 +2705,7 @@ for (auto it = m.begin(); it != m.end(); ++it /* 进入燃烧中的1号朋友家
2705
2705
```cpp
2706
2706
for (auto it = m.begin(); it != m.end(); ++it) {
2707
2707
m.erase(it);
2708
+ // it 已经失效!
2708
2709
}
2709
2710
```
2710
2711
@@ -2767,8 +2768,6 @@ print(msg);
2767
2768
2768
2769
<!-- PG109 -->
2769
2770
2770
- ::left::
2771
-
2772
2771
不奔溃
2773
2772
2774
2773
```cpp
@@ -2782,8 +2781,6 @@ for (auto it = m.begin(); it != m.end(); ) {
2782
2781
}
2783
2782
```
2784
2783
2785
- ::right::
2786
-
2787
2784
奔溃
2788
2785
2789
2786
```cpp
@@ -2889,8 +2886,8 @@ pair<iterator, bool> insert(pair<const K, V> &&kv);
2889
2886
2890
2887
pair 是一个 STL 中常见的模板类型,`pair<K, V>` 有两个成员变量:
2891
2888
2892
- - first:V 类型,表示要插入元素的键
2893
- - second:K 类型,表示要插入元素的值
2889
+ - first:K 类型,表示要插入元素的键
2890
+ - second:V 类型,表示要插入元素的值
2894
2891
2895
2892
我称之为"键值对"。
2896
2893
@@ -2970,8 +2967,6 @@ insert 插入和 [] 写入的异同:
2970
2967
2971
2968
例子:
2972
2969
2973
- ::left::
2974
-
2975
2970
```cpp
2976
2971
map<string, string> m;
2977
2972
m.insert({"key", "old"});
@@ -2983,8 +2978,6 @@ print(m);
2983
2978
{"key": "old"}
2984
2979
```
2985
2980
2986
- ::right::
2987
-
2988
2981
```cpp
2989
2982
map<string, string> m;
2990
2983
m["key"] = "old";
@@ -3252,6 +3245,26 @@ print(config);
3252
3245
```
3253
3246
{"delay": 211, "timeout": 985}
3254
3247
```
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
+ ```
3255
3268
3256
3269
<!-- PG127 -->
3257
3270
@@ -3825,7 +3838,9 @@ emplace 对于 set,元素类型是比较大的类型时,例如 `set<array<in
3825
3838
3826
3839
## try_emplace 更好
3827
3840
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>>`
3829
3844
3830
3845
> {{ icon.detail }} 如果想不用 try_emplace,完全基于 emplace 实现针对值 value 的就地构造需要用到 std::piecewise_construct 和 std::forward_as_tuple,非常麻烦。
3831
3846
@@ -3848,6 +3863,18 @@ m.try_emplace(key, arg1, arg2, ...);
3848
3863
m.insert({key, V(arg1, arg2, ...)});
3849
3864
```
3850
3865
3866
+ 后面的变长参数也可以完全没有:
3867
+
3868
+ ```cpp
3869
+ m.try_emplace(key);
3870
+ ```
3871
+
3872
+ 他等价于调用 V 的默认构造函数:
3873
+
3874
+ ```cpp
3875
+ m.insert({key, V()});
3876
+ ```
3877
+
3851
3878
由于 emplace 实在是憨憨,他变长参数列表就地构造的是 pair,然而 pair 的构造函数正常不就是只有两个参数吗,变长没有用。实际有用的往往是我们希望用变长参数列表就地构造值类型 V,对 K 部分并不关系。因此 C++17 引入了 try_emplace,其键部分保持 `K const &`,值部分采用变长参数列表。
3852
3879
3853
3880
我的评价是:这个比 emplace 实用多了,如果要与 vector 的 emplace_back 对标,那么 map 与之对应的一定是 try_emplace。同学们如果要分奴的话还是建议用 try_emplace。
@@ -3874,9 +3901,9 @@ m.try_emplace("key"); // MyClass()
3874
3901
m.try_emplace("key", 42); // MyClass(int)
3875
3902
m.try_emplace("key", "hell", 3.14f); // MyClass(const char *, float)
3876
3903
// 等价于:
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)
3880
3907
```
3881
3908
3882
3909
对于移动开销较大的类型(例如 `array<int, 1000>`),try_emplace 可以避免移动;对于不支持移动构造函数的值类型,就必须使用 try_emplace 了。
@@ -3889,6 +3916,7 @@ m.insert({"key", {"hell", 3.14f}}); // MyClass(const char *, float)
3889
3916
// 以下两种方式效果等价,只有性能不同
3890
3917
m.try_emplace(key, arg1, arg2, ...); // 开销:1次构造函数
3891
3918
m.insert({key, V(arg1, arg2, ...)}); // 开销:1次构造函数 + 2次移动函数
3919
+ m.insert(make_pair(key, V(arg1, arg2, ...))); // 开销:1次构造函数 + 3次移动函数
3892
3920
```
3893
3921
3894
3922
但是由于 try_emplace 是用圆括号帮你调用的构造函数,而不是花括号初始化。
@@ -4422,18 +4450,31 @@ node_type 是指向游离红黑树节点的特殊智能指针,称为节点句
4422
4450
```cpp
4423
4451
{
4424
4452
auto node = m.extract("fuck");
4425
- print(node.key(), node.value ());
4453
+ print(node.key(), node.mapped ());
4426
4454
} // node 在此自动销毁
4427
4455
```
4428
4456
4429
4457
也可以做一些修改后(例如修改键值),稍后重新用 insert(node) 重新把他插入回去:
4430
4458
4431
4459
```cpp
4432
4460
auto node = m.extract("fuck");
4433
- nh .key() = "love";
4461
+ node .key() = "love";
4434
4462
m.insert(std::move(node));
4435
4463
```
4436
4464
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
+
4437
4478
或者插入到另一个不同的 map 对象(但键和值类型相同)里:
4438
4479
4439
4480
```cpp
@@ -4761,10 +4802,10 @@ static void BM_Insert(benchmark::State &state) {
4761
4802
auto m1 = m1_init;
4762
4803
auto m2 = m2_init;
4763
4804
m2.insert(m1.begin(), m1.end());
4764
- benchmark::DoNotOptimize(m3 );
4805
+ benchmark::DoNotOptimize(m2 );
4765
4806
}
4766
4807
}
4767
- BENCHMARK(BM_Insert);
4808
+ BENCHMARK(BM_Insert)->Arg(1000) ;
4768
4809
4769
4810
static void BM_Merge(benchmark::State &state) {
4770
4811
map<string, int> m1_init;
@@ -4780,7 +4821,7 @@ static void BM_Merge(benchmark::State &state) {
4780
4821
benchmark::DoNotOptimize(m2);
4781
4822
}
4782
4823
}
4783
- BENCHMARK(BM_Merge);
4824
+ BENCHMARK(BM_Merge)->Arg(1000) ;
4784
4825
```
4785
4826
4786
4827
merge 函数不会产生不必要的内存分配导致内存碎片化,所以更高效。但作为代价,他会清空 m2!
@@ -4944,7 +4985,9 @@ struct Student {
4944
4985
string sex;
4945
4986
4946
4987
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< 运算符
4948
4991
}
4949
4992
};
4950
4993
@@ -4965,7 +5008,7 @@ struct Student {
4965
5008
template <>
4966
5009
struct std::less<Student> { // 用户可以特化标准库中的 trait
4967
5010
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) ;
4969
5012
}
4970
5013
};
4971
5014
@@ -4987,10 +5030,7 @@ struct Student {
4987
5030
4988
5031
struct LessStudent {
4989
5032
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
- // 等价于:
4992
5033
return std::tie(x.name, x.id, y.sex) < std::tie(x.name, x.id, y.sex);
4993
- // 因为 tuple 实现了正确的 operator< 运算符
4994
5034
}
4995
5035
};
4996
5036
@@ -5096,8 +5136,8 @@ auto ilist = {
5096
5136
{985, "拳打"},
5097
5137
{211, "脚踢"},
5098
5138
};
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; // 从大到小排序
5101
5141
print(m1); // {{211, "脚踢"}, {985, "拳打"}}
5102
5142
print(m2); // {{985, "拳打"}, {211, "脚踢"}}
5103
5143
```
@@ -5345,11 +5385,11 @@ const_iterator find(Kt &&k) const;
5345
5385
5346
5386
map 的比较器必须是“透明(transparent)”的,也就是 `less<void>` 这种。否则泛型版的 `find(Kt &&)` 不会参与重载,也就是只能调用传统的 `find(K const &)`。
5347
5387
5348
- 但是 `map<K, V>` 默认的比较器是 `less<V >`,他是不透明的,比较的两边必须都是 `V ` 类型。如果其中一边不是的话,就得先隐式转换为 `V ` 才能用。
5388
+ 但是 `map<K, V>` 默认的比较器是 `less<K >`,他是不透明的,比较的两边必须都是 `K ` 类型。如果其中一边不是的话,就得先隐式转换为 `K ` 才能用。
5349
5389
5350
5390
这是早期 C++98 设计的失败,当时他们没想到 `find` 还可以接受 `string_view` 和 `const char *` 这类可以和 `string` 比较,但构造会廉价得多的弱引用类型。
5351
5391
5352
- 只好后来引入了透明比较器企图 ,然而为了历史兼容,`map<K, V>` 默认仍然是 `map<K, V, less<K>>`。
5392
+ 只好后来引入了透明比较器企图力挽狂澜 ,然而为了历史兼容,`map<K, V>` 默认仍然是 `map<K, V, less<K>>`。
5353
5393
5354
5394
如果我们同学的编译器支持 C++14,建议全部改用这种写法 `map<K, V, less<>>`,从而用上更高效的 find、at、erase、count、contains 等需要按键查找元素的函数。
5355
5395
@@ -5577,6 +5617,13 @@ multimap 排序的好处是:
5577
5617
5578
5618
因为 multimap 中,一个键不再对于单个值了;所以 multimap 没有 `[]` 和 `at` 了,也没有 `insert_or_assign`(反正 `insert` 永远不会发生键冲突!)
5579
5619
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
+
5580
5627
要查询 multimap 中的一个键对应了哪些值,可以用 `equal_range` 获取一前一后两个迭代器,他们形成一个区间。这个区间内所有的元素都是同样的键。
5581
5628
5582
5629
```cpp
0 commit comments