c++编程(23)——STL(7)map

欢迎来到博主博主的专栏: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值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小豪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值