我们经常使用过顺序容器如vector,list,queue,deque等,顺序容器是按照元素的插入顺序来存储元素;关联式容器如map,set,multimap,multiset等,这些容器是按照元素的关键字来保存的。
下图是《c++Primer》中的介绍:
map和set的底层实现的数据结构:—红黑树
所以
map
map中存放的元素实pair,同时用有键值(Key)和实值(value);map实根据键值(key)排序的,map中不允许有重复的键值。
pair简介:
pair是一种模板类型,每个pair可以存放两个值;
当我们的函数需要返回多个数据的时候,我们可以把函数返回类型设置为结构体,把要返回的数据放在结构体中。pair的实现就是一个结构体;pair的两个成员变量是first和second。
pair<int, int> p(1, 1);//生成pair的两种方式
p = make_pair(1, 1);
cout << p.first << " " << p.second;
map的定义和初始化
map<k,v> m1;
map<k,v> m2(m1);
map<k,v> m3(a,b);
map的常用接口函数:
1.insert
先解释写下几种类型:
我们常用的插入方法:
pair<iterator,bool> insert (const value_type& val);
参数:map的每个元素都是pair类型,所以insert()的参数也是pair;
返回类型: ;返回值是一个pair,第一个元素是迭代器,第二个元素是个bool变量;如插入成功,迭代器指向该元素的位置,bool值为true;若插入失败,意思就是map中已经有这个要插入的值了,此时迭代器指向原来已有那个元素的位置;bool为false;
例子:
map<int, char> m;
pair<map<int, char>::iterator, bool> p;
p = m.insert(pair<int, char>(1, 'a'));
//p = m.insert(make_pair(1, 'a'));此种方法与上调语句等价
if (p.second == true)
{
cout << "插入成功1-->a" << endl;
}
p = m.insert(pair<int, char>(2, 'b'));
if (p.second == true)
{
cout << "插入成功2-->b" << endl;
}
p = m.insert(pair<int, char>(1, 'b'));
if (p.second == false)
{
cout << "插入失败,已有该值" << endl;
}
此外,insert还可以在指定位置插入:
iterator insert (iterator position, const value_type& val);
我们发现,我们在第一个位置插入(10,‘j’);结果却在最后!这是因为map会按照键值(key)排序,默认升序。
2**operator[]**
mapped_type& operator[] (const key_type& k);
所以我们可以知道,operator[]返回的是map的实值(value)类型;参数类型是键值(key)的类型。
注意:当下标访问一个不存在的元素时,他会自动向map中添加一个新元素。(可以说有初始化的功能)。
而当我们访问已经存在的元素,并且给他赋值的时候,会更新该元素的实值(value)。
例如:
map<int, char> m;
m[0] = 'a';//访问的元素不存在
m[1] = 'b';
m[2] = 'c';
m[1] = 'x';//方位的元素存在
cout << m[0] << endl;
cout << m[1] << endl;
cout << m[2] << endl;
此时:我们或许会问,operator [ ]究竟做了什么?我们来理解一条语句,也就是operator[]的实质:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
其实,分了两步:
第一步:[]是调用了一次insert();insert的返回值是一个pair,第一个元素的迭代器,第二个是bool变量,执行了insert后,如果该元素存在,插入失败,但是迭代器指向该元素位置;如果不存在,插入成功,迭代器也指向这个元素的位置;(所以说,执行m[2]后,不管2以前有没有,现在都有了,并且迭代器都指向2这个位置)。所以,(inser().first)就是这个迭代器;
第二步:map的迭代器也是一个pairl类型map<int,char>::inerator it
, 他的第一个元素时键值(key),第二个元素是实值(value);所以我们(insert().first).second就访问到实值value了。
[]和insert的总结:
- map的插入:
方法一:
map <int,char> m;
m[1] = 'a';//此方法初始化了a[1],并给a[1]赋值;
m[1] = 'b';//此方法修改了a[1]的值;
因为[ ]被重载为,如果不存在该key的值,则创建该对象,所以,一下操作比较危险
方法二:
map<int,char> m;
m.insert(pair<int, char>(1, 'a'));//此初始化了a[1],并且给a[1]赋值;
m.insert(pair<int, char>(1, 'b'));//a[1]已经存在,此时既没有初始化a[1],也没有给a[1]赋值。
3.find()和count()
find和count都能判断键值(key)在不在map中,
count():size_type count (const key_type& k) const;
不能定位出键值(key)的位置,因为map是一对一的映射关系,所以count的返回值要么是0,表示不存在,要么是1,表示存在。
find():iterator find (const key_type& k);
用来定位键值(key)的位置,find(key)返回一个迭代器,如果数据存在,返回数据在该位置的迭代器,如果map中没有改数据,返回的迭代器等于end()返回的迭代器。
例子:
4.元素删除erase()
三种方式:
void erase (iterator position);//按照迭代器位置删除
size_type erase (const key_type& k);//按照键值(key)删除;删除成功返回1,删除失败返回0;
void erase (iterator first, iterator last);//按照迭代器区间删除
map<int, char> m;
m.insert(make_pair(1, 'a'));
m.insert(make_pair(2, 'b'));
m.insert(make_pair(3, 'c'));
m.insert(make_pair(4, 'd'));
int ret = m.erase(2);
if (ret == 1)
cout << "删除成功!";
else if (ret == 0)
cout << "删除失败!";
m.erase(m.find(3));
m.erase(m.begin(), m.end());
5.清空clear()与判空empty();
6.一些其他的接口:
- size() 返回map中元素的个数
- begin() 返回指向map头部的迭代器
- end() 返回指向map末尾的迭代器
- rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
unordered_map;
unordered_map的用法与map相似;unordered是c++11出现的;
与map的区别:
map 底层实现红黑树,会自动排序,所以map的元素是有序的,对map的操作等于岁红黑树节点的操作,所以他的时间复杂度大多为log N;
缺点:空间占用率较高,因为每个节点要保存3个指针,左右孩子节点指针,父节点指针;
unordered_map底层实现是哈希表;所以查找速度非常快,也因此unoedered_map的元素无序的;
缺点:查找速度虽高,但是哈希表的建立费时.
例子:
unordered_map<int, char> m;
m.insert(make_pair(1, 'a'));
m.insert(make_pair(2, 'b'));
m.insert(make_pair(3, 'c'));
m.insert(make_pair(4, 'd'));
m[5] = 'e';
m.erase(1);
for (auto& i : m)//遍历
cout << i.first << "--> " << i.second << endl;