C++ 学习笔记之(11) - 关联容器
关联容器和顺序容器有着根本的不同,关联容器中的元素是按关键字来保存和访问的,与之相对,顺序容器中的而元素是按照他们在容器中的位置来顺序保存和访问的。
关联容器支持高效的关键字查找和访问,关键字起到索引的作用。标准库提供了8
个关联容器,它们的不同体现在三个维度上
- 或者是
set
,或者是map
- 或者要求不重复的关键字; 或者允许重复的关键字,容器名字包含
multi
- 按顺序保存元素; 或者无序保存,其容器名字包含
unordered
关联容器概述
关联容器支持一些前面所讲的普通容器操作,但不支持顺序容器的位置相关的操作,比如push_back
等,因为关联容器中元素是按关键字存储的。关联容器还支持一下特有的操作好类型别名
定义关联容器
类似顺序容器,关联容器也是模板,故比如定义map
需要指定关键字和值的类型。set
需要知道关键字类型。
map<string, size_t> word_count; // 空容器
map<string, size_t> author_count = { {"Austen", 2}, {"Dickens", 5} };
set<string> exclude = {"the", "but"}; // 列表初始化
关键字类型的要求
对于有序容器(map, multimap, set, multiset
) , 关键字必须定义元素比较的方法。默认标准库使用关键字类型的<
运算符比较两个关键字。
可以提供自定义操作来代替关键字上的
<
运算符, 所提供的操作必须在关键字类型上定义一个 严格弱序, 即小于等于- 两个关键字不能同时小于等于对方
- 若
k1
小于等于k2
,k2
小于等于k3
, 则k1
小于等于k3
- 若两个关键字,任何一个都不小于等于对方,则等价
若定义有序关联容器要对关键字类型使用自定义比较函数时,需要提供关键字类型以及比较操作类型
// bookstore 中的元素以 ISBN 的顺序进行排列,允许重复. compareIsbn用来比较Sales_data对象ISBN multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
pair
类型
头文件utility
中, 保存两个成员。类似容器,pair
是模板,定义时序提供两个类型名
关联容器操作
关联容器定义了额外的类型
我们用作用域运算符提取类型的成员
map<string, int>::key_type kt; // kt 是 string
关联容器迭代器
当解引用关联容器迭代器时,得到类型为容器的value_type
的值的引用
set
中的元素是const
,map
中的元素是pair
,其第一个成员是const
set
的迭代器是const
的, 虽然同时定义了iterator
和const_iterator
类型- 通常不对关联容器使用泛型算法,因为关键字是
const
这一特性意味着不能将关联容器传递给修改或重排元素的算法 - 若真要对关联容器使用算法, 则可将它当做源序列或目的序列,使用
copy
拷贝或inserter
绑定调用其他算法
添加元素
关联容器的insert
成员可向容器中添加元素或元素范围
// 向 map 中插入元素的 4 种方法, 注意元素类型为 pair
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string, size_t>::value_type(word, 1));
- 对于不包含重复元素的关联容器来说,
insert
或emplace
返回一个pair
,first
为指向给定关键字元素的迭代器,second
为bool
,表示是否成功插入(已在容器的不插入,返回false
) - 对包含重复元素的关联容器来说,比如
multimap
、multiset
, 总能成功插入,故无需返回bool
值
删除元素
map 的下标操作
set
不支持下标操作,也不能对multimap
和unordered_multimap
进行下标操作,因为可能有多个值与之关联- 与
vector
和string
不同,map
的下标运算符返回的类型与解引用map
迭代器得到的类型不同。对map
下标操作,获取mapped_type
对象,而解引用map
迭代器时,获取value_type
对象
访问元素
下标运算符在关键字不在的情况下,会插入新元素,进行值初始化,若不想更改
map
,可使用find
若
multimap
或multiset
中有多个元素具有给定关键字,则会相邻存储// 三种方法获取相同元素 multimap<string, int> mm = { {"a", 1}, {"b", 2}, {"a", 3}, {"a", 4} }; string search_item("a"); // 方法一:find 和 count for(auto iter = mm.find(search_item), cnt = mm.count(search_item); cnt; ++iter, --cnt) cout << iter->second << endl; auto cnt = mm.count(search_item); // 关键字是 a 的元素个数 auto iter = mm.find(search_item); // 关键字是 a 的第一个元素 for(; cnt; ++iter, --cnt) cout << iter->second << endl; // 输出 1, 3, 4 // 方法2:使用 lower_bound 和 upper_bound // lower_bound 返回指向第一个具有给定关键字的位置; upper_bound 返回最后一个匹配给定关键字的元素之后的位置。若没有元素与关键字匹配,这两个函数返回相同迭代器 for(auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item); beg != end; ++beg) cout << beg->second << endl; // 输出 1, 3, 4 // 方法3:使用 equal_range // equal_range 返回一个迭代器 pair, 若关键字存在,则第一个迭代器返回指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置 for(auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first) cout << pos.first->second << endl; // 输入 1, 3, 4,
无序容器
新标准定义了4
个无序关联容器, 这些容器不使用比较运算符组织元素,而是使用哈希函数
和关键字类型的==
运算符
管理桶
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素,无序容器使用哈希函数将元素映射到桶。
不能直接定义关键字类型为自定义类类型的无序容器,因为要使用哈希模板,所以必须提供自定义的
hash
模板版本(需要提供函数来替代==
运算符和哈希值计算函数)
结语
关联容器通过关键字高效查找和提取元素,而顺序容器通过位置访问元素
标准库定义了
8
个关联容器,容器的类别与三个维度有关- 有序容器使用比较函数来比较关键字,从而将元素按顺序存储,默认比较操作采用关键字类型的
<
运算符。无序容器使用关键字类型的==
运算符和一个hash<key_type>
类型的对象来组织元素 - 允许重复关键字的容器名字都包含
multi
, 使用哈希技术的容器名字都以unordered
开头 - 无论在有序容器还是无序容器中,具有相同关键字的元素都是相邻存储的