欢迎来到博主博主的专栏:c++编程
博主ID:代码小豪
map
map和set都是关联式容器,与set不同的点在于,set是key型的搜索树,而map则是key_value型的搜索树,即元素会存储两个数据,一个是key值,一个value值,在树中按照key值排序,通过key值找到对应value值的元素
最常见的应用则是英语字典,英文单词为key值,中文翻译则是value值,我们在树中搜索key值,然后返回给我们value值。即英文翻译成中文。
在后续例子中我们通过map写一个简略版的中英双词词典。先来了解一下map的作用
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
在set当中只需要传入模板参数T即可实例化,而map则需要传入模板参数key和T才可实例化。记住下面的类型别名,这在下面会经常使用
类型别名 | 说明 |
---|---|
key_type | 模板参数key,即key值的类型 |
map_type | 模板参数T,即value值的类型 |
value_type | 是key值和value值的组合(pair<key,value>) |
这里不得不说说map的元素类型了,由于map是key-value型的搜索树,因此函数的返回类型不能单单是key或者value,因为用户有时候需要使用节点的key值,有时需要使用节点的value值,因此STL采取了另外一个策略,使用组合pair来作为节点的类型。
map会根据key值,对元素进行排序、去重,这个特性与set别无二致。map最主要的区别就是节点多存储了一个value。
pair
关于pair的具体说明,博主打算放在其他文章当中,但是由于map与pair的关系密切,我们先粗略了解一下pair类型。
pair是一个结构体模板类型,在c++使用手册当中给出了pair类的模板参数
template <class T1, class T2> struct pair;
pair结构体就两个成员对象,一个叫做first,类型为T1,第二个叫做second,类型为T2。
default (1)
pair();
copy (2)
template<class U, class V> pair (const pair<U,V>& pr);
initialization (3)
pair (const first_type& a, const second_type& b);
初始化方法有三种,分别是默认构造,拷贝构造,和传值构造,默认构造和传值构造就不多赘述了,来看看传值构造的例子
pair<int, string>p1(1, "one");
cout << p1.first << endl;//1
cout << p1.second << endl;//one
pair的first是int类型的值,second是string类型的值,这就做到了用一个对象储存两个不同类型的元素,我们pair称为键值对。
在map当中,pair是节点元素的类型
constructor
empty (1)
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
explicit map (const allocator_type& alloc);
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);
map (const map& x, const allocator_type& alloc);
initializer list (5)
map (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
empty构造
构造一个空的map
map<string, string>dirc;//空词典
range构造
以迭代区间[first,second)元素构造一个map对象。
vector<pair<string, string>> v1{ {"string","字符串"},{"code","代码"}};
map<string, string> m1(v1.begin(), v1.end());
代码说明,v1是的元素是pair<string,string>类型,通过range构造将v1[begin(),end())区间内的所有元素给予map来构造初始对象。所以此时m1的元素为:{“code”,“代码”},{string,“字符串”}。
要注意,map的元素是pair类型,这就要迭代区间的元素要尽可能符合pair(也不是不能用其他类型,而是会导致map的节点只有key值,没有value值,对于map这个数据结构来说,杀鸡焉用牛刀呢?)。
copy构造
拷贝另外一个map对象,其树状结构会与拷贝的map对象完全一致,是深拷贝。
vector<pair<string, string>> v1{ {"string","字符串"},{"code","代码"}};
map<string, string> m1(v1.begin(), v1.end());
map<string, string>m2(m1);//拷贝m1的数据
初始化列表构造
使用初始化列表(“{}”),传递待构造的数据。
map<string, string>m3({ {"string","字符串"},{"code","代码"} });
这里再次声明,map插入的数据多是pair类型的对象,当然也可以单值传递,但是效果不佳。
operator=
copy (1)
map& operator= (const map& x);
initializer list (3)
map& operator= (initializer_list<value_type> il);
赋值重载函数允许map对象使用赋值号(=),修改容器,这里不多赘述。
iterator
迭代器的原理在c++杂谈中有所提及,这里不多赘述,来关注一下map容器的迭代器的迭代原理吧
map的迭代器顺序符合红黑树中序遍历的顺序,因此遍历map得到的结果是容器内key值的升序顺序(通过修改comp可以修改map的对应顺序,这与set类似。)
iterator begin() noexcept;
const_iterator begin() const noexcept;
iterator end() noexcept;
const_iterator end() const noexcept;
比如我们尝试创建一个中英词典。
map<string, string>dirc({
{"byte","字节"}
,{ "abandom","放弃" }
, { "destory","摧毁"}
,{"construct","构造"}
});
map的迭代区间,是key值的排序(在这里,是按照英文单词的字典序进行排序)。因此dirc的迭代区间[begin,end)的遍历结果是。
“abandom”,“放弃”
“byte”,“字节”
“destory”,“摧毁”
“construct”,“构造”
iterator begin();
const_iterator begin() const;
begin()函数返回map对象迭代区间中的第一个元素。end()函数返回结束标志。
map的查找功能
map查找数据的速度远快于vector,list等序列式容器,序列式容器的查找操作的时间复杂度为O(N)。关联式容器的查找操作的时间复杂度是O(logN)。因此,当程序需要大量查找数据的需求时,选用map容器是一个不错的选择。
map的find函数能完成数据查找的功能。函数原型如下:
iterator find (const key_type& k);
const_iterator find (const key_type& k) const;
find函数会根据实参k查找map当中相同key值的节点,如果找到了对应key值的节点,返回该节点的迭代器,若是没有找到对应key值,则返回end()函数对应的迭代器。
map<string, string>dirc({
{"byte","字节"}
,{ "abandom","放弃" }
, { "destory","摧毁"}
,{"construct","构造"}
});
auto it = dirc.find("byte");
if (it!=dirc.end())
{
dirc.erase(it);//如果存在"byte"则删除对应节点
}
map的插入与删除
insert
single element (1)
pair<iterator,bool> insert (const value_type& val);
with hint (2)
iterator insert (iterator position, const value_type& val);
range (3)
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
single insert
向map容器中插入单个新值,该值是一个组合对(pair),map中的节点都是pair类型的,请记住这个特性。
注意这个函数的返回值也是一个组合对,这个pair的first成员是一个迭代器,指向新插入的节点。second成员是一个布尔值,若插入成功,则返回true,插入失败,则返回false。
细心的铁铁应该发现了,插入成功返回新插入的节点的迭代器可以理解,但是插入失败为什么也能返回新插入的节点呢?因为插入失败就说明没有新插入的节点,那么返回这个迭代器指向什么节点呢?
这里就要聊聊map的底层设计了(更详细的请移步前往数据结构专栏中红黑树章节),因为map的插入并非像序列式容器那样无脑的插入某个位置当中,当新节点插入map容器中时,由于map容器需要保持有序,因此插入的位置并不能随意,map需要找到一个合适的位置(即符合有序的位置),由于map容器不支持数据冗余(即去重),因此如果map容器存在与新插入节点一致key的节点,此时插入数据的操作将会失败。
返回的迭代器是指向容器中已存在的相同key值的那个节点的迭代器,同时返回false表示插入失败。因此可以通过验证返回值的second成员是否是false来检验插入操作是否有效。
if (dirc.insert(make_pair("insert", "插入")).second == false)//插入成功
cout << "插入失败" << endl;
if (dirc.insert(make_pair("insert", "插入")).second == false)//插入失败
cout << "插入失败" << endl;
with hint
该种插入方式是可以指定插入的位置。但是程序并非一定按照你指定的位置进行插入,因为map需要保持有序,如果插入的位置并不合适,会自动寻找一个合适的位置进行插入。返回的迭代器指向新插入的数据或者map容器中具有相同值的节点。
auto it = dirc.insert(dirc.begin(),make_pair("pointer", "指针"));
cout << dirc.begin()->first;//abandom仍是第一个位置,这说明pointer并未插入至map当中。
range
通过传入迭代区间,map容器将迭代区间的整个数据依次插入至容器当中。
vector<pair<string, string>> v1{make_pair("string", "字符串"), make_pair("map", "地图")};
dirc.insert(v1.begin(), v1.end());
erase
(1)
iterator erase (const_iterator position);
(2)
size_type erase (const key_type& k);
(3)
iterator erase (const_iterator first, const_iterator last);
erase的使用方法分为三种
(1)删除某个迭代器对应的节点
(2)删除对应key值的节点
(3)删除某个迭代区间的节点。
至于例子博主这里采用c++手册当中的吧
// erasing from map
#include <iostream>
#include <map>
int main ()
{
std::map<char,int> mymap;
std::map<char,int>::iterator it;
// insert some values:
mymap['a']=10;
mymap['b']=20;
mymap['c']=30;
mymap['d']=40;
mymap['e']=50;
mymap['f']=60;
it=mymap.find('b');
mymap.erase (it); // 删除迭代器it对应的节点
mymap.erase ('c'); // 删除key值‘c’对应的节点
it=mymap.find ('e');
mymap.erase ( it, mymap.end() ); // 删除从'e'到结束位置的节点
// 打印容器的所有元素
for (it=mymap.begin(); it!=mymap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
//a=>10 d=>40
return 0;
}
operator[]
map将下标引用符(即“[]”,该符号经常用于数组,以及某些序列式容器)进行了重载,并获得与众不同的功能。)
mapped_type& operator[] (const key_type& k);
key_type是key值的类型,mapped_type是节点的value值的类型。也就是说这个函数需要我们传入key值,然后返回一个value值给我们。
operator[]的作用是,我们传入一个key值,如果map容器中存在对应key值,则返回key值对应的value值的引用。
如果k与容器中任何元素的key值不匹配,则该函数用该key插入一个新元素,并返回对其value值的引用。这个value值会是一个对应类型的默认构造,比如value是string类型,则调用string的默认构造,通过这个方法可以快速插入一个节点。
map<string, string>dirc({
{"byte","字节"}
,{ "abandom","放弃" }
, { "destory","摧毁"}
,{"construct","构造"}
});
cout << dirc["byte"] << endl;//字节
dirc["insert"] = "查入";//在dirc中插入"insert",并赋予对应value值
dirc["insert"] = "插入";//修改"insert"对应的value值