之前我们介绍过顺序容器,list, vector, queue等,这一篇介绍关联容器, C++关联容器主要有两大类,map和set。
map
map主要是用来管理key,value类型的结构的。
1 | void use_map() |
用map管理输入的单词,当单词第一次出现时对应的value为0,再执行++操作变为1, 多次出现多次累加。
set
set和map不同,set用来存储key,重复的key只会存储一份。
我们用set和map配合,统计不在set中出现的单词的出现次数
1 | void use_set() |
multiset与multimap
一个map或者set中的key是唯一的,multiset和multimap的key不是唯一的,也就是说同一个key可以对应多个value。
1 | void use_multiset() |
上面的例子用ivec初始化iset和imulset,因为imulset允许key相同,所以imulset大小为20,iset大小为10。
关键字key为复合类型
对于关联容器,如果key为自定义的复合类型,需要为该类重载比较运算符或者定义比较函数.
类重载运算符我们放到之后的章节介绍,这里介绍定义比较函数的方式。先定义一个我们自己的图书信息类
1 | class BookData |
接下来我们定义一个multiset,其key为BookData类型
1 | multiset<BookData, decltype(compareIsbn) *> bookmap(compareIsbn); |
定义了一个bookmap,类型为multiset,尖括号内部一个是key值,为BookData,另一个是比较函数指针,通过decltype自动推导函数指针类型。构造函数要传递函数对象给bookmap。
这种方式并不常用,谨在此做演示,以后学习类重载运算符才是常用作法。
pair类型
pair的标准库类型,它定义在头文件utility中。一个pair保存两个数据成员。类似容器,pair是一个用来生成特定类型的模板。
pair主要用来map的插入操作。
1 | void use_pair() |
上述代码介绍了几种pair的初始化方式,记住一种即可。
关联容器的迭代器
当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用。对map而言,value_type是一个pair类型,其first成员保存const的关键字,second成员保存值
1 | void map_iter() |
必须记住,一个map的value_type是一个pair,我们可以改变pair的值,但不能改变key的值。
set的迭代器是const的,因为set仅有一个key值,而key是不允许更改的,所以set获取到迭代器后无法修改其值。
1 | void set_iter() |
关联容器也支持遍历操作,如map的遍历
1 | void map_iteration() |
容器操作
添加元素可以使用insert函数
可以通过insert向关联容器中插入一定范围的元素
1 | void insert_set() |
上述代码中iset插入重复的元素会自动过滤,保证set中有且仅有不重复的key。
如下向map中添加元素
1 | void insert_map() |
insert(或emplace)返回的值依赖于容器类型和参数。对于不包含重复关键字的容器set和map等,添加单一元素的insert和emplace版本返回一个pair,告诉我们插入操作是否成功。pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中。如果关键字已在容器中,则insert什么事情也不做,且返回值中的bool部分为false。如果关键字不存在,元素被插入容器中,且bool值为true。
1 | void insert_map() |
可以看到key为zack被插入两次,所以第二次res的second为false,res的第一个元素为key为zack的元素的迭代器,然后我们再次取迭代器的first,就是key值zack。
删除元素
1 | void insert_map() |
对于保存不重复关键字的容器,erase的返回值总是0或1。若返回值为0,则表明想要删除的元素并不在容器中.
对允许重复关键字的容器,删除元素的数量可能大于1
对于map的下标操作一定要小心,当不存在key时,通过下标访问key会将key和初始的空value自动插入到map中。
我们可以通过find操作替代下标操作,这样保证不存在的key不会写入map
1 | void find_map() |
对于multimap查找元素也可以通过find
1 | void find_map() |
authors为multimap,map以及multimap的for_each函数要传递的lambda表达式参数为pair。
对于key为zack的元素有两个,count返回key为zack的元素个数2,authors.find(string(“zack”))返回key为zack的第一个元素的迭代器位置。然后根据count控制迭代器移动依次打印就可以了。
也可以通过lower_bound和upper_bound打印一个key出现的多个元素。
lower_bound返回大于等于key值的第一个元素的迭代器,upper_bound返回大于key的迭代器,通过左闭右开的区间去遍历打印元素就可以了
1 | void find_map() |
还有一种方法遍历multiset中查找的指定key,通过通过equal_range函数。
此函数接受一个关键字,返回一个迭代器pair。
若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。
若未找到匹配元素,则两个迭代器都指向关键字可以插入的位置。
1 | void find_map() |
无序容器
新标准定义了4个无序关联容器(unordered associative container)。这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数(hash function)和关键字类型的==运算符。在关键字类型的元素没有明显的序关系的情况下,无序容器是非常有用的。在某些应用中,维护元素的序代价非常高昂,此时无序容器也很有用。
最常用的无序容器是unordered_map
可以用unordered_map重写最初的单词计数程序
1 | void use_unorderd_map() |
可以看到打印的key是无序的。
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。
无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。
容器将具有一个特定哈希值的所有元素都保存在相同的桶中。
如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。
因此,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。