关于顺序容器和关联容器的区别已经在博客https://blog.csdn.net/Master_Cui/article/details/107427911中提过
C++标准库中的关联容器一共有八个,分别是map,multimap,set,multiset,unordered_map,unordered_set,unordered_multimap,unordered_multiset
其中,前四个是有序关联容器,简称关联容器,map,multimap,set,multiset默认情况下对key以<的方式自动排序,所以因为这种性质,可以通过key快速锁定要查找的value,复杂度为O(lgN)
因为key的自动排序,所以这四个容器中的元素是有序的,key的排序方式依然得是严格弱序的
因为关联容器的对象都是已经根据既有的key排好序的,所以,不能直接修改关联容器中的key,因为这样会破坏容器现有的排序,所以关联容器的key是const的
所以,如果想修改一个元素的key,必须先删除该key-value对,然后再插入一个新的key-value对
map和multimap的区别在于map的key不能重复,而multimap的key可以重复,set和multiset的区别也在于key是否可以重复
map和set的主要区别在于map中的元素是一对key-value,而set中的元素key-value都是同一类型的值,key就是value,value也是key,所以set是一种特殊的map
关联容器一般不和泛型算法一起使用,因为关联容器的key是const的,所以如果一个泛型算法可能修改关联容器的key时,那么就不能将该算法用于关联容器上
map,multimap,unordered_map以及unordered_multimap都用pair数据结构来表示容器中的元素,使用pair时,要添加头文件#include<utility>
void pairtest()
{
pair<string, int> p1={"one", 1};
pair<string, int> p2={"two", 2};
pair<string, int> p3=make_pair("three", 3);
cout<<p1.first<<p1.second<<endl;
}
三、map的常用操作
1.map的声明与初始化
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > // map::allocator_type
> class map;
map被声明为一个模板类,前两个分别是map的key和value,分别用模板参数Key和T表示,第三个模板参数规定了key的排序方式,默认是<,第四个模板参数Alloc也是默认,作用是提供类型化的内存分配以及对象的分配和撤销
map();//默认构造函数
explicit map (const key_compare& comp, const allocator_type& alloc = allocator_type());//指定key的比较方式的构造函数
explicit map (const allocator_type& alloc);
//通过迭代器初始化的普通构造函数
template <class InputIterator>
map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type());
template <class InputIterator>
map (InputIterator first, InputIterator last, const allocator_type& = allocator_type());
//拷贝构造
map (const map& x);
map (const map& x, const allocator_type& alloc);
//列表初始化
map (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
map (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());
初始化方式和顺序容器类似
示例
void inittest()
{
map<string, int> m1;
m1["one"]=1;
m1["two"]=2;
for (map<string, int>::iterator it = m1.begin();
it!=m1.end();++it) {
cout<<it->first<<","<<it->second<<endl;
}
map<string, int, bool(*)(string, string)> m2(m1.begin(), m1.end(), comp);//指定函数对象的类型
for (map<string, int, bool(*)(string, string)>::iterator it = m2.begin();
it!=m2.end();++it) {
cout<<it->first<<","<<it->second<<endl;
}
map<string, int> m3(m1);//调用拷贝构造
map<string, int> m4=m3;
map<string, int> m5={{"three", 3}, {"four", 4}};//列表初始化
}
上述代码分别用了几种方式对map进行初始化,如果一个key不存在map中时,可以使用[]添加元素,此外,迭代器的使用也和顺序容器的使用方式类似
当使用函数指针对map的key重新指定排序方式时,输出结果按照string大于的方式进行排序
2.map的赋值
赋值操作和初始化操作类似,此外,可以用swap函数对map对象赋值,和顺序容器类似,如下图
示例可参照顺序容器
swap的函数声明如下
void swap (map& x);
注意
1.关联容器没有assign方法,所以不能用assign对关联容器对象赋值
2.赋值时,两个容器的key,value的类型必须相同,但是key的排序准则可同可不同
示例
bool comp(string k1, string k2) {return k1》k2;}
bool comp1(string k1, string k2) {return k1<k2;}
void mapassign()
{
map<string, int> m1;
m1["one"]=1;
m1["two"]=2;
map<string, int, bool(*)(string, string)> m2(
m1.begin(), m1.end(), comp);
map<string, int, bool(*)(string, string)> m3(
m1.begin(), m1.end(), comp1);
for (map<string, int, bool(*)(string, string)>::iterator it = m3.begin();
it!=m3.end();++it) {
cout<<it->first<<","<<it->second<<endl;
}
m3=m2;
for (map<string, int, bool(*)(string, string)>::iterator it = m3.begin();
it!=m3.end();++it) {
cout<<it->first<<","<<it->second<<endl;
}
//m3=m1
}
当13行执行结束后,m3的key排序规则被m2的替换
虽然排序规则可以不同,但是第三个模板参数Compare的类型必须相同(也就是说,描述比较规则的函数类型必须相同)
3.map的查找
map容器的查找函数一共五个,每个的声明及重载形式如下
size_type count (const key_type& k) const;//计算map容器对象中指定key的元素的个数,对于map来说,非0就是1
iterator find (const key_type& k);//查找指定key的元素的所在位置,返回该元素的迭代器,如果没有根据key找到该元素,返回尾后迭代器end()
const_iterator find (const key_type& k) const;
iterator lower_bound (const key_type& k);//查找第一个key>=k的元素,返回该元素的迭代器,如果没有根据key找到该元素,返回尾后迭代器end()
const_iterator lower_bound (const key_type& k) const;
iterator upper_bound (const key_type& k);//查找第一个key>k的元素,返回该元素的迭代器,如果没有根据key找到该元素,返回尾后迭代器end()
const_iterator upper_bound (const key_type& k) const;
pair<const_iterator,const_iterator> equal_range (const key_type& k) const;//将lower_bound和upper_bound的迭代器作为一个pair返回
pair<iterator,iterator> equal_range (const key_type& k);
示例
void searchtest()
{
map<string, int> m1;
m1["one"]=1;
m1["two"]=2;
m1["three"]=3;
m1["four"]=4;
m1["five"]=5;
int res1=m1.count("five");
cout<<res1<<endl;
int res2=m1.count("six");
cout<<res2<<endl;
map<string, int>::iterator it=
m1.find("one");
cout<<(it!=m1.end())<<endl;
map<string, int>::iterator it1=
m1.find("zero");
cout<<(it1!=m1.end())<<endl;
map<string, int>::iterator it2=
m1.lower_bound("v");
if (it2!=m1.end()) {
cout<<it2->first<<it2->second<<endl;
}
map<string, int>::iterator it3=
m1.upper_bound("v");
if (it2!=m1.end()) {
cout<<it3->first<<it3->second<<endl;
}
pair<map<string, int>::iterator, map<string, int>::iterator> res3 =
m1.equal_range("v");
if (res3.first!=m1.end() && res3.second!=m1.end()) {
cout<<res3.first->first<<res3.first->second<<
res3.second->first<<res3.second->second<<endl;
}
}
4.map的添加
除了使用[]添加元素外,还有insert方法
pair<iterator,bool> insert (const value_type& val);
iterator insert (const_iterator position, const value_type& val);
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
void insert (initializer_list<value_type> il);
其中,后两种用法和顺序容器类似
第一个insert声明的形参是一个键值对,可以用pair存储,可以用直接用大括号表示,返回值也是一个pair,first是一个迭代器,指向指定key的元素,bool表示该元素是否插入成功,如果关键字已经存在于map中,那么insert什么也不做,bool值为false,否则插入新的元素,bool值为true
第二个insert声明返回的是新插入元素的迭代器(双向迭代器)
map的operator[]除了可以插入元素,还可以更新元素,当key已经存在map对象中,调用operator[](key)会刷新key对应的值
示例
void inserttest()
{
map<string, int> m1;
m1["one"]=1;
m1["two"]=2;
pair<map<string, int>::iterator, bool> res1 =
m1.insert(make_pair("three", 3));
cout<<res1.second<<endl;
pair<map<string, int>::iterator, bool> res2 =
m1.insert({"three", 3});
cout<<res2.second<<endl;
map<string, int>::iterator it=
m1.insert(m1.end(), make_pair("four", 4));
for (map<string, int>::iterator it = m1.begin();
it!=m1.end();++it) {
cout<<it->first<<","<<it->second<<endl;
}
}
上述代码连续两次插入{"three", 3},第一次插入成功,第二次检测到key three已经存在map中,所以map对象什么也不做,返回值的second为false
其中第6行代码的返回值的类型如下pair<map<string, int>::iterator, bool>,pair的first是一个map对象的迭代器,map对象的key是string,value是int,pair的second是一个bool
在m1.end()插入("four", 4)后,该元素并没有出现在容器的末尾,因为插入后,map会根据key的排序规则重新对元素排序,所以这个接口设计得有些鸡肋
5.map的删除
iterator erase (const_iterator position);
size_type erase (const key_type& k);
iterator erase (const_iterator first, const_iterator last);
第一个根据迭代器指向的位置删除元素,迭代器指向的位置一般通过find来找到,返回被删除元素的下一个元素的迭代器(下一个元素也是根据key排序得到的),和顺序容器类似
第二个是根据key来删除指定的元素,返回删除元素的个数,对于map来说,非0即1
第三个是根据迭代器范围删除元素
示例
void erasetest()
{
map<string, int> m1;
m1["one"]=1;
m1["two"]=2;
m1["three"]=3;
m1["four"]=4;
for (map<string, int>::iterator it=m1.begin();
it!=m1.end();++it) {
cout<<it->first<<it->second<<endl;
}
int num = m1.erase("fout");//key不存在,没有删除元素,返回0
cout<<num<<endl;
map<string, int>::iterator it1=m1.find("four");//删除key为"four"的元素
map<string, int>::iterator it2=m1.erase(it1);//删除并返回下一个元素的迭代器
if (it2!=m1.end()) {
cout<<it2->first<<it2->second<<endl;
}
}
6.map的下标操作
mapped_type& at (const key_type& k);//返回key所对应的value,如果key不存在,则抛出一个异常
const mapped_type& at (const key_type& k) const;
mapped_type& operator[] (const key_type& k);
map的下标操作和vector,string,deque一样,既可以使用[],也可以使用at
但是,map的这两种下标操作还是和顺序容器的下标操作有区别:
1.顺序容器的下标操作直接返回的是容器中的元素,而map的下标操作返回的是key所对应的value,并不是一个键值对
2.顺容容器在使用[]下标操作,如果访问了一个不存在的下标,会非法访问内存,程序退出,而map使用[]下标操作时,如果key下标不存在,会自动创建并添加一个新的元素(键值对),正因为下标操作有可能添加新的元素,所以只能对非const的map对象进行下标操作
正因为map的下标操作的第二个特性,所以如果想查找某个key所对应的value,应该使用find,而不应该使用下标操作,因为下标操作会插入新的元素
示例参考顺序容器vector和上面的代码
7.map的一般操作
map容器也有向size,empty和clear这种的一般操作,同顺序容器
8.map与迭代器失效
map的迭代器失效的情况相比较顺序容器简单很多,因为map内部结构是树形结构,也是基于节点的,类似于list,所以不会出现内存失效的情况,此外insert返回的迭代器是新插入的迭代器,而且插入后,map容器也会重新排序,所以insert并不会导致迭代器失效
但是,erase会导致迭代器失效,因为erase执行删除操作后,被删除元素的迭代器会失效
示例
void iteratorfailed()
{
map<string, int> m1;
m1["one"]=1;
m1["two"]=2;
map<string, int>::iterator it=m1.find("one");
m1.insert(it, make_pair("three", 3));
cout<<it->first<<it->second<<endl;
m1.erase(it);
cout<<it->first<<it->second<<endl;
}
insert之后,迭代器it依然有效,指向one所随影的元素,但是删除后,指向被删除元素的迭代器失效了,所以,不要保存被删除元素的迭代器
参考
《C++ Primer》
《C++标准库》
http://www.cplusplus.com/reference/map/
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出