2024年C C++最新【C++ STL学习笔记】C+(3),2024年最新字节跳动C C++岗面试题

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

    cout << "pair2 != pair3" << endl;
}
return 0;

}


程序执行结果为:


pair != pair2  
 pair2 != pair3


最后需要指出的是,pair类模板还提供有一个 swap() 成员函数,能够互换 2 个 pair 对象的键值对,其操作成功的前提是这 2 个 pair 对象的键和值的类型要相同。例如:



#include
#include // pair
#include // string
using namespace std;
int main() {
pair <string, int> pair1(“pair”, 10);
pair <string, int> pair2(“pair2”, 20);
//交换 pair1 和 pair2 的键值对
pair1.swap(pair2);
cout << "pair1: " << pair1.first << " " << pair1.second << endl;
cout << "pair2: " << pair2.first << " " << pair2.second << endl;
return 0;
}


程序执行结果为:


pair1: pair2 20  
 pair2: pair 10


## C++ STL map容器详解


作为关联式容器的一种,map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 [C++]( ) 基本数据类型(int、double 等)、使用结构体或类自定义的类型。



> 
> 通常情况下,map 容器中存储的各个键值对都选用 string 字符串作为键的类型。
> 
> 
> 


与此同时,在使用 map 容器存储多个键值对时,该容器会自动根据各键值对的键的大小,按照既定的规则进行排序。默认情况下,map 容器选用`std::less<T>`排序规则(其中 T 表示键的数据类型),其会根据键的大小对所有键值对做升序排序。当然,根据实际情况的需要,我们可以手动指定 map 容器的排序规则,既可以选用 [STL]( ) 标准库中提供的其它排序规则(比如`std::greater<T>`),也可以自定义排序规则。



> 
> 关于如何自定义 map 容器的排序规则,后续章节会做详细讲解。
> 
> 
> 


另外需要注意的是,使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改。换句话说,map 容器中存储的各个键值对不仅键的值独一无二,键的类型也会用 const 修饰,这意味着只要键值对被存储到 map 容器中,其键的值将不能再做任何修改。



> 
> 前面提到,map 容器存储的都是 pair 类型的键值对元素,更确切的说,该容器存储的都是 pair<const K, T> 类型(其中 K 和 T 分别表示键和值的数据类型)的键值对元素。
> 
> 
> 


map 容器定义在  头文件中,并位于 std 命名空间中。因此,如果想使用 map 容器,代码中应包含如下语句:



#include
using namespace std;



> 
> 注意,第二行代码不是必需的,如果不用,则后续程序中在使用 map 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。
> 
> 
> 


map 容器的模板定义如下:



template < class Key, // 指定键(key)的类型
class T, // 指定值(value)的类型
class Compare = less, // 指定排序规则
class Alloc = allocator<pair<const Key,T> > // 指定分配器对象的类型
> class map;


可以看到,map 容器模板有 4 个参数,其中后 2 个参数都设有默认值。大多数场景中,我们只需要设定前 2 个参数的值,有些场景可能会用到第 3 个参数,但最后一个参数几乎不会用到。


### 创建C++ map容器的几种方法


map 容器的模板类中包含多种构造函数,因此创建 map 容器的方式也有多种,下面就几种常用的创建 map 容器的方法,做一一讲解。


1. 通过调用 map 容器类的默认构造函数,可以创建出一个空的 map 容器,比如:



std::map<std::string, int>myMap;



> 
> 如果程序中已经默认指定了 std 命令空间,这里可以省略 `std::`。
> 
> 
> 


通过此方式创建出的 myMap 容器,初始状态下是空的,即没有存储任何键值对。鉴于空 map 容器可以根据需要随时添加新的键值对,因此创建空 map 容器是比较常用的。


2. 当然在创建 map 容器的同时,也可以进行初始化,比如:



std::map<std::string, int>myMap{ {“C语言教程”,10},{“STL教程”,20} };


由此,myMap 容器在初始状态下,就包含有 2 个键值对。


再次强调,map 容器中存储的键值对,其本质都是 pair 类模板创建的 pair 对象。因此,下面程序也可以创建出一模一样的 myMap 容器:



std::map<std::string, int>myMap{std::make_pair(“C语言教程”,10),std::make_pair(“STL教程”,20)};


3. 除此之外,在某些场景中,可以利用先前已创建好的 map 容器,再创建一个新的 map 容器。例如:



std::map<std::string, int>newMap(myMap);


由此,通过调用 map 容器的拷贝(复制)构造函数,即可成功创建一个和 myMap 完全一样的 newMap 容器。


C++ 11 标准中,还为 map 容器增添了移动构造函数。当有临时的 map 对象作为参数,传递给要初始化的 map 容器时,此时就会调用移动构造函数。举个例子:



#创建一个会返回临时 map 对象的函数
std::mapstd::string,int disMap() {
std::map<std::string, int>tempMap{ {“C语言教程”,10},{“STL教程”,20} };
return tempMap;
}
//调用 map 类模板的移动构造函数创建 newMap 容器
std::map<std::string, int>newMap(disMap());



> 
> 注意,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。
> 
> 
> 


4. map 类模板还支持取已建 map 容器中指定区域内的键值对,创建并初始化新的 map 容器。例如:



std::map<std::string, int>myMap{ {“C语言教程”,10},{“STL教程”,20} };
std::map<std::string, int>newMap(++myMap.begin(), myMap.end());


这里,通过调用 map 容器的双向迭代器,实现了在创建 newMap 容器的同时,将其初始化为包含一个 {“STL教程”,20} 键值对的容器。



> 
> 有关 map 容器迭代器,后续章节会做详细讲解。
> 
> 
> 


5. 当然,在以上几种创建 map 容器的基础上,我们都可以手动修改 map 容器的排序规则。默认情况下,map 容器调用 std::less 规则,根据容器内各键值对的键的大小,对所有键值对做升序排序。


因此,如下 2 行创建 map 容器的方式,其实是等价的:



std::map<std::string, int>myMap{ {“C语言教程”,10},{“STL教程”,20} };
std::map<std::string, int, std::lessstd::string >myMap{ {“C语言教程”,10},{“STL教程”,20} };


以上 2 中创建方式生成的 myMap 容器,其内部键值对排列的顺序为:


<“C语言教程”, 10>  
 <“STL教程”, 20>


下面程序手动修改了 myMap 容器的排序规则,令其作降序排序:



std::map<std::string, int, std::greaterstd::string >myMap{ {“C语言教程”,10},{“STL教程”,20} };


此时,myMap 容器内部键值对排列的顺序为:


<“STL教程”, 20>  
 <“C语言教程”, 10>



> 
> 在某些特定场景中,我们还需要为 map 容器自定义排序规则,此部分知识后续将利用整整一节做重点讲解。
> 
> 
> 


### C++ map容器包含的成员方法


表 1 列出了 map 容器提供的常用成员方法以及各自的功能。




| 成员方法 | 功能 |
| --- | --- |
| begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| find(key) | 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| lower\_bound(key) | 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| upper\_bound(key) | 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| equal\_range(key) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower\_bound() 方法的返回值等价,pair.second 和 upper\_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。 |
| empty() | 若容器为空,则返回 true;否则 false。 |
| size() | 返回当前 map 容器中存有键值对的个数。 |
| max\_size() | 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
| operator[] | map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。 |
| at(key) | 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out\_of\_range 异常。 |
| insert() | 向 map 容器中插入键值对。 |
| erase() | 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。 |
| swap() | 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。 |
| clear() | 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。 |
| emplace() | 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。 |
| emplace\_hint() | 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。 |
| count(key) | 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。 |


下面的样例演示了表 1 中部分成员方法的用法:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建空 map 容器,默认根据个键值对中键的值,对键值对做降序排序
std::map<std::string, std::string, std::greaterstd::string>myMap;
//调用 emplace() 方法,直接向 myMap 容器中指定位置构造新键值对
myMap.emplace(“C语言教程”,“http://c.biancheng.net/c/”);
myMap.emplace(“Python教程”, “http://c.biancheng.net/python/”);
myMap.emplace(“STL教程”, “http://c.biancheng.net/stl/”);
//输出当前 myMap 容器存储键值对的个数
cout << “myMap size==” << myMap.size() << endl;
//判断当前 myMap 容器是否为空
if (!myMap.empty()) {
//借助 myMap 容器迭代器,将该容器的键值对逐个输出
for (auto i = myMap.begin(); i != myMap.end(); ++i) {
cout << i->first << " " << i->second << endl;
}
}
return 0;
}


程序执行结果为:


myMap size==3  
 STL教程 http://c.biancheng.net/stl/  
 Python教程 http://c.biancheng.net/python/  
 C语言教程 http://c.biancheng.net/c/



> 
> 有关表 1 中其它成员函数的用法,后续章节会做详细详解。
> 
> 
> 


## C++ STL map容器迭代器用法详解


无论是前面学习的序列式容器,还是关联式容器,要想实现遍历操作,就必须要用到该类型容器的迭代器。当然,map 容器也不例外。


[C++]( ) [STL]( ) 标准库为 map 容器配备的是双向迭代器(bidirectional iterator)。这意味着,map 容器迭代器只能进行 ++p、p++、–p、p–、\*p 操作,并且迭代器之间只能使用 == 或者 != 运算符进行比较。


值得一提的是,相比序列式容器,map 容器提供了更多的成员方法(如表 1 所示),通过调用它们,我们可以轻松获取具有指定含义的迭代器。




| 成员方法 | 功能 |
| --- | --- |
| begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| find(key) | 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| lower\_bound(key) | 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| upper\_bound(key) | 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| equal\_range(key) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower\_bound() 方法的返回值等价,pair.second 和 upper\_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。 |


表 1 中多数的成员方法,诸如 begin()、end() 等,在学习序列式容器时已经多次使用过,它们的功能如图 2 所示。


![C++ STL map部分成员方法示意图](https://img-blog.csdnimg.cn/img_convert/a19d52f886560482da5a27b6af090c27.gif)  
 图 2 表 1 部分成员方法的功能示意图



> 
> 注意,图中 Ei 表示的是 pair 类对象,即键值对。对于 map 容器来说,每个键值对的键的值都必须保证是唯一的。
> 
> 
> 


下面程序以 begin()/end() 组合为例,演示了如何遍历 map 容器:



#include
#include // pair
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},{“C语言教程”,“http://c.biancheng.net/c/”} };
//调用 begin()/end() 组合,遍历 map 容器
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}


程序执行结果为:


C语言教程 http://c.biancheng.net/c/  
 STL教程 http://c.biancheng.net/stl/



> 
> 读者可自行尝试使用其他组合(如 cbegin()/cend()、 rbegin()/rend() 等)遍历 map 容器。
> 
> 
> 


除此之外,map 类模板中还提供了 find() 成员方法,它能帮我们查找指定 key 值的键值对,如果成功找到,则返回一个指向该键值对的双向迭代器;反之,其功能和 end() 方法相同。


举个例子:



#include
#include // pair
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
//查找键为 “Java教程” 的键值对
auto iter = myMap.find(“Java教程”);
//从 iter 开始,遍历 map 容器
for (; iter != myMap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}


程序执行结果为:


Java教程 http://c.biancheng.net/java/  
 STL教程 http://c.biancheng.net/stl/


此程序中,创建并初始化的 myMap 容器,默认会根据各键值对中键的值,对各键值对做升序排序,其排序的结果为:


<“C语言教程”,“http://c.biancheng.net/c/”>  
 <“Java教程”,“http://c.biancheng.net/java/”>  
 <“STL教程”,“http://c.biancheng.net/stl/”>


在此基础上,通过调用 find() 方法,我们可以得到一个指向键为 “Java教程” 的键值对的迭代器,由此当使用 [for 循环]( )从该迭代器出开始遍历时,就只会遍历到最后 2 个键值对。


同时,map 类模板中还提供有 lower\_bound(key) 和 upper\_bound(key) 成员方法,它们的功能是类似的,唯一的区别在于:


* lower\_bound(key) 返回的是指向第一个键不小于 key 的键值对的迭代器;
* upper\_bound(key) 返回的是指向第一个键大于 key 的键值对的迭代器;


下面程序演示了它们的功能:



#include
#include // pair
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
//找到第一个键的值不小于 “Java教程” 的键值对
auto iter = myMap.lower_bound(“Java教程”);
cout << “lower:” << iter->first << " " << iter->second << endl;

//找到第一个键的值大于 "Java教程" 的键值对
iter = myMap.upper_bound("Java教程");
cout <<"upper:" << iter->first << " " << iter->second << endl;
return 0;

}


程序执行结果为:


lower:Java教程 http://c.biancheng.net/java/  
 upper:STL教程 http://c.biancheng.net/stl/



> 
> lower\_bound(key) 和 upper\_bound(key) 更多用于 multimap 容器,在 map 容器中很少用到。
> 
> 
> 


equal\_range(key) 成员方法可以看做是 lower\_bound(key) 和 upper\_bound(key) 的结合体,该方法会返回一个 pair 对象,其中的 2 个元素都是迭代器类型,其中 pair.first 实际上就是 lower\_bound(key) 的返回值,而 pair.second 则等同于 upper\_bound(key) 的返回值。


显然,equal\_range(key) 成员方法表示的一个范围,位于此范围中的键值对,其键的值都为 key。举个例子:



#include
#include //pair
#include // map
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<string, string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
//创建一个 pair 对象,来接收 equal_range() 的返回值
pair <std::map<string, string>::iterator, std::map<string, string>::iterator> myPair = myMap.equal_range(“C语言教程”);
//通过遍历,输出 myPair 指定范围内的键值对
for (auto iter = myPair.first; iter != myPair.second; ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}


程序执行结果为:


C语言教程 http://c.biancheng.net/c/



> 
> 和 lower\_bound(key)、upper\_bound(key) 一样,该方法也更常用于 multimap 容器,因为 map 容器中各键值对的键的值都是唯一的,因此通过 map 容器调用此方法,其返回的范围内最多也只有 1 个键值对。
> 
> 
> 


## C++ STL map获取键对应值的几种方法(超详细)


我们知道,map 容器中存储的都是 pair 类型的键值对,但几乎在所有使用 map 容器的场景中,经常要做的不是找到指定的 pair 对象(键值对),而是从该容器中找到某个键对应的值。



> 
> 注意,使用 map 容器存储的各个键值对,其键的值都是唯一的,因此指定键对应的值最多有 1 个。
> 
> 
> 


庆幸的是,map 容器的类模板中提供了以下 2 种方法,可直接获取 map 容器指定键对应的值。


1. map 类模板中对`[ ]`运算符进行了重载,这意味着,类似于借助数组下标可以直接访问数组中元素,通过指定的键,我们可以轻松获取 map 容器中该键对应的值。


举个例子:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
string cValue = myMap[“C语言教程”];
cout << cValue << endl;
return 0;
}


程序执行结果为:


http://c.biancheng.net/c/


可以看到,在第 11 行代码中,通过指定键的值为 “C语言教程”,借助重载的 [ ] 运算符,就可以在 myMap 容器中直接找到该键对应的值。


注意,只有当 map 容器中确实存有包含该指定键的键值对,借助重载的 [ ] 运算符才能成功获取该键对应的值;反之,若当前 map 容器中没有包含该指定键的键值对,则此时使用 [ ] 运算符将不再是访问容器中的元素,而变成了向该 map 容器中增添一个键值对。其中,该键值对的键用 [ ] 运算符中指定的键,其对应的值取决于 map 容器规定键值对中值的数据类型,如果是基本数据类型,则值为 0;如果是 string 类型,其值为 “”,即空字符串(即使用该类型的默认值作为键值对的值)。


举个例子:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建空 map 容器
std::map<std::string, int>myMap;
int cValue = myMap[“C语言教程”];
for (auto i = myMap.begin(); i != myMap.end(); ++i) {
cout << i->first << " "<< i->second << endl;
}
return 0;
}


程序执行结果为:


C语言教程 0


显然,对于空的 myMap 容器来说,其内部没有以 “C语言教程” 为键的键值对,这种情况下如果使用 [ ] 运算符获取该键对应的值,其功能就转变成了向该 myMap 容器中添加一个`<"C语言教程",0>`键值对(由于 myMap 容器规定各个键值对的值的类型为 int,该类型的默认值为 0)。


实际上,[ ] 运算符确实有“为 map 容器添加新键值对”的功能,但前提是要保证新添加键值对的键和当前 map 容器中已存储的键值对的键都不一样。例如:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建空 map 容器
std::map<string, string>myMap;
myMap[“STL教程”]=“http://c.biancheng.net/java/”;
myMap[“Python教程”] = “http://c.biancheng.net/python/”;
myMap[“STL教程”] = “http://c.biancheng.net/stl/”;
for (auto i = myMap.begin(); i != myMap.end(); ++i) {
cout << i->first << " " << i->second << endl;
}
return 0;
}


程序执行结果为:


Python教程 http://c.biancheng.net/python/  
 STL教程 http://c.biancheng.net/stl/


注意,程序中第 9 行代码已经为 map 容器添加了一个以 “STL教程” 作为键的键值对,则第 11 行代码的作用就变成了修改该键对应的值,而不再是为 map 容器添加新键值对。


2. 除了借助 [ ] 运算符获取 map 容器中指定键对应的值,还可以使用 at() 成员方法。和前一种方法相比,at() 成员方法也需要根据指定的键,才能从容器中找到该键对应的值;不同之处在于,如果在当前容器中查找失败,该方法不会向容器中添加新的键值对,而是直接抛出 out\_of\_range 异常。


举个例子:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
cout << myMap.at(“C语言教程”) << endl;
//下面一行代码会引发 out_of_range 异常
//cout << myMap.at(“Python教程”) << endl;
return 0;
}


程序执行结果为:


http://c.biancheng.net/c/


程序第 12 行代码处,通过 myMap 容器调用 at() 成员方法,可以成功找到键为 “C语言教程” 的键值对,并返回该键对应的值;而第 14 行代码,由于当前 myMap 容器中没有以 “Python教程” 为键的键值对,会导致 at() 成员方法查找失败,并抛出 out\_of\_range 异常。


除了可以直接获取指定键对应的值之外,还可以借助 find() 成员方法间接实现此目的。和以上 2 种方式不同的是,该方法返回的是一个迭代器,即如果查找成功,该迭代器指向查找到的键值对;反之,则指向 map 容器最后一个键值对之后的位置(和 end() 成功方法返回的迭代器一样)。


举个例子:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
map< std::string, std::string >::iterator myIter = myMap.find(“C语言教程”);
cout << myIter->first << " " << myIter->second << endl;
return 0;
}


程序执行结果为:


C语言教程 http://c.biancheng.net/c/



> 
> 注意,此程序中如果 find() 查找失败,会导致第 13 行代码运行出错。因为当 find() 方法查找失败时,其返回的迭代器指向的是容器中最后一个键值对之后的位置,即不指向任何有意义的键值对,也就没有所谓的 first 和 second 成员了。
> 
> 
> 


如果以上方法都不适用,我们还可以遍历整个 map 容器,找到包含指定键的键值对,进而获取该键对应的值。比如:



#include
#include // map
#include // string
using namespace std;
int main() {
//创建并初始化 map 容器
std::map<std::string, std::string>myMap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
//调用 string 类的 compare() 方法,找到一个键和指定字符串相同的键值对
if (!iter->first.compare(“C语言教程”)) {
cout << iter->first << " " << iter->second << endl;
}
}
return 0;
}


程序执行结果为:


C语言教程 http://c.biancheng.net/c/



> 
> 本节所介绍的几种方法中,仅从“在 map 容器存储的键值对中,获取指定键对应的值”的角度出发,更推荐使用 at() 成员方法,因为该方法既简单又安全。
> 
> 
> 


## C++ STL map insert()插入数据的4种方式


前面讲过,[C++]( ) [STL]( ) map 类模板中对`[ ]`运算符进行了重载,即根据使用场景的不同,借助`[ ]`运算符可以实现不同的操作。举个例子:



#include
#include //map
#include //string
using namespace std;
int main()
{
std::map<string, string> mymap{ {“STL教程”,“http://c.biancheng.net/java/”} };
//获取已存储键值对中,指定键对应的值
cout << mymap[“STL教程”] << endl;
//向 map 容器添加新键值对
mymap[“Python教程”] = “http://c.biancheng.net/python/”;
//修改 map 容器已存储键值对中,指定键对应的值
mymap[“STL教程”] = “http://c.biancheng.net/stl/”;
for (auto iter = mymap.begin(); iter != mymap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}

return 0;

}


程序执行结果为:


http://c.biancheng.net/java/  
 Python教程 http://c.biancheng.net/python/  
 STL教程 http://c.biancheng.net/stl/


可以看到,当操作对象为 map 容器中已存储的键值对时,则借助 [ ] 运算符,既可以获取指定键对应的值,还能对指定键对应的值进行修改;反之,若 map 容器内部没有存储以 [ ] 运算符内指定数据为键的键值对,则使用 [ ] 运算符会向当前 map 容器中添加一个新的键值对。


实际上,除了使用 [ ] 运算符实现向 map 容器中添加新键值对外,map 类模板中还提供有 insert() 成员方法,该方法专门用来向 map 容器中插入新的键值对。


注意,这里所谓的“插入”,指的是 insert() 方法可以将新的键值对插入到 map 容器中的指定位置,但这与 map 容器会自动对存储的键值对进行排序并不冲突。当使用 insert() 方法向 map 容器的指定位置插入新键值对时,其底层会先将新键值对插入到容器的指定位置,如果其破坏了 map 容器的有序性,该容器会对新键值对的位置进行调整。


自 C++ 11 标准后,insert() 成员方法的用法大致有以下 4 种。


1. 无需指定插入位置,直接将键值对添加到 map 容器中。insert() 方法的语法格式有以下 2 种:


//1、引用传递一个键值对  
 pair<iterator,bool> insert (const value\_type& val);  
 //2、以右值引用的方式传递键值对  
 template   
 pair<iterator,bool> insert (P&& val);


其中,val 参数表示键值对变量,同时该方法会返回一个 pair 对象,其中 pair.first 表示一个迭代器,pair.second 为一个 bool 类型变量:


* 如果成功插入 val,则该迭代器指向新插入的 val,bool 值为 true;
* 如果插入 val 失败,则表明当前 map 容器中存有和 val 的键相同的键值对(用 p 表示),此时返回的迭代器指向 p,bool 值为 false。


以上 2 种语法格式的区别在于传递参数的方式不同,即无论是局部定义的键值对变量还是全局定义的键值对变量,都采用普通引用传递的方式;而对于临时的键值对变量,则以右值引用的方式传参。有关右值引用,可阅读《[C++右值引用]( )》一文做详细了解。


举个例子:



#include
#include //map
#include //string
using namespace std;
int main()
{
//创建一个空 map 容器
std::map<string, string> mymap;

//创建一个真实存在的键值对变量
std::pair<string, string> STL = { "STL教程","http://c.biancheng.net/stl/" };

//创建一个接收 insert() 方法返回值的 pair 对象
std::pair<std::map<string, string>::iterator, bool> ret;

//插入 STL,由于 STL 并不是临时变量,因此会以第一种方式传参
ret = mymap.insert(STL);
cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
//以右值引用的方式传递临时的键值对变量
ret = mymap.insert({ "C语言教程","http://c.biancheng.net/c/" });
cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
//插入失败样例
ret = mymap.insert({ "STL教程","http://c.biancheng.net/java/" });
cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
return 0;

}


程序执行结果为:


ret.iter = <{STL教程, http://c.biancheng.net/stl/}, 1>  
 ret.iter = <{C语言教程, http://c.biancheng.net/c/}, 1>  
 ret.iter = <{STL教程, http://c.biancheng.net/stl/}, 0>


从执行结果中不难看出,程序中共执行了 3 次插入操作,其中成功了 2 次,失败了 1 次:


* 对于插入成功的 insert() 方法,其返回的 pair 对象中包含一个指向新插入键值对的迭代器和值为 1 的 bool 变量
* 对于插入失败的 insert() 方法,同样会返回一个 pair 对象,其中包含一个指向 map 容器中键为 “STL教程” 的键值对和值为 0 的 bool 变量。


另外,在程序中的第 21 行代码,还可以使用如下 2 种方式创建临时的键值对变量,它们是等价的:



//调用 pair 类模板的构造函数
ret = mymap.insert(pair<string,string>{ “C语言教程”,“http://c.biancheng.net/c/” });
//调用 make_pair() 函数
ret = mymap.insert(make_pair(“C语言教程”, “http://c.biancheng.net/c/”));


2. 除此之外,insert() 方法还支持向 map 容器的指定位置插入新键值对,该方法的语法格式如下:


//以普通引用的方式传递 val 参数  
 iterator insert (const\_iterator position, const value\_type& val);  
 //以右值引用的方式传递 val 键值对参数  
 template   
 iterator insert (const\_iterator position, P&& val);


其中 val 为要插入的键值对变量。注意,和第 1 种方式的语法格式不同,这里 insert() 方法返回的是迭代器,而不再是 pair 对象:


* 如果插入成功,insert() 方法会返回一个指向 map 容器中已插入键值对的迭代器;
* 如果插入失败,insert() 方法同样会返回一个迭代器,该迭代器指向 map 容器中和 val 具有相同键的那个键值对。


举个例子:



#include
#include //map
#include //string
using namespace std;
int main()
{
//创建一个空 map 容器
std::map<string, string> mymap;

//创建一个真实存在的键值对变量
std::pair<string, string> STL = { "STL教程","http://c.biancheng.net/stl/" };
//指定要插入的位置
std::map<string, string>::iterator it = mymap.begin();
//向 it 位置以普通引用的方式插入 STL
auto iter1 = mymap.insert(it, STL);
cout << iter1->first << " " << iter1->second << endl;
//向 it 位置以右值引用的方式插入临时键值对
auto iter2 = mymap.insert(it, std::pair<string, string>("C语言教程", "http://c.biancheng.net/c/"));
cout << iter2->first << " " << iter2->second << endl;
//插入失败样例
auto iter3 = mymap.insert(it, std::pair<string, string>("STL教程", "http://c.biancheng.net/java/"));
cout << iter3->first << " " << iter3->second << endl;
return 0;

}


程序执行结果为:


STL教程 http://c.biancheng.net/stl/  
 C语言教程 http://c.biancheng.net/c/  
 STL教程 http://c.biancheng.net/stl/


再次强调,即便指定了新键值对的插入位置,map 容器仍会对存储的键值对进行排序。也可以说,决定新插入键值对位于 map 容器中位置的,不是 insert() 方法中传入的迭代器,而是新键值对中键的值。


3. insert() 方法还支持向当前 map 容器中插入其它 map 容器指定区域内的所有键值对,该方法的语法格式如下:


template   
 void insert (InputIterator first, InputIterator last);


其中 first 和 last 都是迭代器,它们的组合`<first,last>`可以表示某 map 容器中的指定区域。


举个例子:



#include
#include //map
#include //string
using namespace std;
int main()
{
//创建并初始化 map 容器
std::map<std::string, std::string>mymap{ {“STL教程”,“http://c.biancheng.net/stl/”},
{“C语言教程”,“http://c.biancheng.net/c/”},
{“Java教程”,“http://c.biancheng.net/java/”} };
//创建一个空 map 容器
std::map<std::string, std::string>copymap;
//指定插入区域
std::map<string, string>::iterator first = ++mymap.begin();
std::map<string, string>::iterator last = mymap.end();
//将<first,last>区域内的键值对插入到 copymap 中
copymap.insert(first, last);
//遍历输出 copymap 容器中的键值对
for (auto iter = copymap.begin(); iter != copymap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}


程序执行结果为:


Java教程 http://c.biancheng.net/java/  
 STL教程 http://c.biancheng.net/stl/


此程序中,<first,last> 指定的区域是从 mumap 容器第 2 个键值对开始,之后所有的键值对,所以 copymap 容器中包含有 2 个键值对。


4. 除了以上一种格式外,insert() 方法还允许一次向 map 容器中插入多个键值对,其语法格式为:


void insert ({val1, val2, …});


其中,vali 都表示的是键值对变量。


举个例子:



#include
#include //map
#include //string
using namespace std;
int main()
{
//创建空的 map 容器
std::map<std::string, std::string>mymap;
//向 mymap 容器中添加 3 个键值对
mymap.insert({ {“STL教程”, “http://c.biancheng.net/stl/”},
{ “C语言教程”,“http://c.biancheng.net/c/” },
{ “Java教程”,“http://c.biancheng.net/java/” } });
for (auto iter = mymap.begin(); iter != mymap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}


程序执行结果为:


C语言教程 http://c.biancheng.net/c/  
 Java教程 http://c.biancheng.net/java/  
 STL教程 http://c.biancheng.net/stl/


值得一提的是,除了 insert() 方法,map 类模板还提供 emplace() 和 emplace\_hint() 方法,它们也可以完成向 map 容器中插入键值对的操作,且效率还会 insert() 方法高。关于这 2 个方法,会在下一节做详细介绍。


## C++ map容器operator[]和insert()效率对比(深度剖析)


通过前面的学习我们知道,map 容器模板类中提供有 operator[ ] 和 insert() 这 2 个成员方法,而值得一提的是,这 2 个方法具有相同的功能,它们既可以实现向 map 容器中添加新的键值对元素,也可以实现更新(修改)map 容器已存储键值对的值。


举个例子(程序一):



#include
#include //map
#include //string
using namespace std;
int main()
{
std::map<string, string> mymap;
//借用 operator[] 添加新键值对
mymap[“STL教程”] = “http://c.biancheng.net/java/”;
cout << “old mymap:” << mymap[“STL教程”] << endl;
//借用 operator[] 更新某个键对应的值
mymap[“STL教程”] = “http://c.biancheng.net/stl/”;
cout << “new mymap:” << mymap[“STL教程”] << endl;
//借用insert()添加新键值对
std::pair<string, string> STL = { “Java教程”,“http://c.biancheng.net/python/” };
std::pair<std::map<string, string>::iterator, bool> ret;
ret = mymap.insert(STL);
cout << “old ret.iter = <{” << ret.first->first << ", " << ret.first->second << "}, " << ret.second << “>” << endl;
//借用 insert() 更新键值对
mymap.insert(STL).first->second = “http://c.biancheng.net/java/”;
cout << “new ret.iter = <” << ret.first->first << ", " << ret.first->second << “>” << endl;
return 0;
}


程序执行结果为:


old mymap:http://c.biancheng.net/java/  
 new mymap:http://c.biancheng.net/stl/  
 old ret.iter = <{Java教程, http://c.biancheng.net/python/}, 1>  
 new ret.iter = <Java教程, http://c.biancheng.net/java/>



> 
> 有关程序中 operator[ ] 和 insert() 成员方法的具体用法,读者可翻阅前面的文章做详细了解,这里不再做过多解释。
> 
> 
> 


显然,map 模板类中 operator[ ] 和 insert() 的功能发生了重叠,这就产生了一个问题,谁的执行效率更高呢?


总的来说,读者可记住这样一条结论:当实现“向 map 容器中添加新键值对元素”的操作时,insert() 成员方法的执行效率更高;而在实现“更新 map 容器指定键值对的值”的操作时,operator[ ] 的效率更高。


至于为什么,有兴趣的读者可继续往下阅读。


### 向map容器中增添元素,insert()效率更高


首先解释一下,为什么实现向 map 容器中添加新键值对元素,insert() 方法的执行效率比 operator[ ] 更高?回顾程序一中,如下语句完成了向空 mymap 容器添加新的键值对元素:



mymap[“STL教程”] = “http://c.biancheng.net/java/”;


此行代码中,mymap[“STL教程”] 实际上是 mymap.operator 的缩写(底层调用的 operator[ ] 方法),该方法会返回一个指向 “STL教程” 对应的 value 值的引用。


但需要注意的是,由于此时 mymap 容器是空的,并没有 “STL教程” 对应的 value 值。这种情况下,operator[ ] 方法会默认构造一个 string 对象,并将其作为 “STL教程” 对应的 value 值,然后返回一个指向此 string 对象的引用。在此基础上,代码还会将 “http://c.biancheng.net.java/” 赋值给这个 string 对象。


也就是说,上面这行代码的执行流程,可以等效为如下程序:



typedef map<string, string> mstr;
//创建要添加的默认键值对元素
pair<mstr::iterator, bool>res = mymap.insert(mstr::value_type(“STL教程”, string()));
//将新键值对的值赋值为指定的值
res.first->second = “http://c.biancheng.net/java/”;



> 
> 注意,这里的 value\_type(K,T) 指的是 map 容器中存储元素的类型,其实际上就等同于 pair<K,T>。
> 
> 
> 


可以看到,使用 operator[ ] 添加新键值对元素的流程是,先构造一个有默认值的键值对,然后再为其 value 赋值。


那么,为什么不直接构造一个要添加的键值对元素呢,比如:



mymap.insert(mstr::value_type(“STL教程”, “http://c.biancheng.net/java/”));


此行代码和上面程序的执行效果完全相同,但它省略了创建临时 string 对象的过程以及析构该对象的过程,同时还省略了调用 string 类重载的赋值运算符。由于可见,同样是完成向 map 容器添加新键值对,insert() 方法比 operator[ ] 的执行效率更高。


### 更新map容器中的键值对,operator[]效率更高


仍以程序一中的代码为例,如下分别是 operator[ ] 和 insert() 实现更新 mymap 容器中指定键对应的值的代码:



//operator[]
mymap[“STL教程”] = “http://c.biancheng.net/stl/”;
//insert()
std::pair<string, string> STL = { “Java教程”,“http://c.biancheng.net/python/” };
mymap.insert(STL).first->second = “http://c.biancheng.net/java/”;


仅仅从语法形式本身来考虑,或许已经促使很多读者选择 operator[ ] 了。接下来,我们再从执行效率的角度对比以上 2 种实现方式。


从上面代码可以看到,insert() 方法在进行更新操作之前,需要有一个 pair 类型(也就是 map::value\_type 类型)元素做参数。这意味着,该方法要多构造一个 pair 对象(附带要构造 2 个 string 对象),并且事后还要析构此 pair 对象(附带 2 个 string 对象的析构)。


而和 insert() 方法相比,operator[ ] 就不需要使用 pair 对象,自然不需要构造(并析构)任何 pair 对象或者 string 对象。因此,对于更新已经存储在 map 容器中键值对的值,应优先使用 operator[ ] 方法。


## C++ STL map emplace()和emplace\_hint()方法详解


学习 map insert() 方法时提到,[C++]( ) [STL]( ) map 类模板中还提供了 emplace() 和 emplace\_hint() 成员函数,也可以实现向 map 容器中插入新的键值对。本节就来讲解这 2 个成员方法的用法。


值得一提的是,实现相同的插入操作,无论是用 emplace() 还是 emplace\_hont(),都比 insert() 方法的效率高(后续章节会详细讲解)。


和 insert() 方法相比,emplace() 和 emplace\_hint() 方法的使用要简单很多,因为它们各自只有一种语法格式。其中,emplace() 方法的语法格式如下:


template <class… Args>  
 pair<iterator,bool> emplace (Args&&… args);


参数 (Args&&… args) 指的是,这里只需要将创建新键值对所需的数据作为参数直接传入即可,此方法可以自行利用这些数据构建出指定的键值对。另外,该方法的返回值也是一个 pair 对象,其中 pair.first 为一个迭代器,pair.second 为一个 bool 类型变量:


* 当该方法将键值对成功插入到 map 容器中时,其返回的迭代器指向该新插入的键值对,同时 bool 变量的值为 true;
* 当插入失败时,则表明 map 容器中存在具有相同键的键值对,此时返回的迭代器指向此具有相同键的键值对,同时 bool 变量的值为 false。


下面程序演示 emplace() 方法的具体用法:



#include
#include //map
#include //string
using namespace std;
int main()
{
//创建并初始化 map 容器
std::map<string, string>mymap;
//插入键值对
pair<map<string, string>::iterator, bool> ret = mymap.emplace(“STL教程”, “http://c.biancheng.net/stl/”);
cout << “1、ret.iter = <{” << ret.first->first << ", " << ret.first->second << "}, " << ret.second << “>” << endl;
//插入新键值对
ret = mymap.emplace(“C语言教程”, “http://c.biancheng.net/c/”);
cout << “2、ret.iter = <{” << ret.first->first << ", " << ret.first->second << "}, " << ret.second << “>” << endl;
//失败插入的样例
ret = mymap.emplace(“STL教程”, “http://c.biancheng.net/java/”);
cout << “3、ret.iter = <{” << ret.first->first << ", " << ret.first->second << "}, " << ret.second << “>” << endl;
return 0;
}


程序执行结果为:


1、ret.iter = <{STL教程, http://c.biancheng.net/stl/}, 1>  
 2、ret.iter = <{C语言教程, http://c.biancheng.net/c/}, 1>  
 3、ret.iter = <{STL教程, http://c.biancheng.net/stl/}, 0>


可以看到,程序中共执行了 3 次向 map 容器插入键值对的操作,其中前 2 次都成功了,第 3 次由于要插入的键值对的键和 map 容器中已存在的键值对的键相同,因此插入失败。


emplace\_hint() 方法的功能和 emplace() 类似,其语法格式如下:


template <class… Args>  
 iterator emplace\_hint (const\_iterator position, Args&&… args);


显然和 emplace() 语法格式相比,有以下 2 点不同:


1. 该方法不仅要传入创建键值对所需要的数据,还需要传入一个迭代器作为第一个参数,指明要插入的位置(新键值对键会插入到该迭代器指向的键值对的前面);
2. 该方法的返回值是一个迭代器,而不再是 pair 对象。当成功插入新键值对时,返回的迭代器指向新插入的键值对;反之,如果插入失败,则表明 map 容器中存有相同键的键值对,返回的迭代器就指向这个键值对。


下面程序演示 emplace\_hint() 方法的用法:



#include
#include //map
#include //string
using namespace std;
int main()
{
//创建并初始化 map 容器
std::map<string, string>mymap;
//指定在 map 容器插入键值对
map<string, string>::iterator iter = mymap.emplace_hint(mymap.begin(),“STL教程”, “http://c.biancheng.net/stl/”);
cout << iter->first << " " << iter->second << endl;
iter = mymap.emplace_hint(mymap.begin(), “C语言教程”, “http://c.biancheng.net/c/”);
cout << iter->first << " " << iter->second << endl;
//插入失败样例
iter = mymap.emplace_hint(mymap.begin(), “STL教程”, “http://c.biancheng.net/java/”);
cout << iter->first << " " << iter->second << endl;
return 0;
}


程序执行结果为:


STL教程 http://c.biancheng.net/stl/  
 C语言教程 http://c.biancheng.net/c/  
 STL教程 http://c.biancheng.net/stl/


注意,和 insert() 方法一样,虽然 emplace\_hint() 方法指定了插入键值对的位置,但 map 容器为了保持存储键值对的有序状态,可能会移动其位置。


那么,为什么 emplace() 和 emplace\_hint() 方法的执行效率,比 insert() 高呢?下一节会做详细解释。


## C++ map容器3种插入键值对的方法,谁的效率更高?


上一节在学习 C++STL map 容器的 emplace() 和 emplace\_hint() 的基本用法时,还遗留了一个问题,即为什么 emplace() 和 emplace\_hint() 的执行效率会比 insert() 高?


原因很简单,它们向 map 容器插入键值对时,底层的实现方式不同:


* 使用 insert() 向 map 容器中插入键值对的过程是,先创建该键值对,然后再将该键值对复制或者移动到 map 容器中的指定位置;
* 使用 emplace() 或 emplace\_hint() 插入键值对的过程是,直接在 map 容器中的指定位置构造该键值对。


也就是说,向 map 容器中插入键值对时,emplace() 和 emplace\_hint() 方法都省略了移动键值对的过程,因此执行效率更高。下面程序提供了有利的证明:



#include
#include //map
#include //string
using namespace std;
class testDemo
{
public:
testDemo(int num) :num(num) {
std::cout << “调用构造函数” << endl;
}
testDemo(const testDemo& other) :num(other.num) {
std::cout << “调用拷贝构造函数” << endl;
}
testDemo(testDemo&& other) :num(other.num) {
std::cout << “调用移动构造函数” << endl;
}
private:
int num;
};
int main()
{
//创建空 map 容器
std::map<std::string, testDemo>mymap;
cout << “insert():” << endl;
mymap.insert({ “http://c.biancheng.net/stl/”, testDemo(1) });

cout << "emplace():" << endl;
mymap.emplace( "http://c.biancheng.net/stl/:", 1);
cout << "emplace_hint():" << endl;
mymap.emplace_hint(mymap.begin(), "http://c.biancheng.net/stl/", 1);
return 0;

}


程序输出结果为:


insert():  
 调用构造函数  
 调用移动构造函数  
 调用移动构造函数  
 emplace():  
 调用构造函数  
 emplace\_hint():  
 调用构造函数


分析一下这个程序。首先,我们创建了一个存储 <string,tempDemo> 类型键值对的空 map 容器,接下来分别用 insert()、emplace() 和 emplace\_hint() 方法向该 map 容器中插入相同的键值对。


从输出结果可以看出,在使用 insert() 方法向 map 容器插入键值对时,整个插入过程调用了 1 次 tempDemo 类的构造函数,同时还调用了 2 次移动构造函数。实际上,程序第 28 行代码底层的执行过程,可以分解为以下 3 步:



//构造类对象
testDemo val = testDemo(1); //调用 1 次构造函数
//构造键值对
auto pai = make_pair(“http://c.biancheng.net/stl/”, val); //调用 1 次移动构造函数
//完成插入操作
mymap.insert(pai); //调用 1 次移动构造函数


而完成同样的插入操作,emplace() 和 emplace\_hint() 方法都只调用了 1 次构造函数,这足以证明,这 2 个方法是在 map 容器内部直接构造的键值对。


因此,在实现向 map 容器中插入键值对时,应优先考虑使用 emplace() 或者 emplace\_hint()。


## C++ STL multimap容器用法完全攻略(超详细)


在掌握 [C++]( ) [STL]( ) map 容器的基础上,本节再讲一个和 map 相似的关联式容器,即 multimap 容器。


所谓“相似”,指的是 multimap 容器具有和 map 相同的特性,即 multimap 容器也用于存储 pair<const K, T> 类型的键值对(其中 K 表示键的类型,T 表示值的类型),其中各个键值对的键的值不能做修改;并且,该容器也会自行根据键的大小对存储的所有键值对做排序操作。和 map 容器的区别在于,multimap 容器中可以同时存储多(≥2)个键相同的键值对。


和 map 容器一样,实现 multimap 容器的类模板也定义在`<map>`头文件,并位于 std 命名空间中。因此,在使用 multimap 容器前,程序应包含如下代码:


#include   
 u[sin]( )g namespace std;



> 
> 注意,第二行代码不是必需的,但若不用,则程序中在使用 multimap 容器时需手动注明 std 命名空间(强烈建议初学者使用)。
> 
> 
> 


multimap 容器类模板的定义如下:



template < class Key, // 指定键(key)的类型
class T, // 指定值(value)的类型
class Compare = less, // 指定排序规则
class Alloc = allocator<pair<const Key,T> > // 指定分配器对象的类型
> class multimap;


可以看到,multimap 容器模板有 4 个参数,其中后 2 个参数都设有默认值。



> 
> 大多数场景中,我们只需要设定前 2 个参数的值,有些场景可能会用到第 3 个参数,但最后一个参数几乎不会用到。
> 
> 
> 


### 创建C++ multimap容器的方法


multimap 类模板内部提供有多个构造函数,总的来说,创建 multimap 容器的方式可归为以下 5 种。


1. 通过调用 multimap 类模板的默认构造函数,可以创建一个空的 multimap 容器:


std::multimap<std::string, std::string>mymultimap;



> 
> 如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。
> 
> 
> 


2. 当然,在创建 multimap 容器的同时,还可以进行初始化操作。比如:



//创建并初始化 multimap 容器
multimap<string, string>mymultimap{ {“C语言教程”, “http://c.biancheng.net/c/”},
{“Python教程”, “http://c.biancheng.net/python/”},
{“STL教程”, “http://c.biancheng.net/stl/”} };


注意,使用此方式初始化 multimap 容器时,其底层会先将每一个`{key, value}`创建成 pair 类型的键值对,然后再用已建好的各个键值对初始化 multimap 容器。


实际上,我们完全可以先手动创建好键值对,然后再用其初始化 multimap 容器。下面程序使用了 2 种方式创建 pair 类型键值对,再用其初始化 multimap 容器,它们是完全等价的:



//借助 pair 类模板的构造函数来生成各个pair类型的键值对
multimap<string, string>mymultimap{
pair<string,string>{“C语言教程”, “http://c.biancheng.net/c/”},
pair<string,string>{ “Python教程”, “http://c.biancheng.net/python/”},
pair<string,string>{ “STL教程”, “http://c.biancheng.net/stl/”}
};
//调用 make_pair() 函数,生成键值对元素
//创建并初始化 multimap 容器
multimap<string, string>mymultimap{
make_pair(“C语言教程”, “http://c.biancheng.net/c/”),
make_pair(“Python教程”, “http://c.biancheng.net/python/”),
make_pair(“STL教程”, “http://c.biancheng.net/stl/”)
};


3. 除此之外,通过调用 multimap 类模板的拷贝(复制)构造函数,也可以初始化新的 multimap 容器。例如:



multimap<string, string>newmultimap(mymultimap);


由此,就成功创建一个和 mymultimap 完全一样的 newmultimap 容器。


在 C++ 11 标准中,还为 multimap 类增添了移动构造函数。即当有临时的 multimap 容器作为参数初始化新 multimap 容器时,其底层就会调用移动构造函数来实现初始化操作。举个例子:



//创建一个会返回临时 multimap 对象的函数
multimap<string, string> dismultimap() {
multimap<string, string>tempmultimap{ {“C语言教程”, “http://c.biancheng.net/c/”},{“Python教程”, “http://c.biancheng.net/python/”} };
return tempmultimap;
}
//调用 multimap 类模板的移动构造函数创建 newMultimap 容器
multimap<string, string>newmultimap(dismultimap());


上面程序中,由于 dismultimap() 函数返回的 tempmultimap 容器是一个临时对象,因此在实现初始化 newmultimap 容器时,底层调用的是 multimap 容器的移动构造函数,而不再是拷贝构造函数。



> 
> 注意,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。
> 
> 
> 


4. multimap 类模板还支持从已有 multimap 容器中,选定某块区域内的所有键值对,用作初始化新 multimap 容器时使用。例如:



//创建并初始化 multimap 容器
multimap<string, string>mymultimap{ {“C语言教程”, “http://c.biancheng.net/c/”},
{“Python教程”, “http://c.biancheng.net/python/”},
{“STL教程”, “http://c.biancheng.net/stl/”} };
multimap<string, string>newmultimap(++mymultimap.begin(), mymultimap.end());


这里使用了 multimap 容器的迭代器,选取了 mymultimap 容器中的最后 2 个键值对,用于初始化 newmultimap 容器。



> 
> multimap 容器迭代器,和 map 容器迭代器的用法完全相同,这里不再赘述。
> 
> 
> 


5. 前面讲到,multimap 类模板共可以接收 4 个参数,其中第 3 个参数可用来修改 multimap 容器内部的排序规则。默认情况下,此参数的值为`std::less<T>`,这意味着以下 2 种创建 multimap 容器的方式是等价的:



multimap<char, int>mymultimap{ {‘a’,1},{‘b’,2} };
multimap<char, int, std::less>mymultimap{ {‘a’,1},{‘b’,2} };


mymultimap 容器中键值对的存储顺序为:


<a,1>  
 <b,2>


下面程序利用了 STL 模板库提供的`std::greater<T>`排序函数,实现令 multimap 容器对存储的键值对做降序排序:



multimap<char, int, std::greater>mymultimap{ {‘a’,1},{‘b’,2} };


其内部键值对的存储顺序为:


<b,2>  
 <a,1>



> 
> 在某些特定场景中,我们还可以为 multimap 容器自定义排序规则,此部分知识后续将利用整整一节做重点讲解。
> 
> 
> 


### C++ multimap容器包含的成员方法


表 1 列出了 multimap 类模板提供的常用成员方法及各自的功能。




| 成员方法 | 功能 |
| --- | --- |
| begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
| find(key) | 在 multimap 容器中查找首个键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| lower\_bound(key) | 返回一个指向当前 multimap 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| upper\_bound(key) | 返回一个指向当前 multimap 容器中第一个大于 key 的键值对的迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| equal\_range(key) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower\_bound() 方法的返回值等价,pair.second 和 upper\_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对。 |
| empty() | 若容器为空,则返回 true;否则 false。 |
| size() | 返回当前 multimap 容器中存有键值对的个数。 |
| max\_size() | 返回 multimap 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
| insert() | 向 multimap 容器中插入键值对。 |
| erase() | 删除 multimap 容器指定位置、指定键(key)值或者指定区域内的键值对。 |
| swap() | 交换 2 个 multimap 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。 |
| clear() | 清空 multimap 容器中所有的键值对,使 multimap 容器的 size() 为 0。 |
| emplace() | 在当前 multimap 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。 |
| emplace\_hint() | 在本质上和 emplace() 在 multimap 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。 |
| count(key) | 在当前 multimap 容器中,查找键为 key 的键值对的个数并返回。 |


和 map 容器相比,multimap 未提供 at() 成员方法,也没有重载 [] 运算符。这意味着,map 容器中通过指定键获取指定指定键值对的方式,将不再适用于 multimap 容器。其实这很好理解,因为 multimap 容器中指定的键可能对应多个键值对,而不再是 1 个。



> 
> 另外值的一提的是,由于 multimap 容器可存储多个具有相同键的键值对,因此表 1 中的 lower\_bound()、upper\_bound()、equal\_range() 以及 count() 成员方法会经常用到。
> 
> 
> 


下面例子演示了表 1 中部分成员方法的用法:



#include
#include //map
using namespace std;
int main()
{
//创建并初始化 multimap 容器
multimap<char, int>mymultimap{ {‘a’,10},{‘b’,20},{‘b’,15}, {‘c’,30} };
//输出 mymultimap 容器存储键值对的数量
cout << mymultimap.size() << endl;
//输出 mymultimap 容器中存储键为 ‘b’ 的键值对的数量
cout << mymultimap.count(‘b’) << endl;
for (auto iter = mymultimap.begin(); iter != mymultimap.end(); ++iter) {
cout << iter->first << " " << iter->second << endl;
}
return 0;
}


程序执行结果为:


4  
 2  
 a 10  
 b 20  
 b 15  
 c 30



> 
> 注意,只要是 multimap 容器提供的成员方法,map 容器都提供,并且它们的用法是相同的。前面章节中已经对 map 容器提供的成员方法做了详细的讲解,因此这里不再对表 1 中其它的成员方法做详细的介绍。
> 
> 
> 


## C++ STL set容器完全攻略(超级详细)


前面章节讲解了 map 容器和 multimap 容器的用法,类似地,[C++]( ) [STL]( ) 标准库中还提供有 set 和 multiset 这 2 个容器,它们也属于关联式容器。不过,本节先讲解 set 容器,后续章节再讲解 multiset 容器。


和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。


举个例子,如下有 2 组键值对数据:


{<‘a’, 1>, <‘b’, 2>, <‘c’, 3>}  
 {<‘a’, ‘a’>, <‘b’, ‘b’>, <‘c’, ‘c’>}


显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。


基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {‘a’,‘b’,‘c’} ,该容器即可成功将它们存储起来。


通过前面的学习我们知道,map、multimap 容器都会自行根据键的大小对存储的键值对进行排序,set 容器也会如此,只不过 set 容器中各键值对的键 key 和值 value 是相等的,根据 key 排序,也就等价为根据 value 排序。


另外,使用 set 容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是,C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。



> 
> 对于初学者来说,切勿尝试直接修改 set 容器中已存储元素的值,这很有可能破坏 set 容器中元素的有序性,最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。
> 
> 
> 


值得一提的是,set 容器定义于`<set>`头文件,并位于 std 命名空间中。因此如果想在程序中使用 set 容器,该程序代码应先包含如下语句:



#include
using namespace std;


注意,第二行代码不是必需的,如果不用,则后续程序中在使用 set 容器时,需手动注明 std 命名空间(强烈建议初学者使用)。


set 容器的类模板定义如下:



template < class T, // 键 key 和值 value 的类型
class Compare = less, // 指定 set 容器内部的排序规则
class Alloc = allocator // 指定分配器对象的类型
> class set;


注意,由于 set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型。



> 
> 对于 set 类模板中的 3 个参数,后 2 个参数自带默认值,且几乎所有场景中只需使用前 2 个参数,第 3 个参数不会用到。
> 
> 
> 


### 创建C++ set容器的几种方法


常见的创建 set 容器的方法,大致有以下 5 种。


1. 调用默认构造函数,创建空的 set 容器。比如:



std::setstd::string myset;



> 
> 如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。
> 
> 
> 


由此就创建好了一个 set 容器,该容器采用默认的`std::less<T>`规则,会对存储的 string 类型元素做升序排序。注意,由于 set 容器支持随时向内部添加新的元素,因此创建空 set 容器的方法是经常使用的。


2. 除此之外,set 类模板还支持在创建 set 容器的同时,对其进行初始化。例如:



std::setstd::string myset{“http://c.biancheng.net/java/”,
“http://c.biancheng.net/stl/”,
“http://c.biancheng.net/python/”};


由此即创建好了包含 3 个 string 元素的 myset 容器。由于其采用默认的 std::less 规则,因此其内部存储 string 元素的顺序如下所示:


“http://c.biancheng.net/java/”  
 “http://c.biancheng.net/python/”  
 “http://c.biancheng.net/stl/”


3. set 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中。


例如,在第 2 种方式创建的 myset 容器的基础上,执行如下代码:



std::setstd::string copyset(myset);
//等同于
//std::setstd::string copyset = myset


该行代码在创建 copyset 容器的基础上,还会将 myset 容器中存储的所有元素,全部复制给 copyset 容器一份。


另外,C++ 11 标准还为 set 类模板新增了移动构造函数,其功能是实现创建新 set 容器的同时,利用临时的 set 容器为其初始化。比如:



set retSet() {
std::setstd::string myset{ “http://c.biancheng.net/java/”,
“http://c.biancheng.net/stl/”,
“http://c.biancheng.net/python/” };
return myset;
}
std::setstd::string copyset(retSet());
//或者
//std::setstd::string copyset = retSet();


注意,由于 retSet() 函数的返回值是一个临时 set 容器,因此在初始化 copyset 容器时,其内部调用的是 set 类模板中的移动构造函数,而非拷贝构造函数。



> 
> 显然,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。
> 
> 
> 


4. 在第 3 种方式的基础上,set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如:



std::setstd::string myset{ “http://c.biancheng.net/java/”,
“http://c.biancheng.net/stl/”,
“http://c.biancheng.net/python/” };
std::setstd::string copyset(++myset.begin(), myset.end());


由此初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:


“http://c.biancheng.net/python/”  
 “http://c.biancheng.net/stl/”


5. 以上几种方式创建的 set 容器,都采用了默认的`std::less<T>`规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如:



std::set<std::string,std::greater > myset{ “http://c.biancheng.net/java/”, “http://c.biancheng.net/stl/”, “http://c.biancheng.net/python/”};


通过选用 std::greater 降序规则,myset 容器中元素的存储顺序为:


“http://c.biancheng.net/stl/”  
 “http://c.biancheng.net/python/”  
 “http://c.biancheng.net/java/”


### C++ STL set容器包含的成员方法


表 1 列出了 set 容器提供的常用成员方法以及各自的功能。




| 成员方法 | 功能 |
| --- | --- |
| begin() | 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| find(val) | 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| lower\_bound(val) | 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| upper\_bound(val) | 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| equal\_range(val) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower\_bound() 方法的返回值等价,pair.second 和 upper\_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。 |
| empty() | 若容器为空,则返回 true;否则 false。 |
| size() | 返回当前 set 容器中存有元素的个数。 |
| max\_size() | 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。 |
| insert() | 向 set 容器中插入元素。 |
| erase() | 删除 set 容器中存储的元素。 |
| swap() | 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。 |
| clear() | 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。 |
| emplace() | 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。 |
| emplace\_hint() | 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。 |
| count(val) | 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。 |


下面程序演示了表 1 中部分成员函数的用法:



#include
#include
#include
using namespace std;
int main()
{
//创建空set容器
std::setstd::string myset;
//空set容器不存储任何元素
cout << "1、myset size = " << myset.size() << endl;
//向myset容器中插入新元素
myset.insert(“http://c.biancheng.net/java/”);
myset.insert(“http://c.biancheng.net/stl/”);
myset.insert(“http://c.biancheng.net/python/”);
cout << "2、myset size = " << myset.size() << endl;
//利用双向迭代器,遍历myset
for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}


程序执行结果为:


1、myset size = 0  
 2、myset size = 3  
 http://c.biancheng.net/java/  
 http://c.biancheng.net/python/  
 http://c.biancheng.net/stl/



> 
> 有关表 1 中其它成员方法的用法,后续章节会做详细讲解。
> 
> 
> 


## C++ STL set容器迭代器用法详解


和 map 容器不同,[C++]( ) [STL]( ) 中的 set 容器类模板中未提供 at() 成员函数,也未对 [] 运算符进行重载。因此,要想访问 set 容器中存储的元素,只能借助 set 容器的迭代器。


值得一提的是,C++ STL 标准库为 set 容器配置的迭代器类型为双向迭代器。这意味着,假设 p 为此类型的迭代器,则其只能进行 ++p、p++、–p、p–、\*p 操作,并且 2 个双向迭代器之间做比较,也只能使用 == 或者 != 运算符。


在 set 容器类模板提供的所有成员函数中,返回迭代器的成员函数如表 1 所示。




| 成员方法 | 功能 |
| --- | --- |
| begin() | 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。通常和 rbegin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
| cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
| find(val) | 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| lower\_bound(val) | 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| upper\_bound(val) | 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
| equal\_range(val) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower\_bound() 方法的返回值等价,pair.second 和 upper\_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。 |



> 
> 注意,以上成员函数返回的迭代器,指向的只是 set 容器中存储的元素,而不再是键值对。另外,以上成员方法返回的迭代器,无论是 const 类型还是非 const 类型,都不能用于修改 set 容器中的值。
> 
> 
> 


图 2 演示了表 1 中除最后 4 个成员函数外,其它几个成员函数的具体功能。


![img](https://img-blog.csdnimg.cn/img_convert/a19d52f886560482da5a27b6af090c27.gif)  
 图 2 set容器迭代器功能示意图



> 
> 其中,Ei 表示 set 容器中存储的各个元素,它们的值各不相同。
> 
> 
> 


下面程序以 begin()/end() 为例,演示了如何使用图 2 中相关迭代器遍历 set 容器:



#include
#include
#include
using namespace std;
int main()
{
//创建并初始化set容器
std::setstd::string myset{ “http://c.biancheng.net/java/”,
“http://c.biancheng.net/stl/”,
“http://c.biancheng.net/python/”
};
//利用双向迭代器,遍历myset
for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}


程序执行结果为:


http://c.biancheng.net/java/  
 http://c.biancheng.net/python/  
 http://c.biancheng.net/stl/



> 
> 再次强调,正如程序第 15 行代码所示的那样,因为 iter 迭代器指向的是 set 容器存储的某个元素,而不是键值对,因此通过 \*iter 可以直接获取该迭代器指向的元素的值。
> 
> 
> 


除此之外,如果只想遍历 set 容器中指定区域内的部分数据,则可以借助 find()、lower\_bound() 以及 upper\_bound() 实现。通过调用它们,可以获取一个指向指定元素的迭代器。


需要特别指出的是,equal\_range(val) 函数的返回值是一个 pair 类型数据,其包含 2 个迭代器,表示 set 容器中和指定参数 val 相等的元素所在的区域,但由于 set 容器中存储的元素各不相等,因此该函数返回的这 2 个迭代器所表示的范围中,最多只会包含 1 个元素。


举个例子:



#include
#include
#include
using namespace std;
int main()
{
//创建并初始化set容器
std::setstd::string myset{ “http://c.biancheng.net/java/”,
“http://c.biancheng.net/stl/”,
“http://c.biancheng.net/python/”
};

set<string>::iterator iter = myset.find("http://c.biancheng.net/python/");
for (;iter != myset.end();++iter)
{
    cout << *iter << endl;
}
return 0;

}


程序执行结果为:


http://c.biancheng.net/python/  
 http://c.biancheng.net/stl/



> 
> 值得一提的是,虽然 C++ STL 标准中,set 类模板中包含 lower\_bound()、upper\_bound()、equal\_range() 这 3 个成员函数,但它们更适用于 multiset 容器,几乎不会用于操作 set 容器。
> 
> 
> 


## C++ STL set insert()方法详解


通过前面的学习,我们已经学会如何创建一个 set 容器。在此基础上,如果想向 set 容器中继续添加元素,可以借助 set 类模板提供的 insert() 方法。


为满足不同场景的需要,[C++]( ) 11 标准的 set 类模板中提供了多种不同语法格式的 insert() 成员方法,它们各自的功能和用法如下所示。


1. 只要给定目标元素的值,insert() 方法即可将该元素添加到 set 容器中,其语法格式如下:


//普通引用方式传参  
 pair<iterator,bool> insert (const value\_type& val);  
 //右值引用方式传参  
 pair<iterator,bool> insert (value\_type&& val);


其中,val 表示要添加的新元素,该方法的返回值为 pair 类型。



> 
> 以上 2 种格式的区别仅在于传递参数的方式不同,即第一种采用普通引用的方式传参,而第二种采用右值引用的方式传参。右值引用为 C++ 11 新添加的一种引用方式,可阅读《[C++ 右值引用]( )》一文做详细了解。
> 
> 
> 



![img](https://img-blog.csdnimg.cn/img_convert/164e715f62c8915f65db8a1a4685d20b.png)
![img](https://img-blog.csdnimg.cn/img_convert/97485775402320a5f94fa2898eb62e95.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

out << *iter << endl;
    }
    return 0;
}

程序执行结果为:

http://c.biancheng.net/java/
http://c.biancheng.net/python/
http://c.biancheng.net/stl/

再次强调,正如程序第 15 行代码所示的那样,因为 iter 迭代器指向的是 set 容器存储的某个元素,而不是键值对,因此通过 *iter 可以直接获取该迭代器指向的元素的值。

除此之外,如果只想遍历 set 容器中指定区域内的部分数据,则可以借助 find()、lower_bound() 以及 upper_bound() 实现。通过调用它们,可以获取一个指向指定元素的迭代器。

需要特别指出的是,equal_range(val) 函数的返回值是一个 pair 类型数据,其包含 2 个迭代器,表示 set 容器中和指定参数 val 相等的元素所在的区域,但由于 set 容器中存储的元素各不相等,因此该函数返回的这 2 个迭代器所表示的范围中,最多只会包含 1 个元素。

举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
    //创建并初始化set容器
    std::set<std::string> myset{ "http://c.biancheng.net/java/",
                                 "http://c.biancheng.net/stl/",
                                 "http://c.biancheng.net/python/"
    };
   
    set<string>::iterator iter = myset.find("http://c.biancheng.net/python/");
    for (;iter != myset.end();++iter)
    {
        cout << *iter << endl;
    }
    return 0;
}

程序执行结果为:

http://c.biancheng.net/python/
http://c.biancheng.net/stl/

值得一提的是,虽然 C++ STL 标准中,set 类模板中包含 lower_bound()、upper_bound()、equal_range() 这 3 个成员函数,但它们更适用于 multiset 容器,几乎不会用于操作 set 容器。

C++ STL set insert()方法详解

通过前面的学习,我们已经学会如何创建一个 set 容器。在此基础上,如果想向 set 容器中继续添加元素,可以借助 set 类模板提供的 insert() 方法。

为满足不同场景的需要,C++ 11 标准的 set 类模板中提供了多种不同语法格式的 insert() 成员方法,它们各自的功能和用法如下所示。

  1. 只要给定目标元素的值,insert() 方法即可将该元素添加到 set 容器中,其语法格式如下:

//普通引用方式传参
pair<iterator,bool> insert (const value_type& val);
//右值引用方式传参
pair<iterator,bool> insert (value_type&& val);

其中,val 表示要添加的新元素,该方法的返回值为 pair 类型。

以上 2 种格式的区别仅在于传递参数的方式不同,即第一种采用普通引用的方式传参,而第二种采用右值引用的方式传参。右值引用为 C++ 11 新添加的一种引用方式,可阅读《C++ 右值引用》一文做详细了解。

[外链图片转存中…(img-D27HkRyk-1715555889183)]
[外链图片转存中…(img-DvcaurSV-1715555889183)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值