Hello 大家好,今天,我们来学习STL的一种容器map,在学习map之前,我们先来了解一下容器的两种类型
1. 序列式容器和关联式容器
前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。map和set底层是红黑树,红黑树是一颗平衡二叉搜索树(二叉搜索树的进阶,左右子树高度之差的绝对值不超过1)。set是key搜索场景的结构,map是key/value搜索场景的结构。
2.map和multimap参考文档
https://legacy.cplusplus.com/reference/map/
map不允许存在冗余,multimap允许存在冗余
3.map类的介绍
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,map默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是O(logN) ,迭代器遍历是走的中序,所以是按key有序顺序遍历的。
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;
4.pair类型介绍
map底层的红黑树节点中的数据,使用pair<Key, T>存储键值对数据。
typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
//pair()初始化
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
//pair拷贝构造
template<class U, class V>
pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
{}
};
//内联函数
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}
5.map的构造
map的构造我们关注以下几个接口即可。
map的支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
// empty (1) 无参默认构造
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
// copy (3) 拷贝构造
map (const map& x);
// initializer list (5) initializer 列表构造
map (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是一个双向迭代器
iterator -> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
#include<iostream>
#include<map>
using namespace std;
int main()
{
//empty (1) 无参默认构造
map<string, string> first;
//initializer list (5) initializer 列表构造
//使用pair<Key, T>存储键值对数据。
//按key有序顺序遍历,字符串升序
map<string, string> second = { {"string","字符串"},{"sort","排序"},{"array","数组"} };
// copy (3) 拷贝构造
map<string, string> third(second);
// range (2) 迭代器区间构造
// 正向迭代器
map<string, string> fourth(second.begin(), --second.end());
// 反向迭代器
map<string, string> fifth(second.rbegin(), --second.rend());
return 0;
}
6.map的增删查
map的增删查关注以下几个接口即可:
map增接口,插入的pair键值对数据,跟set有所不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代器还可以修改value
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 单个数据插入,如果已经key存在则插入失败,key存在相等value不相等也会插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回大于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回大于k位置的迭代器
iterator upper_bound (const key_type& k);
6.1 insert插入
// 单个数据插入,如果已经key存在则插入失败,key存在相等value不相等也会插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
// C++98
map<string, string> dict;
pair<string, string> kv1("sort", "排序");
// 单个数据插入,如果已经key存在则插入失败,key存在相等value不相等也会插入失败
dict.insert(kv1);
dict.insert(pair<string, string>("string", "字符串"));
//内联函数
/* template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y)
{
return (pair<T1, T2>(x, y));
}*/
dict.insert(make_pair("pair", "一对"));
// C++11
//C++11支持多参数隐式类型转换
dict.insert({ "left", "左边"});
auto ret= dict.insert({ "left", "左边" });
if (ret.second == false)
cout << "插入失败" << endl;
auto ret1 = dict.insert({ "left", "xxx" });
if (ret1.second == false)
cout << "插入失败" << endl;
// 列表插入,已经在容器中存在的值不会插入
dict.insert({ {"string","字符串"},{"sort","排序"},{"array","数组"} });
auto it = dict.begin();
while (it != dict.end())
{
cout << (*it).first << " " << (*it).second;
cout << " ";
it++;
}
cout << endl;
// 迭代器区间插入,已经在容器中存在的值不会插入
map<string, string> m= { {"string","字符串"},{"sort","排序"},{"array","数组"} };
m.insert(dict.begin(), dict.end());
auto it1 = m.begin();
while (it1 != m.end())
{
cout << (*it1).first << " " << (*it1).second;
cout << " ";
it1++;
}
cout << endl;
return 0;
}
//补充
//在map中访问元素习惯使用->,更方便
map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
//cout << (*it).first <<" "<< (*it).second << endl;
cout << it->first << " " << it->second << endl;
//实际上有两个箭头,为了便于观看及书写,编译器简化成一个
//第一个是重载的箭头,重载后指向迭代器中pair的指针
//第二个箭头是pair指针指向pair中的成员
//cout << it.operator->()->first << " " << it.operator->()->second << endl;
++it;
}
6.2 erase删除
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k不存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, string> m = { {"string","字符串"},{"sort","排序"},{"array","数组"},{"insert","插入"}};
map<string, string> m1 = { {"string","字符串"},{"sort","排序"},{"array","数组"},{"insert","插入"} };
map<string, string> m2 = { {"string","字符串"},{"sort","排序"},{"array","数组"},{"insert","插入"} };
map<string, string> m3 = { {"string","字符串"},{"sort","排序"},{"array","数组"},{"insert","插入"} };
auto it3 = m3.begin();
while (it3 != m3.end())
{
cout << it3->first << " " << it3->second;
cout << " ";
it3++;
}
cout << endl;
// 删除一个迭代器位置的值
m.erase(m.begin());
auto it = m.begin();
while (it != m.end())
{
cout << it->first << " " << it->second;
cout << " ";
it++;
}
cout << endl;
// 删除k,k不存在返回0,存在返回1
size_t ret=m1.erase("sort");
cout << ret << endl;
auto it1 = m1.begin();
while (it1 != m1.end())
{
cout << it1->first << " " << it1->second;
cout << " ";
it1++;
}
cout << endl;
// 删除一段迭代器区间的值
m2.erase(++m2.begin(), --m2.end());
auto it2 = m2.begin();
while (it2 != m2.end())
{
cout << it2->first << " " << it2->second;
cout << " ";
it2++;
}
cout << endl;
}
6.3 查找
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 返回大于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回大于k位置的迭代器
iterator upper_bound (const key_type& k);
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, string> m = { {"string","字符串"},{"sort","排序"},{"array","数组"},{"insert","插入"} };
// 查找k,返回k所在的迭代器,没有找到返回end()
auto it = m.find("string");
auto it1 = m.find("xxx");
// 查找k,返回k的个数
multimap<string, string> m1 = { {"string","字符串"},{"string","字符串"},{"string","字符串"},{"string","字符串"},
{"sort","排序"},{"array","数组"},{"insert","插入"} };
size_t n = m1.count("string");
map<char, int> m2 = { {'a',10},{'b',20},{'c',30},{'d',40},{'e',50},{'f',60} };
// 返回大于等k位置的迭代器
auto itlow = m2.lower_bound('b');
// 返回大于k位置的迭代器
auto itup = m2.upper_bound('e');
return 0;
}
7.map的数据修改
前面提到map支持修改mapped_type 数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
map第一个支持修改的方式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口
需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型,typedef为mapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value。
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的mapped_type值
iterator find (const key_type& k);
operator[]函数
operator[]函数有两个功能,插入和修改
如果k在容器中,operator[]返回k对应的value
如果k不在容器中,operator[]则进行插入操作(相当于insert)插入k,返回k对应的value
// insert插入一个pair<key, T>对象
1、如果key已经在map中,插入失败,则返回一个pair<iterator,bool>对象,返回pair对象
first是key所在结点的迭代器,second是false
2、如果key不在在map中,插入成功,则返回一个pair<iterator,bool>对象,返回pair对象
first是新插入key所在结点的迭代器,second是true
也就是说无论插入成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
代器
那么也就意味着insert插入失败时充当了查找的功能,正是因为这一点,insert可以用来实现
operator[]
需要注意的是这里有两个pair,不要混淆了,一个是map底层红黑树节点中存的pair<key, T>,另
一个是insert返回值pair<iterator,bool>
我们通过下面代码来观察operator[]函数的这两个功能
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
// 利用[]插入+修改功能,巧妙实现统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
map<string, int> countMap;
for (const auto& str : arr)
{
// []先查找水果在不在map中
// 1、不在,说明水果第一次出现,则插入{水果, 0},同时返回次数的引用,++一下就变成1次了
// 2、在,则返回水果对应的次数++
countMap[str]++;
}
for (const auto& e : countMap)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
// key不存在->插入 {"insert", string()}
dict["insert"];
// 插入+修改
dict["left"] = "左边";
// 修改
dict["left"] = "左边、剩余";
// key存在->查找
cout << dict["left"] << endl;
return 0;
}
8.multimap和map的差异
multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。
到此,map的插入、删除、查找就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。
如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路
如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正
博语小屋将持续为您推出文章