C++知识点30——使用C++标准库(关联容器map及其初始化,赋值,查找,添加,删除与迭代器失效)

一.关联容器简介

关于顺序容器和关联容器的区别已经在博客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时,那么就不能将该算法用于关联容器上

二、元素对key-value的表示

map,multimap,unordered_map以及unordered_multimap都用pair数据结构来表示容器中的元素,使用pair时,要添加头文件#include<utility>

pair的常用操作如下

示例

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/

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值