0.目录
- 简介
- 关系
- 介绍
3.1 容器(Container)
3.2 算法(Algorithm)
3.3 迭代器(Iterator)
3.4 仿函数(Function object)
3.5 适配器(Adaptor)
3.6 空间配置器(allocator) - 引用
1.简介
容器(Container):各种基本数据结构
算法(Algorithm):各种基本算法
迭代器(Iterator):提供访问容器中对象的方法
仿函数(Function object)
适配器(Adaptor)
空间配置器(allocator)
2.关系
3.介绍
3.1 容器(Container)
容器库是类模板与算法的汇集,允许程序员简单地访问常见数据结构,例如队列、链表和栈。有三类容器——顺序容器、关联容器和无序关联容器——每种都被设计为支持不同组的操作。
容器管理为其元素分配的存储空间,并提供直接或间接地通过迭代器(Iterator)访问它们的函数。
大多数容器拥有至少几个常见的成员函数,并共享功能。特定应用的最佳容器不仅依赖于提供的功能,还依赖于对于不同工作量的效率。
顺序容器
顺序容器实现能按顺序访问的数据结构。
-
array
: 静态的连续数组 (C++11) -
vector
: 动态的连续数组 -
deque
: 双端队列 -
forward_list
: 单链表 (C++11) -
list
: 双链表
关联容器
关联容器实现能快速查找( O(log n) 复杂度)的数据结构。
-
set
: 唯一键的集合,按照键排序 -
map
: 键值对的集合,按照键排序,键是唯一的 -
multiset
: 键的集合,按照键排序 -
multimap
: 键值对的集合,按照键排序
无序关联容器
无序关联容器提供能快速查找(均摊 O(1) ,最坏情况 O(n) 的复杂度)的无序(哈希)数据结构。
-
unordered_set
: 唯一键的集合,按照键生成散列 (C++11) -
unordered_map
: 键值对的集合,按照键生成散列,键是唯一的 (C++11) -
unordered_multiset
: 键的集合,按照键生成散列 (C++11) -
unordered_multimap
: 键值对的集合,按照键生成散列 (C++11)
3.2 算法(Algorithm)
算法库提供大量用途的函数(例如查找、排序、计数、操作),它们在元素范围上操作。注意范围定义为 [first, last)
,其中 last
指代要查询或修改的最后元素的后一个元素。
不修改序列的操作 <algorithm>
|
作用 |
---|---|
all_of (C++11) any_of (C++11) none_of (C++11)
|
检查谓词是否对范围中所有、任一或无元素为true
|
for_each for_each_n (C++17)
|
应用函数到范围中的所有/前n个元素 |
count count_if
|
返回满足指定判别标准的元素数 |
mismatch |
寻找两个范围出现不同的首个位置 |
find find_if find_if_not (C++11)
|
寻找首个满足特定判别标准的元素 |
find_end |
在特定范围中寻找最后出现的元素序列 |
find_first_of |
搜索元素集合中的任意元素 |
adjacent_find |
查找首对相邻的相同(或满足给定谓词的)元素 |
search search_n
|
搜索一个元素范围中的某个元素/一定量的某个元素的连续副本 |
修改序列的操作 <algorithm>
|
作用 |
---|---|
copy copy_if (C++11) copy_n (C++11)
|
将某一范围的元素/一定数目的元素复制到一个新的位置 |
copy_backward |
按从后往前的顺序复制一个范围内的元素 |
move (C++11)
|
将某一范围的元素移动到一个新的位置 |
move_backward (C++11)
|
按从后往前的顺序移动某一范围的元素到新的位置 |
fill fill_n
|
将一个给定值复制赋值给一个范围内的每个元素/N个元素 |
transform |
将一个函数应用于某一范围的各个元素,并在目标范围存储结果 |
generate generate_n
|
将相继的函数调用结果赋值给一个范围中的每个元素/N个元素 |
remove remove_if
|
移除满足特定判别标准的元素 |
remove_copy remove_copy_if
|
复制一个范围的元素,忽略满足特定判别标准的元素 |
replace replace_if
|
将所有满足特定判别标准的值替换为另一个值 |
replace_copy replace_copy_if
|
复制一个范围内的元素,并将满足特定判别标准的元素替换为另一个值 |
swap |
交换两个对象的值 |
swap_ranges |
交换两个范围的元素 |
iter_swap |
交换两个迭代器所指向的元素 |
reverse |
逆转范围中的元素顺序 |
reverse_copy |
创建一个范围的逆向副本 |
shift_left shift_right (C++20)
|
迁移范围中的元素 |
rotate |
旋转范围中的元素顺序 |
rotate_copy |
复制并旋转元素范围 |
random_shuffle (C++17 前) shuffle (C++11)
|
随机重排范围中的元素 |
sample (C++17)
|
从一个序列中随机选择 n 个元素 |
unique |
移除范围内的连续重复元素 |
unique_copy |
创建某范围的不含连续重复元素的副本 |
划分操作 <algorithm>
|
作用 |
---|---|
is_partitioned (C++11)
|
判断范围是否已按给定的谓词划分 |
partition |
将范围中的元素分为两组 |
partition_copy (C++11)
|
复制一个范围,将各元素分为两组 |
stable_partition |
将元素分为两组,同时保留其相对顺序 |
partition_point (C++11)
|
定位已划分范围的划分点 |
排序操作 <algorithm>
|
作用 |
---|---|
is_sorted (C++11)
|
检查范围是否已按升序排列 |
is_sorted_until (C++11)
|
找出最大的已排序子范围 |
sort |
将范围按升序排序 |
partial_sort |
排序一个范围的前 N 个元素 |
partial_sort_copy |
对范围内的元素进行复制并部分排序 |
stable_sort |
将范围内的元素排序,同时保持相等的元素之间的顺序 |
nth_element |
将给定的范围部分排序,确保其按给定元素划分 |
二分搜索操作(在已排序范围上) <algorithm>
|
作用 |
---|---|
lower_bound |
返回指向第一个不小于给定值的元素的迭代器 |
upper_bound |
返回指向第一个大于给定值的元素的迭代器 |
binary_search |
确定元素是否存在于某范围中 |
equal_range |
返回匹配特定键值的元素范围 |
集合操作(在已排序范围上) <algorithm>
|
作用 |
---|---|
merge |
归并两个已排序的范围 |
inplace_merge |
就地归并两个有序范围 |
includes |
若一个集合是另一个的子集则返回 true |
set_difference |
计算两个集合的差集 |
set_intersection |
计算两个集合的交集 |
set_symmetric_difference |
计算两个集合的对称差 |
set_union |
计算两个集合的并集 |
堆操作 <algorithm>
|
作用 |
---|---|
is_heap |
检查给定范围是否为一个最大堆 |
is_heap_until (C++11)
|
查找能成为最大堆的最大子范围 |
make_heap |
从一个元素范围创建出一个最大堆 |
push_heap |
将一个元素加入到一个最大堆 |
pop_heap |
从最大堆中移除最大元素 |
sort_heap |
将一个最大堆变成一个按升序排序的元素范围 |
最小/最大操作 <algorithm>
|
作用 |
---|---|
max |
返回各给定值中的较大者 |
max_element |
返回范围内的最大元素 |
min |
返回各给定值中的较小者 |
min_element |
返回范围内的最小元素 |
minmax (C++11)
|
返回两个元素的较小和较大者 |
minmax_element (C++11)
|
返回范围内的最小元素和最大元素 |
clamp (C++17)
|
在一对边界值间夹逼一个值 |
比较操作 <algorithm>
|
作用 |
---|---|
equal |
确定两个元素集合是否是相同的 |
lexicographical_compare |
当一个范围按字典顺序小于另一个范围时,返回 true |
lexicographical_compare_three_way (C++20)
|
用三路比较比较两个范围 |
排列操作 <algorithm>
|
作用 |
---|---|
is_permutation (C++11)
|
判断一个序列是否为另一个序列的排列 |
next_permutation |
产生某个元素范围的按字典顺序的下一个较大的排列 |
prev_permutation |
产生某个元素范围的按字典顺序的下一个较小的排列 |
数值运算 <numeric>
|
作用 |
---|---|
iota (C++11)
|
用从起始值开始连续递增的值填充一个范围 |
accumulate |
对一个范围内的元素求和 |
inner_product |
计算两个范围的元素的内积 |
adjacent_difference |
计算范围内各相邻元素之间的差 |
partial_sum |
计算范围内元素的部分和 |
reduce (C++17)
|
类似std::accumulate ,但不依序执行 |
exclusive_scan (C++17)
|
类似std::partial_sum ,第 i 个和中排除第 i 个输入 |
inclusive_scan (C++17)
|
类似std::partial_sum ,第 i 个和中包含第 i 个输入 |
transform_reduce (C++17)
|
应用一个函数对象,然后以乱序规约 |
transform_exclusive_scan (C++17)
|
应用一个函数对象,然后进行排除扫描 |
transform_inclusive_scan (C++17)
|
应用一个函数对象,然后进行包含扫描 |
未初始化内存上的操作 <memory>
|
作用 |
---|---|
uninitialized_copy |
将范围内的对象复制到未初始化的内存区域 |
uninitialized_copy_n (C++11)
|
将指定数量的对象复制到未初始化的内存区域 |
uninitialized_fill |
复制一个对象到以范围定义的未初始化内存区域 |
uninitialized_fill_n |
复制一个对象到以起点和计数定义的未初始化内存区域 |
uninitialized_move (C++17)
|
移动一个范围的对象到未初始化的内存区域 |
uninitialized_move_n (C++17)
|
移动一定数量对象到未初始化内存区域 |
uninitialized_default_construct (C++17)
|
在范围所定义的未初始化的内存区域以默认初始化构造对象 |
uninitialized_default_construct_n (C++17)
|
在起始和计数所定义的未初始化内存区域用默认初始化构造对象 |
uninitialized_value_construct (C++17)
|
在范围所定义的未初始化内存中用值初始化构造对象 |
uninitialized_value_construct_n (C++17)
|
在起始和计数所定义的未初始化内存区域以值初始化构造对象 |
destroy_at (C++17)
|
销毁在给定地址的对象 |
destroy (C++17)
|
销毁一个范围中的对象 |
destroy_n (C++17)
|
销毁范围中一定数量的对象 |
C 库 <cstdlib>
|
作用 |
---|---|
qsort |
对未指定类型的元素的一个范围进行排序 |
bsearch |
在未指定类型的数组中搜索元素 |
3.3 迭代器(Iterator)
迭代器Iterator
,用来在一个对象群集(collection of objects)的元素上进行遍历。这个对象群集或许是个容器,或许是容器的一部分。
迭代器的主要好处是,为所有容器提供了一组很小的公共接口。迭代器以++
进行累进,以*
进行提领,类似于指针,每种容器都提供了自己的迭代器。
迭代器库提供了五种迭代器的定义,同时还提供了迭代器特征、适配器及相关的工具函数:
-
Input Iterator
:只读,自增(++
),单趟 -
Output Iterator
:只写,自增(++
),单趟 -
Forward Iterator
:读写,自增(++
) -
Bidirectional Iterator
: 读写,自增(++
),自减(--
) -
Random Access Iterator
:读写,随机访问,增(it+n
,it+=n
),减(it-n
,it-=n
,it1-it2
)
迭代器非法化
只读方法决不非法化迭代器或引用。修改容器内容的方法可能非法化迭代器和/或引用如下:
容器 | 插入 | 删除 |
---|---|---|
array |
无 | 无 |
vector |
位于插入元素之前的迭代器合法 位于插入元素及之后的迭代器非法 当插入引起内存重新分配时所有迭代器非法 |
位于删除元素之前的迭代器合法 位于删除元素及之后的迭代器非法 |
deque |
所有迭代器非法 | 删除首/尾元素时除被删除元素外的迭代器合法 删除中部元素时所有迭代器非法 |
list forward_list
|
始终合法 | 仅被删除元素非法 |
set multiset map multimap
|
始终合法 | 仅被删除元素非法 |
unordered_set unordered_multiset
|
若插入导致重哈希,所有迭代器非法 否则始终合法 |
仅被删除元素非法 |
unordered_map unordered_multimap
|
始终合法 | 仅被删除元素非法 |
此处插入指代任何添加一或多个元素到容器的方法,而删除指代任何从容器移除一或多个元素的方法。
- 插入方法的例子是
std::set::insert
、std::map::emplace
、std::vector::push_back
和std::deque::push_front
。
注意std::unordered_map::operator[]
也算,因为它可能插入元素到map
中。 - 擦除方法的例子是
std::set::erase
、std::vector::pop_back
、std::deque::pop_front
和std::map::clear
。
clear
非法化所有迭代器和引用。因为它擦除所有元素,这在技术上遵照上述规则。
尾后迭代器需要特别留意。通常像指向未被擦除元素的正常迭代器一般非法化此迭代器。故std::set::end
决不被非法化,std::unordered_set::end
仅在重哈希时被非法化,std::vector::end
始终被非法化(因为它始终出现在被修改元素后),以此类推。
有一个例外:删除std::deque
末元素的擦除操作会非法化尾后迭代器,尽管它不是容器的被擦除元素(或者说根本不是元素)。与std::deque
迭代器的通用规则结合后,最终结果是不非法化std::deque::end
的唯一修改操作是删除首元素,而非末元素的擦除。
3.4 仿函数(Function object)
仿函数具体有什么用?没找到很好的解释。
- 一个行为类似函数的对象,它可以没有参数,也可以带有若干参数。
- 任何重载了调用运算符
operator()
的类的对象都满足函数对象的特征 - 函数对象可以把它称之为
smart function
。 - STL中也定义了一些标准的函数对象,如果以功能划分,可以分为算术运算、关系运算、逻辑运算三大类。为了调用这些标准函数对象,需要包含头文件
<functional>
:
算术运算 | 功能 |
---|---|
plus |
实现 x + y 的函数对象 |
minus |
实现 x - y 的函数对象 |
multiplies |
实现 x * y 的函数对象 |
divides |
实现 x / y 的函数对象 |
modulus |
实现 x % y 的函数对象 |
negate |
实现 -x 的函数对象 |
比较 | 功能 |
---|---|
equal_to |
实现 x == y 的函数对象 |
not_equal_to |
实现 x != y 的函数对象 |
greater |
实现 x > y 的函数对象 |
less |
实现 x < y 的函数对象 |
greater_equal |
实现 x >= y 的函数对象 |
less_equal |
实现 x <= y 的函数对象 |
逻辑运算 | 功能 |
---|---|
logical_and |
实现 x && y 的函数对象 |
logical_or |
实现 x || y 的函数对象 |
logical_not |
实现 !x 的函数对象 |
逐位运算 | 功能 |
---|---|
bit_and |
实现 x & y 的函数对象 |
bit_or |
实现 x | y 的函数对象 |
bit_xor |
实现 x ^ y 的函数对象 |
bit_not (C++14)
|
实现 ~x 的函数对象 |
取反器 | 功能 |
---|---|
not_fn (C++17)
|
创建返回其保有的函数对象的结果之补的函数对象 |
搜索器 | 功能 |
---|---|
default_searcher (C++17)
|
标准 C++ 库搜索算法实现 |
boyer_moore_searcher (C++17)
|
Boyer-Moore 搜索算法实现 |
boyer_moore_horspool_searcher (C++17)
|
Boyer-Moore-Horspool 搜索算法实现 |
3.5 适配器(Adaptor)
适配器是一种类,为已有的类提供新的接口,目的是简化、约束、使之安全、隐藏或者改变被修改类提供的服务集合,适配器对容器进行包装,使其表现出另外一种行为。
容器适配器
用来扩展7种基本容器,它们和顺序容器相结合构成栈、队列和优先队列容器,stack,queue, priority_queue可以基于vector和deque,采用最大堆来实现,因为需要随机存取迭代器,只有这两个;
容器适配器提供顺序容器的不同接口。
种类 | 默认顺序容器 | 可用顺序容器 | 说明 |
---|---|---|---|
stack |
deque |
vector , list , deque
|
适配一个容器以提供栈(LIFO 数据结构) |
queue |
deque |
list , deque
|
适配一个容器以提供队列(FIFO 数据结构) |
priority_queue |
vector |
vector , deque
|
适配一个容器以提供优先级队列 |
迭代器适配器
-
reverse_iterator
: 逆序遍历的迭代器适配器 -
make_reverse_iterator
(C++14)
: 创建拥有从实参推出的类型的std::reverse_iterator
-
move_iterator
(C++11)
: 解引用结果为右值引用的迭代器适配器 -
move_sentinel
(C++20)
: 用于std::move_iterator
的哨位适配器 -
make_move_iterator
(C++11)
: 创建拥有从实参推出的类型的std::move_iterator
-
common_iterator
(C++20)
: 适配一个迭代器类型及其哨位为一个公共迭代器类型 -
default_sentinel_t
(C++20)
: 用于知晓其边界的迭代器的默认哨位 -
counted_iterator
(C++20)
: 对到范围结尾距离进行跟踪的迭代器适配器 -
unreachable_sentinel_t
(C++20)
: 始终与任何weakly_incrementable
类型比较都不相等的哨位 -
back_insert_iterator
: 用于在容器尾部插入的迭代器适配器 -
back_inserter
: 创建拥有从实参推出的类型的std::back_insert_iterator
-
front_insert_iterator
: 用于在容器头部插入的迭代器适配器 -
front_inserter
: 创建拥有从实参推出的类型的std::front_insert_iterator
-
insert_iterator
: 用于插入容器的迭代器适配器 -
inserter
: 创建拥有从实参推出的类型的std::insert_iterator
函数适配器(函数对象适配器、成员函数适配器、普通函数适配器)
3.6 空间配置器(allocator)
负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。
隐藏在这些容器后的内存管理工作是通过STL提供的一个默认的allocator实现的。当然,用户也可以定制自己的allocator,只要实现allocator模板所定义的接口方法即可,然后通过将自定义的allocator作为模板参数传递给STL容器,创建一个使用自定义allocator的STL容器对象,如:
stl::vector< int, UserDefinedAllocator> array;
大多数情况下,STL默认的allocator就已经足够了。这个allocator是一个由两级分配器构成的内存管理器,当申请的内存大小大于128byte时,就启动第一级分配器通过malloc/free直接向系统的堆空间分配,如果申请的内存大小小于128byte时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round
up成8的倍数)从对应的空闲块列表取表头块给用户。
这种做法有两个优点
(1)小对象的快速分配。
小对象是从内存池分配的,这个内存池是系统调用一次malloc分配一块足够大的区域给程序备用,当内存池耗尽时再向系统申请一块新的区域,整个过程类似于批发和零售,起先是由allocator向总经商批发一定量的货物,然后零售给用户,与每次都总经商要一个货物再零售给用户的过程相比,显然是快捷了。当然,这里的一个问题时,内存池会带来一些内存的浪费,比如当只需分配一个小对象时,为了这个小对象可能要申请一大块的内存池,但这个浪费还是值得的,况且这种情况在实际应用中也并不多见。
(2)避免了内存碎片的生成。
程序中的小对象的分配极易造成内存碎片,给操作系统的内存管理带来了很大压力,系统中碎片的增多不但会影响内存分配的速度,而且会极大地降低内存的利用率。以内存池组织小对象的内存,从系统的角度看,只是一大块内存池,看不到小对象内存的分配和释放。