Map和Multimap(下文统称Map)将key/value作为元素进行管理,逻辑上是一种键值映射关系,即数据结构中哈希表。它们可以根据key的排序规则进行自动元素排序,Multimap允许元素重复,而Map不允许。
在使用Map和Multimap之前, 必须引入头文件map
#include
在其中,map和multimap在std中被定义为:
template<
template Key,
template T,
template Compare = std::less<Key>,
template Allocator = std::allocator<std::pair<const Key, T> >
> class map;
template<
template Key,
template T,
template Compare = std::less<Key>,
template Allocator = std::allocator<std::pair<const Key, T> >
> class multimap;
其中,第一个template,指定了key的类型,第二个template,指定了value的类型,key和value必须满足以下条件——
1. Key和value必须是可复制(copyable)的和可移动的(movable,移动语义);
2. 对于指定的排序准则而言,key必须是可比较的(comparable)。
第三个template,用来指定排序准则。这点与Set相同,排序遵循strict weak ordering准则,如果没有指定,则默认为less<>
排序准则。
第四个template则是指定内存模型,默认采用allocator,由C++标准库提供。
举个例子,我们使用multimap定义一个
multimap<int, string> mmp{
pair<int,string>(1,"jack"),
pair<int,string>(2,"jason"),
pair<int,string>(2,"joe"),
pair<int,string>(3,"stack"),
{5,"test" },{5,"test"},{5,"test"}
};
for (auto elem : mmp) {
cout << elem.first << " " << elem.second << endl;
}
multimap的初始化必须是成双成对,可以用std::pair,或者直接使用大括号。然后你会发现,multimap会根据key值自动进行排序。
和所有的关联式容器一样,Map和Multimap的内部是以平衡二叉树来实现的,Map和Set具有很多相似之处,Map具有和Set几乎相同的能力和操作,不同的是,其内部管理的元素是pair
Map会根据元素的Key进行排序,如果已知元素的Key来查找元素,效率相当卓越。但如果根据value进行查找,效率将大打折扣。与Set相同,自动排序的规则,限制了其不能改变容器内的元素的Key(但是value可以)——
1. 修改元素的key,需要先移除,再插入;
2. 从迭代器的角度而言,Key是常量,无法通过迭代器修改Key;
3. value不作为排序准则,不会影响既定顺序,因此非常量value可以直接修改。
Map的操作函数满足通用STL容器标准,同时,Map也具有一定自己的特色——
map的构造函数和析构函数,如下表所示——
序号 | 操作 | 效果 |
---|---|---|
1 | map c | Default构造函数,产生一个空map,没有任何元素 |
2 | map c |
建立一个空的map,并以op为排序规则 |
3 | map c(c2) map c=c2 |
Copy构造函数,建立c2同型map并成为c2的一份副本,该复制是深度复制 |
4 | map c(rv) map c=rv |
Move构造函数,rv是一个map右值引用,那么这里的构造函数是一个Move构造函数,建立一个新的map,取右值内容(C++11新特性) |
5 | map c(beg,end) | 建立一个map,并以迭代器所指向的区间[beg,end)作为元素值,该构造函数支持从其他容器类型接收元素 |
6 | map c(beg,end,op) | 建立一个map,并以迭代器所指向的区间[beg,end)作为元素值,同时以op作为排序准则 |
7 | map c(initlist) map c=initlist |
建立一个map,以初值列initlist元素为初值(C++11新特性) |
8 | c.~map() | 销毁所有元素,释放内存 |
其中,上表中map的形式可以置换为以下任意一种——
序号 | map | 效果 |
---|---|---|
1 | map |
使用默认排序准则,以Key-Val作为元素类型的map |
2 | map |
使用op作为排序准则,以Key-Val作为元素类型的map |
3 | multimap |
使用默认排序规则,以Key-Val作为元素类型的multimap |
4 | multimap |
使用op作为排序准则,以Key-Val作为元素类型的multimap |
Map也提供元素比较、查询大小等操作——
序号 | 操作 | 效果 |
---|---|---|
1 | c.empty() | 容器为空返回true,不为空返回false,相当于size()==0 |
2 | c.size() | 返回当前元素的个数 |
3 | c.max_size() | 返回元素个数之最大可能量 |
4 | c1==c2 | 对每个元素调用c1==c2,全部相等返回true |
5 | c1!=c2 | 只要有一个元素相等,返回true,相当于!(c1==c2) |
6 | c1>c2,c1>=c2,c1同上,依次类推 |
|
7 | c.key_comp() | 返回比较准则 |
8 | c.value_comp() | 返回针对value的比较准则 |
map的元素比较只适用于类型相同的元素,这里的类型相同包括Key类型,Value类型和排序准则,这3个类型只要有一个不相同,就无法进行比较,否则会编译错误,比如——
map<int, float> m1;
map<int, int> m2;
if (m1 == m2) {
}
Map在元素查找方面与Set完全相同——
序号 | map | 效果 |
---|---|---|
1 | c.count(val) | 返回Key为val的个数 |
2 | c.find(val) | 返回Key为val的第一个位置,注意这里返回的是一个迭代器 |
3 | c.lower_bound(val) | 返回Key为val第一个可插入的位置,即第一个元素值<=val的位置 |
4 | c.upper_bound(val) | 返回Key为val最后一个可插入的位置,即第一个元素值 |
5 | c.equal_range(val) | 返回Key为val可以被插入的第一个位置和最后一个位置的范围 |
值得一提的是,Map所有的查找函数都是基于Key的操作,如果想根据value进行操作,则会麻烦很多。比如使用find函数进行value的查找,只能进行遍历进行查找——
multimap<int, string> mmp{
pair<int,string>(1,"jack"),
pair<int,string>(2,"jason"),
pair<int,string>(2,"joe"),
pair<int,string>(3,"stack"),
{5,"test" },{5,"test"},{5,"jason"}
};
for (auto elem : mmp) {
if (elem.second == "jason") {
cout << "Key = " << elem.first << endl;
}
}
Map只提供任何容器都提供的基本赋值操作:
序号 | 操作 | 效果 |
---|---|---|
1 | c = c2 | 将c2的全部元素赋值给c |
2 | c = rv | 将rv右值语义所有元素以Move assign的方式赋值给c(C++11新特性) |
3 | c = initlist | 将初值列所有元素赋值给c(C++11新特性) |
4 | c1.swap(c2) swap(c1,c2) |
置换c1和c2的数据 |
同样,赋值操作必须满足两端容器具有相同的类型,包括Key、Value和比较准则。
Map和Multimap不提供元素访问,所以只能采用range-based for循环,迭代器的使用就尤为常见,尤其注意的是,在使用迭代器的时候,Key是作为一种常量处理,因此,这意味着,无法通过迭代器更改Key,这也保证了排序的顺序:
序号 | 操作 | 效果 |
---|---|---|
1 | c.begin() | 返回一个bidirectional iterator指向第一个元素 |
2 | c.end() | 返回一个bidirectional iterator指向的之后一个元素 |
3 | c.cbegin() | 返回一个const bidirectional iterator指向的第一个元素(C++11新特性) |
4 | c.cend() | 返回一个const bidirectional iterator指向的最后一个元素(C++11新特性) |
5 | c.rbegin() | 返回一个反向迭代器(reverse iterator)指向的第一个元素 |
6 | c.rend() | 返回一个reverse iterator指向的最后一个元素 |
7 | c.crbegin() | 返回一个const reverse iterator指向的第一个元素(C++新特性) |
8 | c.crend() | 返回一个const reverse iterator指向的最后一个元素(C++11新特性) |
Map的所有插入和移除操作如下表所示——。
序号 | 操作 | 效果 |
---|---|---|
1 | c.insert(val) | 插入val元素,并返回新元素的位置,不论是否成功 |
2 | c.insert(pos,val) | 在iterator指向的pos位置的前方插入一个元素val的副本,并返回新元素的位置(pos只是一个提示,该提示恰当会加快查找过程) |
3 | c.insert(beg,end) | 插入区间[beg,end)内所有元素的副本,无返回值 |
4 | c.insert(initilist) | 插入initilist的所有元素的副本,无返回值(C++11新特性) |
5 | c.emplace(args…) | 插入一个以args为初值的元素,并返回新元素的位置,无论是否成功(C++11新特性) |
6 | c.emplace_hint(pos,args…) | 插入一个以args为初值的元素,并返回新元素的位置,pos是个位置提示,如果恰当,将大大提高插入效率 |
7 | c.erase(val) | 移除与val相等的所有元素,返回被移除的个数 |
8 | c.erase(pos) | 移除迭代器iterator指向的元素,无返回值 |
9 | c.erase(beg,end) | 移除区间[beg,end)内的所有元素,无返回值 |
10 | c.clear() | 移除所有元素,并将容器清空 |
注意,Map的所有插入操作都是基于pari的,即必须成双成对——
multimap<int, string> mmp{
pair<int,string>(1,"jack"),
pair<int,string>(2,"jason"),
pair<int,string>(2,"joe"),
pair<int,string>(3,"stack"),
{5,"test" },{5,"test"},{5,"jason"}
};
mmp.insert(pair<int,string>(10,"hello"));
for (multimap<int, string>::iterator it = mmp.begin(); it != mmp.end();it++) {
cout << it->first << "," << it->second << endl;
}
就异常处理而言,Map具有Set完全相同的规则,即对于单一元素的操作,遵循“要么成功,要么没有任何作用”的原则,对于多元素插入操作,在保证排序准则不抛出异常的情况下,同样遵循这一点。
作为关联容器,Map具有Set几乎完全相同的接口和函数名,甚至使用场景和方式都一模一样。最大的不同是Map是一种key-value关系,而Set只是一种value集合,从这种角度来看,Set可以看做是Map中key=value的特殊情况。
Map的使用场景比Set更广泛,map可以作为一种索引而使用,multiset作为数据字典而使用,两者都是为了提高查询效率而存在,毕竟关联容器内部都是以平衡二叉树实现,先天决定了其独一无二的高效查询特性。