C++ STL基础
文章目录
- STL简介
- STL容器
- 顺序容器
- 有序关联容器
- 无序关联容器
- 容器适配器
- 拟容器
- 标准容器操作复杂性
- STL算法
- 算法复杂性
- 不修改序列的算法
- 修改序列的算法
- 排序和搜索
- 最大/最小值
- 参考
本文将对C++的STL进行一些基础介绍。
STL简介
STL(Standard Template Library,标准模板库),顾名思义是一堆C++模板及其支持工具所组成的库,由于STL基本占据了C++标准库的绝大部分内容,所以有时也用STL指代C++标准库。
STL开源实现:
- https://github.com/microsoft/STL.git
- https://sgistl.github.io/download.html
- https://libcxx.llvm.org/
- https://gcc.gnu.org/onlinedocs/libstdc++/
- https://sourceforge.net/projects/stlport/
STL主要包含6大组件:
- 容器(containers):各种数据结构,是一种class template
- 算法(algorithms):各种常用算法,是一种function template
- 迭代器(iterators):连接容器和算法,是所谓的“泛型指针”
- 仿函数(functors):行为类似函数,可作为算法的某种策略。从实现角度看,仿函数是一种重载了operator()的class或者class template,一般的函数指针可以视为狭义的仿函数。
- 适配器(adapters):一种用来修饰容器或者仿函数或者迭代器接口的东西。
- 配置器(allocators):复制空间配置和管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。
这6大组件之间的关系如下:
- 容器作为STL的主体,是许多不同的数据结构
- 分配器为容器的实现分配应有的空间
- 泛型算法用来处理容器中的数据
- 迭代器是泛型算法和容器之间的粘合剂
- 仿函数使得算法可以有更加灵活的自定义模式
- 适配器保证了自定义的功能可以和STL中现有的功能相融合
STL容器
顺序容器
A是分配器选项,默认值为
std::allocator<T>
vector<T,A>
:空间连续分配的T 类型元素序列;默认选择容器
List<T,A>
:T类型元素双向链表;当需要插入/删除元素当不移动已有元素时选择
forward_list<T,A>
:T类型元素单向链表;很短的或空序列的理想选择
deque<T,A>
:T类型元素双端队列;向量和链表的混合;对大多数应用而言,都比向量和链表要慢
有序关联容器
C是比较类型,A是分配器选项
map<K,V,C,A>
:从K到V的有序映射;一个(k,V)对序列
multimap<K,V,C,A>
:从K到V的有序映射;允许重复关键字
set<K,C,A>
:K的有序集合
multiset<K,C,A>
:K的有序集合;允许重复关键字
这些容器通常用平衡二叉树(通常是红黑树)实现,关键字K的默认序标准是std::less<K>
,对映射,A的默认值为std::allocator<std::pair<const K, T>>
,对集合A的默认值为std::allocator<K>
。
无序关联容器
H是哈希函数类型,E是相等性测试,A是分配器类型
unordered_map<K,V,H,E,A>
:从K到V的无序映射
unordered_multimap<K,V,H,E,A>
:从K到V的无序映射;允许重复关键字
unordered_set<K,H,E,A>
:K的无序集合
unordered_multiset<K,H,E,A>
:K的无序集合;允许重复关键字
这些容器都是采用一处链表法的哈希表实现。关键字类型K的默认哈希哈树类型H为std::hash<K>
。关键字类型K的默认相等性判定函数类型E为std::equal_to<K>
;相等性判断函数用来判断哈希值相同的两个对象是否相等。
容器适配器
C是一个容器类型
priority_queue<T,C,Cmp>
:T的优先队列;Cmp是优先级函数类型
queue<T,C>
:T的队列,支持push和pop操作
stack<T,C>
:T的栈,支持push和pop操作
priority_queue的默认优先级函数Cmp是std::less<T>
,它的底层是容器加heap。queue的默认容器类型C为std::deque<T>
,stack和priority_queue的默认容器类型C为std::vector<T>
。
容器适配器(container adaptor)为容器提供不同(通常受限)的接口。容器适配器的实际用法就是仅通过其特殊接口使用。STL容器适配器不提供直接访问其底层容器的方式,也不提供迭代器或下标操作。
拟容器
某些数据类型具有标准容器所应有的大部分特性,但有非全部,称这些容器为拟容器。
T[N]
:固定大小的内置数组;没有size()或其他成员函数
array<T,N>
:固定大小的数组;类似内置数组,但解决了大部分问题
basic_string<C,Tr,A>
:一个连续分配空间的类型为C的字符序列,支持文本处理操作;A是分配器,Tr是字符萃取。basic_string通常经过优化,短字符串无须使用自由存储空间。
string
:basic_string<char>
u16string
:basic_string<char16_t>
u32string
:basic_string<char32_t>
wstring
:basic_string<wchar_t>
valarray<T>
:数值向量,支持向量运算
bitset<N>
:N个二进制位的集合,支持集合操作,如&
和|
vector<bool>
:vector<T>
的特化版本,紧凑保存二进制位
标准容器操作复杂性
容器 | 下标访问 | 任意位置插入和删除 | 在头部插入和删除 | 在尾部插入和删除 | 支持迭代器 |
---|---|---|---|---|---|
vector | 常量 | O(n)+ | 常量+ | 随机 | |
list | 常量 | 常量 | 常量 | 双向 | |
forward_list | 常量 | 常量 | 前向 | ||
deque | 常量 | O(n) | 常量 | 常量 | 随机 |
stack | 常量 | ||||
queue | 常量 | 常量 | |||
priority_queue | O(log(n)) | O(log(n)) | |||
map | O(log(n)) | O(log(n))+ | 双向 | ||
multimap | O(log(n))+ | 双向 | |||
set | O(log(n))+ | 双向 | |||
multiset | O(log(n))+ | 双向 | |||
unordered_map | 常量+ | 常量+ | 前向 | ||
unordered_multimap | 常量+ | 前向 | |||
unordered_set | 常量+ | 前向 | |||
unordered_multiset | 常量+ | 前向 | |||
string | 常量 | O(n)+ | O(n)+ | 常量+ | 随机 |
array | 常量 | 随机 | |||
内置数组 | 常量 | 随机 | |||
valarray | 常量 | 随机 | |||
bitset | 常量 |
STL算法
标准库算法的目标是为可有化实现的某些东西提供最通用最灵活的接口。
无论一个STL算法返回什么,它都不会是实参的容器。传递给STL算法的实参是迭代器,算法完全不了解迭代器所指向的数据结构。迭代器存在主要是为了将算法从它所处理的数据结构上分离开来。
每个算法基本都有一个使用后缀_if
的版本,该版本接受一个谓词,可以指定策略。
算法复杂性
大多数算法都是线性时间O(n)的,n通常是输入序列长度。
复杂度 | 算法 |
---|---|
O(1) | swap(), iter_swap() |
O(log(n)) | lower_bound(), upper_bound(), equal_range(),binary_search(),push_heap(),pop_heap() |
O(n*log(n)) | inplace_merge()(最坏情况),stable_partition(最坏情况),sort(),stable_sort(),partial_sort(),partial_sort_copy(),sort_heap() |
O(n*n) | find_end(),find_first_of(),search(), search_n() |
O(n) | 所有其他算法 |
不修改序列的算法
-
for_each()
f=for_each(b,e,f)
:对[b,e)中的每个x执行f(x) -
序列谓词
all_of(b,e,f)
:[b,e)中所有元素x都满足f(x)吗?
any_of(b,e,f)
:[b,e)中有元素x满足f(x)吗?
none_of(b,e,f)
:[b,e)中所有元素x都不满足f(x)吗? -
count()/count_if()
:寻找满足条件的元素数目 -
find/find_if/find_if_not/find_first_of/adjacent_find/find_end
:顺序搜索具有特定值或令谓词为真的元素 -
equal/mismatch
:比较一对序列 -
search/search_n
:查找给定序列是否是另一个序列的子序列
修改序列的算法
修改序列的算法(可变序列算法,mutating sequence algorithm)可以修改其实参序列的元素。
transform
copy/copy_if/copy_n/copy_backward/move/move_backward
:copy算法的目标序列不一定是一个容器,任何可用一个输出迭代器描述的东西都可以作为它的目标。unique/unique_copy
:从序列中删除连续的重复元素。- remove和replace
remove/remove_if/remove_copy./remove_copy_if
:“删除”序列末尾元素reverse/reverse_copy
:逆序排列和拷贝replace/replace_if/replace_copy/replace_copy_if
- 这些算法不能改变输入序列的大小,即使是remove也会保持输入序列大小不变。类似unique,它是通过将元素移动到左侧来实现“删除”的。
rotate/retate_copy/random_shuffle/shuffle/partition/statble_partition/partition_copy/partition_point/is_partitioned
:提供了移动序列中元素的系统方法,用swap来移动元素的。- 排列:提供了生成一方额序列所有排列的系统方法
next_permutation/prev_permutation/is_permutation
- fill:fill系列算法提供了向序列元素赋值和初始化元素的方法
fill/fill_n/generate/generate_n/uninitialized_fill/uninitialized_fill_n/uninitialized_copy/uninitialized_copy_n
- fill算法反复用指定值进行赋值,generate算法通过反复调用其函数实参得到的值进行赋值
- 如果希望初始化,使用
uinitialized_
的版本。
swap/swap_ranges/iter_swap
:交换对象的值,它是标准库中最简单也最重要的算法之一
排序和搜索
sort
系列算法
sort算法要求随机访问迭代器,排序list时一般将list元素拷贝到vector中,排序这个vector后再将元素拷回list。binary_search
:二分搜索算法提供了有序序列上的二分搜索,二分搜索算法不需要随机访问迭代器,一个前向迭代器就够了lower_bound/upper_bound/binary_search/qual_range
merge
:将两个有序序列合并为1个merge/inplace_merge
- 集合算法:集合算法将序列当作一个元素集合来处理,并提供基本的集合操作。输入数列应该是排好序的,输出序列也会被排序。
includes/set_union/set_intersection/set_difference/set_symmetric_difference
- 堆:堆是一种按最大值优先的方式组织元素的紧凑型数据结构。可以理解为一种二叉树的表示方式。堆算法允许程序员将一个随机访问序列作为堆处理。
make_heap/push_heap/pop_heap/sort_heap/is_heap/is_heap_until
lexicographical_compare
:字典序比较就是我们用来排序字典中单词的规则。
最大/最小值
min
max
minmax
min_element
max_element
minmax_element
参考
STL源码剖析