关联容器
使用关联式容器存储的元素,都是一个一个的“键值对”( <key,value> ),底层选用了 「红黑树」这种数据结构来组织和存储各个键值对。
除此之外,序列式容器中存储的元素默认都是未经过排序的,而使用关联式容器存储的元素,默认会根据各元素的键值的大小做升序排序。
1 pair 类模板
“键值对”算是什么数据类型?
C++ STL 标准库提供了 pair 类模板,其专门用来将 2 个普通元素 first 和 second创建成一个新元素<first, second>。
1.1 pair 的初始化
#include <utility>
- #1) 默认构造函数,即创建空的 pair 对象
pair(); - #2) 直接使用 2 个元素初始化成 pair 对象
pair (const first_type& a, const second_type& b); - #3) 拷贝(复制)构造函数,即借助另一个 pair 对象,创建新的 pair 对象
- template<class U, class V>
pair (const pair<U,V>& pr);
在 C++ 11 标准中,在引入右值引用的基础上,pair 类模板中又增添了如下 2 个构造函数:
- #4) 移动构造函数
template<class U, class V> pair (pair<U,V>&& pr); - #5) 使用右值引用参数,创建 pair 对象
template<class U, class V> pair (U&& a, V&& b);
// 无参构造
pair <string, double> pair1;
// 带初值的构造函数
pair <string, string> pair2("2","2");
// 调用拷贝构造函数
pair <string, string> pair3(pair2);
//make_pair("4", "4")返回的是匿名临时变量的右值引用
pair <string, string> pair4(make_pair("4", "4"));
// string("5")返回的是匿名临时变量的右值引用
pair <string, string> pair5(string("5"), string("5"));
1.2 pair 之间的比较
<utility>
头文件中除了提供创建 pair 对象的方法之外,还为 pair 对象重载了 <、<=、>、>=、==、!= 这 6 的运算符,其运算规则是:对于进行比较的 2 个 pair 对象,先比较 pair.first 元素的大小,如果相等则继续比较 pair.second 元素的大小。
2 map 容器
#include <map>
map 容器存储的都是 pair 对象,选用std::less排序规则(其中 T 表示键的数据类型),其会根据键的大小对所有键值对做升序排序。
使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改。map 容器中存储的各个键值对不仅键的值独一无二,键的类型也会用 const 修饰,这意味着只要键值对被存储到 map 容器中,其键的值将不能再做任何修改。
2.1 初始化
template < class Key, // 指定键(key)的类型
class T, // 指定值(value)的类型
class Compare = less<Key>, // 指定排序规则
class Alloc = allocator<pair<const Key,T> > // 指定分配器对象的类型
map 容器模板有 4 个参数,其中后 2 个参数都设有默认值。大多数场景中,我们只需要设定前 2 个参数的值,有些场景可能会用到第 3 个参数,但最后一个参数几乎不会用到。
std::map<std::string, int>myMap;空
std::map<std::string, int>myMap{ {"a",10},{"b",20} };包含有 2 个键值对
std::map<std::string, int>myMap{std::make_pair("a",10),std::make_pair("b",20)};
std::map<std::string, int>newMap(myMap);拷贝构造
std::map<std::string, int>newMap(disMap());移动构造,实参是右值引用
std::map<std::string, int>newMap(myMap.begin(), myMap.end());基于范围的拷贝构造
2.2 迭代器指向pair
map 容器配备的是双向迭代器(bidirectional iterator)。这意味着,map 容器迭代器只能进行 ++p、p++、–p、p–、*p 操作,并且迭代器之间只能使用 == 或者 != 运算符进行比较。
2.3 怎么获取pair->second?即键所对应的值
2.3.1 直接读取键值对的值
- 使用
[]
运算符,既可读取(若找不到键值对会添加默认值),也可修改(覆盖),和增加; - 使用at()成员函数,只能读取,若找不到键值对,则抛出out_of_range 异常
std::map<string, int>myMap;空 map 容器
int value = myMap["a"];空map,肯定找不到,所以会添加默认值<"a",0>
myMap["b"] = 32;如果map中没有键"b",则会添加<"b",32>,若存在"b",则会修改
int bvalue = myMap.at("b");若找不到键"b"则会抛出异常
2.3.2 间接读取,借助迭代器来获取值
使用find()成员函数,找到则返回迭代器指向pair,找不到则返回指向end的迭代器
if((auto iter = myMap.find("a")) != myMap.end())如果不等于end说明找到了
cout << iter->first << " " << iter->second << endl;
2.4 插入数据的方式
除了运算符[]
可以插入数据,还可以使用insert()
成员函数,有4种用法:
- insert()不指定插入位置
std::pair<string, string> STL = { "a","123" };创建一个真实存在的键值对变量
auto ret = mymap.insert(STL);
auto ret = mymap.insert({ "b","456" });以右值引用的方式传递临时的键值对变量
返回值的含义:
如果插入成功( key 无重复),其返回的 pair 对象(pair<iterator,bool>)中包含一个指向新插入key的迭代器和值为 1 的 bool 变量
如果插入失败( key 重复了),会返回一个 pair 对象(pair<iterator,bool>),其中包含一个指向已存在的 key 的迭代器和值为 0 的 bool 变量。
- insert(),并且指定插入位置
std::pair<string, string> STL = { "a","123" };
std::map<string, string>::iterator it = mymap.begin();
auto iter1 = mymap.insert(it, STL);向 it 位置以普通引用的方式插入 STL
std::map<string, string>::iterator it = mymap.begin();
auto iter2 = mymap.insert({ "b","456" });
返回值的含义:
如果插入成功( key 无重复),insert() 方法会返回一个指向 map 容器中已插入键值对的迭代器;
如果插入失败( key 重复了),insert() 方法同样会返回一个迭代器,该迭代器指向 map 容器中和 val 具有相同键的那个键值对。
- 一次性插入多个pair对象
template <class InputIterator> void insert (InputIterator first, InputIterator last);基于map范围的插入
void insert ({val1, val2, ...});列举多个pair的插入
- 比insert()效率更好的emplace()
实现相同的插入操作,无论是用 emplace() 还是 emplace_hont(),都比 insert() 方法的效率高
auto ret = myMap.emplace("a", "123");
返回值也是一个 pair 对象,其中 pair.first 为一个迭代器(指向新插入的pair或已重复的pair),pair.second 为一个 bool 类型变量
iter = myMap.emplace_hint(myMap.begin(), "b", "456");
返回迭代器,指向新插入的pair或已重复的pair
2.5 删除数据
erase();传入迭代器可以删除指定pair,也可以基于范围删除pair,返回删除的个数
clear();可以一次性删除所有
3 multimap 容器
#include <map>
multimap 容器具有和 map 相同的特性:会对 key 排序
区别在于:
multimap 容器中可以同时存储多(≥2)个键相同的键值对。
Q:同名KEY怎么索引?
A:只能通过迭代器!不能用 at() 成员方法,也没有重载 [] 运算符。
因为 multimap 容器中指定的键可能对应多个键值对,而不再是 1 个。
3.1 初始化
空
- std::multimap<std::string, std::string>mymultimap;
带参数初始化
- multimap<string, string>mymultimap{ {"a", "1"},{"b", "2"},{"c", "3"} };
- multimap<string, string>mymultimap{ pair<string,string>{"a", "1"},pair<string,string>{"b", "2"},pair<string,string>{"c", "3"} };
- multimap<string, string>mymultimap{ make_pair("a", "1"),make_pair("b", "2"),make_pair("c", "3") };
拷贝构造
- multimap<string, string>newmultimap(mymultimap);
右值引用构造
- multimap<string, string>newmultimap(dismultimap());
基于范围的拷贝构造
- multimap<string, string>newmultimap(mymultimap.begin(), mymultimap.end());
3.2 读取、遍历操作
multimap支持多个1个key对应多个value,它内部的结构类似于:
{{1,a},{1,b},{1,c},{2,d},{3,e},{3,f}}
这些同key键值对是并列存在的,对于key=1的pair有多少个,使用count(key)可以得到,利用它来作为迭代器的偏移量。
- count(key)
读取或遍历multimap时需要配合成员函数,查找键为 key 的键值对的个数并返回。 - lower_bound(key)
返回一个指向当前 multimap 容器中第一个大于或等于 key 的键值对的双向迭代器。 - upper_bound(key)
返回一个指向当前 multimap 容器中第一个大于 key 的键值对的迭代器。 - equal_range(key)
该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对。
3.3 删除、插入、修改
multimap与map的方法一样,需要用到迭代器
4 set 容器
#include <set>
set译名为集合,表明了没有重复的内容。例如集合{a,b,c},包含了3个元素。我们常常使用set来去重~
小知识:从set的实现角度来看,set的元素也是pair类型的,只不过key与value是相等的而已。
4.1 初始化
std::set<std::string> myset;
std::set<std::string> myset{1,2,3};
std::set<std::string> copyset(myset);
std::set<std::string> copyset(retSet());
std::set<std::string> copyset(myset.begin(), myset.end());
4.2 插入元素
- insert()不指定插入位置
string str = "abc";
auto retpair = myset.insert(str);普通引用传值
auto retpair = myset.insert("123");右值引用传值
返回值的含义:
如果插入成功( key 无重复),其返回的 pair 对象中包含一个指向新插入key的迭代器和值为 1 的 bool 变量。
如果插入失败( key 重复了),会返回一个 pair 对象(pair<iterator,bool>),其中包含一个指向 key 的迭代器和值为 0 的 bool 变量。
- insert()指定了插入位置
auto iter = myset.insert(myset.begin(),str);普通引用传值
auto iter = myset.insert(myset.end(),"123");右值引用传值
返回值的含义:
如果插入成功( key 无重复),insert() 方法会返回一个指向 set 容器中已插入元素的迭代器;
如果插入失败( key 重复了),insert() 方法同样会返回一个迭代器,该迭代器指向 set 容器中和 str 具有相同键的那个元素。
- 一次性插入多个pair对象
void insert(InputIterator first, InputIterator last);插入其它 set 容器指定区域内的所有元素
void insert ({val1, val2, ...});列举多个元素的插入
-
用性能更好的emplace()取代insert()
auto ret = myset.emplace("124");
该方法的返回值类型为 pair 类型,其包含 2 个元素,一个迭代器和一个 bool 值,迭代器指向新添加元素(或已存在的旧元素)set<string>::iterator iter = myset.emplace_hint(myset.begin(), "123");
该方法需要额外传入一个迭代器,用来指明新元素添加到 set 容器的具体位置(新元素会添加到该迭代器指向元素的前面);返回值是一个迭代器,而不再是 pair 对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向 set 容器和要添加元素的值相同的元素。
4.3 删除数据
set没有修改元素的功能,一般是先删除,再添加。
//删除 set 容器中值为 val 的元素,返回成功删除的个数
size_type erase (const value_type& val);
//删除 position 迭代器指向的元素,返回后续元素的迭代器
iterator erase (const_iterator position);
//删除 [first,last) 区间内的所有元素,返回后续元素的迭代器
iterator erase (const_iterator first, const_iterator last);
//删除全部
void clear();
5 multiset 容器
- 与set的共同点在于:
元素只需要给定value即可,
默认做升序排序,
元素不可以修改(会破坏结构)若要修改一般是先删除再插入。 - 和 set 容器不同的是:
multiset 容器可以存储多个value相同的元素,那么它的查找、遍历、删除方式都要依赖count(value),这一点与multimap相同。