type
status
date
slug
summary
tags
category
icon
password
Property
根据应用场景的不同,STL实现了两种不同结构的关联式容器:树型结构和哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(红黑树)作为其底层结构,容器中的元素是一个有序的序列。
pair类模板
关联式容器存储的是“键值对”形式的数据,例如:
<“penny”,“leonard”>
。这个键值对第一个元素作为键(key),第二个元素作为值(value)。考虑到 “键值对” 并不是普通类型的数据,C++ STL标准库提供了
pair
类模板 ,其专门用来将2个普通元素first
和second
创建成一个新元素<first, second>
。pair
类模板定义在#include<utility>
头文件中:pair
支持的操作:在创建
pair
对象时,必须提供两个类型名,两个对应的类型名的类型不必相同在定义时进行成员初始化:
如果定义多个相同的
pair
类型对象,可以使用typedef
简化声明:set和multiset
set
的底层是用平衡搜索二叉树(红黑树)实现的,是按照一定次序存储元素的容器。在内部,set
中的元素总是按照其内部比较对象(类型比较)所指示的特定严格若排序准则进行排序在
set
中,元素的value
也表示key
,并且每个value(key
)必须是唯一的。map
中存储的是真正的键值对<key, value>
,set
中只放value
,但在底层实际存放的是由<value, value>
构成的键值对。set
中插入元素时,只需要插入value
即可,不需要构造键值对。set
容器通过key
访问单个元素的速度通常比unordered_set
容器慢,但它们允许根据顺序对子集进行直接迭代。set
中的元素不可以重复(因此可以使用set
进行去重)set
中的元素不允许修改,因为set
在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
set的模板参数列表
T
: set
中存放元素的类型,实际在底层存储<value, value>
的键值对Compare
:set
中元素默认按照小于来比较Alloc
:set
中元素空间的管理方式,使用STL
提供的空间配置器管理set的构造
set的迭代器
成员方法 | 功能 |
begin() | 返回指向容器中第一个(已排好序的第一个)元素的双向迭代器。如果 set 容器用const限定,则该方法返回的是const类型的双向迭代器 |
end() | 返回指向容器最后一个元素(是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值 |
set的增删查
成员方法 | 功能 |
find(val) | 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
lower_bound(val) | 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(val) | 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(val) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 set 容器中存有元素的个数。 |
max_size() | 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。 |
insert() | 向 set 容器中插入元素。 |
erase() | 删除 set 容器中存储的元素。 |
swap() | 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。 |
clear() | 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。 |
emplace() | 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。 |
emplace_hint() | 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。 |
count(val) | 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。 |
multiset的使用
multiset
和set
的接口近乎一致,唯一不同于set
的在于multiset
允许键值冗余:正是
multiset
允许了键值冗余,所以multiset
的两个函数接口find
和count
与set
的也是有区别的:成员函数count | 功能说明 |
set对象 | 值为val的元素存在则返回1,不存在则返回0 |
multiset对象 | 返回set中值为x的元素的个数 |
成员函数find | 功能说明 |
set对象 | 返回值为val的元素的迭代器位置 |
multiset对象 | 返回底层搜索树中序的第一个值为val的元素的迭代器 |
map和multismap
map
通常被实现为红黑树,是关联容器,按照特定的次序(按照key
来比较)存储由键值key
和值value
组合而成的元素。在
map
中,键值key
通常用于排序和惟一地标识元素,而值value
中存储与此键值key
关联的内容。键值key
和值value
的类型可能不同,并且在map
的内部,key
与value
通过成员类型value_type
绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;
map
中通过键值访问单个元素的速度通常比unordered_map
容器慢,但map
允许根据顺序对元素进行直接迭代(即对map
中的元素进行迭代时,可以得到一个有序的序列)。map
支持下标访问符,即在[]
中放入key
,就可以找到与key
对应的value
。operator[]
中实际进行插入查找。map
中的key
是唯一的,并且不能修改。key
: 键值对中key
的类型
T
: 键值对中value
的类型
Compare
:比较器的类型,map
中的元素是按照key
来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
Alloc
:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器
map的构造
insert插入函数
insert
函数声明:在
map
中插入键值对x
,注意x
是一个键值对,返回值也是键值对:iterator
代表新插入元素的位置,bool
代表释放插入成功。因此,在使用insert
时,首先用key
和value
构造一个pair
对象,再把pair
对象作为参数传入insert
函数。借助
pair
构造函数:借助
pair
构造匿名对象插入:调用
make_pair
函数模板插入:使用
{}
:在新标准下创建
pair
最简单的方式是使用括弧初始化于参数列表。[ ]运算符重载
[]
运算符重载函数原型声明:[ ]
运算符重载函数的具体源码实现如下:针对该返回值,其主要是进行了两大步骤:
- 首先调用
insert
函数插入键值对返回迭代器ret
- 通过返回的迭代器
ret
调用元素值value
[ ]运算符重载的好处,既可以遇到新
key
像insert
一样插入,又满足了insert
未有的功能,查找并修改。统计水果出现次数:
map的迭代器
成员方法 | 功能 |
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
map的增删改查
成员方法 | 功能 |
find(key) | 在map容器中查找键为key的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和end()方法一样的迭代器。另外,如果 map容器用const限定,则该方法返回的是const类型的双向迭代器。 |
count(key) | 在当前map容器中,查找键为key的键值对的个数并返回。注意,由于map容器中各键值对的键的值是唯一的,因此返回值最大为1 |
lower_bound(key) | 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(key) | 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(key) | 该方法返回一个pair对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 map 容器中存有键值对的个数。 |
max_size() | 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
operator[] | map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。 |
at(key) | 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。 |
insert() | 向 map 容器中插入键值对。 |
erase() | 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。 |
swap() | 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。 |
clear() | 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。 |
emplace() | 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。 |
emplace_hint() | 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。 |
multimap的使用
multimap
和map
的唯一不同就是:map
中的key
是唯一的,而multimap
中key是可以重复的。multimap
中没有重载operator[]
操作(因为multimap
允许冗余,导致key
和value
面临一对多的关系)multimap
允许冗余,这也就导致其内部的find
和count
函数和map
中的有所区别,如下:成员函数count | 功能说明 |
map对象 | 值为key的元素存在则返回1,不存在则返回0 |
multimap对象 | 返回set中值为key的元素的个数 |
成员函数find | 功能说明 |
map对象 | 返回键值为key的元素的迭代器位置 |
multimap对象 | 返回底层搜索树中序的第一个值为key的元素的迭代器 |
unordered系列关联式容器
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 ,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个
unordered
系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构是哈希函数。这些容器不是使用比较运算符来组织元素,而是使用哈希函数(hash function) 和关键字类型的
==
运算符组织元素。map/set与unordered_map/unordered_set的区别
ㅤ | unordered_map / unordered_set | map / set |
底层数据结构 | 哈希表/散列表 | 红黑树 |
是否有序 | 无序 | 有序 |
查找的效率 | O(1) | O(logN) |
迭代器类型 | 单向迭代器 | 双向迭代器 |
头文件 | #include<unordered_map(set)> | #include<map(set)> |
在内部,
unordered_map
没有对<kye, value>
按照任何特定的顺序排序, 为了能在常数范围内找到key
所对应的value
,unordered_map
将相同哈希值的键值对放在相同的桶中。如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。无序容器和对应的有序容器通常可以相互替换。但是由于元素未按顺序存储,使用无序容器的程序输出一般会与有序容器的版本不同。
无序容器在存储上组织为一组桶,每个桶保存零或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。因此无序容器的性能依赖于哈希函数的质量和桶的数量及大小。
无序容器管理操作:
默认情况下,无序容器使用关键字类型的
==
运算符比较元素,还使用一个 hash<key_type>
类型的对象来生成每个元素的哈希值。标准库为内置类型和一些标准库类型提供了hash
模板。因此可以直接定义关键字是这些类型的无序容器,而不能直接定义关键字类型为自定义类类型的无序容器,必须先提供对应的hash
模板版本。除了使用默认的
hash
模板之外,还可以提供自己的相等比较函数和计算哈希值的函数:上面的代码同时指定了
hash
函数和相等性比较函数,如果我们的类有自己的 ==
操作符,可以仅仅只覆盖哈希函数: