文章目录
在使用 C++ 编程中,最重要的库就是 C++ 标准库。C++ 标准库的核心是泛型容器和泛型算法,库中的这一子集通常称为标准模板库(Standard Template Library, STL),因为这一部分大量使用了模板。STL 的威力在于提供了泛型容器和泛型算法,使大部分算法可用于大部分容器,而无论容器中保存的数据类型是什么。性能是 STL 中非常重要的一部分。STL 的目标是要让 STL 容器和算法和手工编写的代码速度相当快。
编码原则
标准库大量使用了C++的模板特性和运算符重载特性,标准模板库也是这样。
C++标准库概述
字符串
从技术角度,C++ string实际上是basic_string
模板char
实例化的typedef
名称。然而,不需要关注这些细节。只要像非模板类那样使用string即可。
正则表达式
I/O流
I/O功能在几个头文件中定义:
<fstream>
<iomanip>
<ios>
<iosfwd>
<iostream>
<istream>
<ostream>
<sstream>
<streambuf>
<strstream>
智能指针
C++用智能指针unique_ptr
、shared_ptr
和week_ptr
解决内存管理问题。
shared_ptr
和week_ptr
是线程安全的,都在<memory>
中定义。
异常
C++标准库提供了一个异常的类层次结构,在程序中可以使用这些类,也可以通过继承方式创建自己的异常类型。
异常在以下头文件中定义:
<exception>
<stdexcept>
<system_error>
数学工具
虽然这些类模板化了,可以用于任何类型,但是一般不认为这些类是标准模板库的一部分。
-
<complex>
提供了一个复数类complex
,提供了对实部和虚部的数的操作抽象。 -
<ratio>
有理数运算库,可以精确地表示任何由分子和分母定义的有限有理数。 -
<valarray>
提供了valarray
,和vector
类似,但是对高性能数值应用做了特别的优化,提供了表示矢量切片概念的相关类。通过这些构件,可以构建执行矩阵数学运算的类。不过,没有内建的矩阵类。Boost这样的第三方库提供了矩阵支持。
获取数值极限的标准方式。
<limts>
中的numeric_limits
模板。
cout << "Max int value: " << numeric_limits<int>::max() << endl;
cout << "Min int value: " << numeric_limits<int>::min() << endl;
cout << "Lowest int value: " << numeric_limits<int>::lowest() << endl;
cout << "Max double value: " << numeric_limits<double>::max() << endl;
cout << "Min double value: " << numeric_limits<double>::min() << endl;
cout << "Lowest double value: " << numeric_limits<double>::lowest() << endl;
时间工具
<chrono>
简化了时间相关的操作,特定时间间隔的定时操作和定时相关的操作执行。
随机数
srand()
和rand()
只提供了非常初级的随机数,也无法修改生成随机数的分布。
C++11添加了一个完善的随机数库<random>
,带有随机数引擎、随机数引擎适配器以及随机数分布。
通过<random>
可以生成特定问题域的随机数,例如正态分布、负指数分布。
初始化列表
<initializer_list>
编写参数数目可变的函数。
Pair和Tuple
-
<utility>
定义了pair
模板,可以用于存储两种不同类型的元素。这称为存储异构元素。 -
<tuple>
定义的tuple
是pair
的一种泛化,是一个固定大小的序列,元组的元素可以是异构的。
函数对象
实现函数调用运算符的类称为函数对象。函数对象可以用作某些STL算法的谓词。
<functional>
多线程
单个线程可以用<thread>
建立。
多线程代码,需要考虑:几个线程不能同时读写同一个数据。为了避免这种情形,可以使用<atomic>
定义的原子性。
<condition_vaiable>
和<mutex>
提供了其他线程同步机制。
如果只需计算某个数据,得到结果,并具有相应的异常处理,使用<future>
头文件定义的async
和future
要比thread
简单。
类型特质
类型特质在<type_traits>
中定义,提供了编译期间的类型信息。编写高级模板的时候可以使用它。
标准模板库
C++ STL(Standard Template Library, STL)容器是同构的,每种容器只允许一种类型的元素。
注意:C++标准定义了每个容器和算法的接口(interface),没有定义实现。因此,不同的供应商可以自由提供不同的实现。不过,作为接口的一部分,标准定义了性能需求。
STL容器
vector
如果在程序中需要快速访问元素,但是不会频繁在中间添加或删除元素,则应该使用vector
。
在任何可能的情况下使用vector而不是C风格的数组
vector<bool>
有特定优化过的模板,不过标准没有规定如何实现优化空间。
list
双向链表
forward_list(C++11)
只支持单向遍历的单链表。内存需求比list
小。
deque
双端队列(double-ended queue)。不过行为更像vector
,而不是queue
。
提供了快速的元素访问(常量时间),在序列两端还提供了快速的插入和删除(摊还常量时间),但是在序列中间插入和删除的速度较慢(线性时间)。
如果需要在两头快速插入或删除元素,还要求快速访问所有元素,那么应该使用deque
而不是vector
。
大部分场景并不满足这种需求,因此大部分情况下,vector
或list
足以满足需求。
array
标准C风格数组的替代品。特别适用于大小固定的集合,而且没有vector
的开销。
使用array
比C风格数组有几点好处:
-
总能知道自己的大小;
-
不会自动转换为指针类型,避免某些bug;
-
大小固定,允许堆栈上分配内存,不需要像
vector
一样需要堆访问权限。
vector
、list
、deque
、array
和forward_list
容器都称为顺序容器(sequential container),因为它们保存的是元素的序列。
queue
先进先出的队列
priority_queue
优先级队列。插入和删除比简单的queue
插入和删除慢。
stack
先入后出
从技术角度,
queue
、priority_queue
和stack
容器都是容器适配器(adapter)。它们只是构建在某种标准顺序容器(vector、list或deque)上的简单接口。
set和multiset
类似数学上的集合。不过set
中按照一定的顺序保存。排序的原因是当客户枚举元素时,能够以operator<
或用户自定义的比较器的顺序出现。
set
提供了对数时间的插入、删除和查找。如果要存储重复元素,就必须使用 <set>
头文件定义的 multiset
。
map和multimap
map
保存的是键/值对。map
按照排好的顺序保存元素,排序的依据是键值而非对象值。在其他所有方面,map
和 set
是一致的。如果需要关联键和值,就应该使用 map
。
multimap
也在 <map>
中定义,它和 map
的关系等同于 multiset
和 set
的关系。确切地讲,multimap
是一个允许重复键的 map
。
set
和map
容器都是关联容器,因为它们关联了键和值。在set
中,键本身就是值。这些容器会对元素进行排序,因此这些容器称为排序或者有序关联容器。
无序关联容器/哈希表
哈希表也称为无序(unordered associative container)。有4个无序关联容器:
- unordered_map
- unordered_set
- unordered_multimap
- unordered_multiset
在C++11之前,哈希表不属于C++标准库的一部分,因此很多第三方库都用hash
作为前缀。因此C++标准委员会决定使用unordered
而不是hash
,避免名称冲突。
bitset
bitset
不是常规意义的容器,不能插入和删除元素。固定大小,不支持迭代器。可以想象成一个可以读写的布尔值序列。
bitset
不局限于int
或者其他原始类型的大小。可以在声明时指定:bitset<N>
。
vector
应该是默认使用的容器。实际上,vector
中的插入和删除常常快于list
或者forward_list
。这是现代CPU上内存和缓存的工作方式,而list
或forward_list
需要先移动到要插入或删除元素的位置上。list
或者forward_list
的内存可能是碎片化的,所以迭代慢于vector
。
STL算法
STL采取了分离数据(容器)和功能(算法)的方式。看上去有点违背了面向对象编程的思想,但是为了在STL中支持泛型编程,有必要这么做。
正交性的指导原则使算法和容器分离开,(几乎)所有算法都可以用于(几乎)所有容器。
有些容器以类方法提供了某些算法,因为泛型算法在特定容器上表现不出色。**比如,set
提供了自己的find()
算法,比泛型的find()
算法快。**如果类提供了特定的算法实现,应该使用容器特定方法的形式。
泛型算法并不是直接对容器操作。泛型算法使用迭代器(iterator)作为中介。
函数名 | 概要 |
---|---|
begin(), end() | 第一个元素和最后一个元素后面的元素,返回非const迭代器 |
以下是C++14新增的迭代器 | |
cbegin(), cend() | 第一个元素和最后一个元素后面的元素,返回const迭代器 |
rbegin(), rend() | 非const的反向迭代器 |
crbegin(), crend() | const的反向迭代器 |
非修改顺序算法
有了这些算法后,几乎不再需要编写for
循环来迭代值序列了。【就是暴力枚举的封装形式】
算法名称 | 算法概要 | 复杂度 |
---|---|---|
adjacent_find() | 查找第一个两个连续元素相等或匹配谓词的实例 | 线性 |
find()、find_if() | 查找第一个匹配值或使谓词返回true 的元素 |
线性 |
find_first_of() | 与find 类似,只是同时搜索多个元素中的一个 |
二次 |
find_if_not() | 查找第一个使谓词返回false 的元素 |
线性 |
search()、find_end() | 在序列中查找第一个(search() )或最后一个(find_end() )匹配另一个序列的子序列,或者这个子序列的元素和谓词指定的一致 |
线性 |
搜索算法
不需要元素有序
比较算法
不要求有序,最差复杂度为线性复杂度
算法名称 | 算法概要 |
---|---|
equal() | 检查相应元素是否相等或匹配谓词,以此判断两个序列是否相等 |
mismatch() | 返回每个序列第一个出现的和其他序列同一位置元素不匹配的元素 |
lexicographical_compare() | 比较两个序列,判断这两个序列的“词典顺序”。将第一个序列中的每一个元素和第二个序列中对应的元素进行比较。如果一个元素小于另一个元素,那么这个序列按照词典顺序在前面。如果两个元素相等,则按顺序比较下一个元素 |
工具算法
算法名称 | 算法概要 |
---|---|
all_of() | 序列为空或对所有谓词返回true ,则返回true ,否则返回false |
any_of() | 序列为空或至少谓词判断有一次true ,则返回true ,否则返回false |
none_of() | 序列为空或者谓词对所有元素返回false ,则返回true ,否则返回false |
count()、count_if() | 计算匹配一个值或者使谓词返回true 的元素个数 |
修改序列算法
算法名称 | 算法概要 |
---|---|
copy()、copy_backward() | 将一个序列的元素复制到另一个序列 |
copy_if() | 将谓词返回true 的元素复制到另一个序列 |
copy_n() | 复制n个元素到另一个序列 |
fill() | 所有元素设置为一个新值 |
fill_n() | 前n个元素设置为一个新值 |
generate() | 调用指定函数,为序列中每一个元素生成新值 |
generate_n() | 调用指定函数,为序列中前n个元素生成一个新值 |
move(),move_backward() | 将一个序列的元素移到另一个序列。使用了高效的移动语义 |
remove()、remove_if()、remove_copy()、remove_copy_if() | 删除匹配给定值或使谓词返回true 的元素,就地删除或将结果复制到另一个不同的序列 |
replace()、replace_if()、replace_copy()、replace_copy_if() | 将匹配特定值或导致谓词返回true 的所有元素替换为新元素,就地替换或将结果复制到新序列 |
reverse()、reverse_copy() | 掉转序列中的元素,原地操作或者复制到新序列 |
rotate()、rotate_copy() | 交换序列中前半部分和后半部分,原地操作或者复制到新序列。两个要交换的子序列不一定要一样大 |
shuffle()、random_shuffle() | 打乱元素的顺序,random_shuffle() 在C++14之后被废弃 |
transform() | 对序列中的每个元素调用一元函数,或对两个队列中的对应元素调用二元函数 |
unique()、unique_copy() | 在序列中删除连续出现的重复元素,原地删除或复制到新序列 |
操作算法
for_each
:对每个元素执行函数
交换算法
算法名称 | 算法概要 |
---|---|
iter_swap() 、swap_ranges() |
交换两个元素,或交换两个元素的序列 |
swap() |
交换两个值,在<utility> 头文件中定义 |
分区算法
排序算法
算法名称 | 算法概要 | 复杂度 |
---|---|---|
is_sorted()、is_sorted_until() | 检查一个序列是否排序,或者哪个子序列已经排序 | 线性 |
nth_element() | 重定位序列中的第n个元素,使第n个位置的元素就是排好序之后第n个位置的元素。该函数会重新安排所有元素,使第n个元素前面的所有元素都小于新的第n个元素 | 线性 |
partial_sort()、partial_sort_copy() | 只排序序列中的一部分元素:只有前n个元素(由迭代器指定)排序,其余元素不排序。在原位置排序,或者复制到新的序列 | 线性对数 |
sort()、stable_sort() | 在原位置排序,保留重复元素的顺序或不保留 | 线性对数 |
二叉搜索树算法
算法名称 | 算法概要 | 复杂度 |
---|---|---|
lower_bound()、upper_bound()、equal_range() | 查找包含给定元素的范围的头(lower_bound())、尾(upper_bound())或两端(equal_range()) | 对数 |
binary_search() | 在序列中查找一个值 | 对数 |
集合算法
这些算法最适合set
,但是也能操作大部分容器排序后的序列。【归并排序】
算法名称 | 算法概要 | 复杂度 |
---|---|---|
inplace_merge() | 在原位置将两个排好序的序列合并 | 线性对数 |
merge() | 合并两个排好序的序列,将两个序列复制到新的序列 | 线性 |
includes() | 确定是否序列中的每一个元素都在另一个序列中 | 线性 |
set_union()、set_intersection()、set_difference()、set_symmetric_difference()[并、交、差、补] | 在两个排序的序列上执行特定的集合操作,将结果复制到第三个排序的序列中 | 线性 |
堆算法
堆(heap)是一个标准的数据结构,数组或序列中的元素在其中以半排序的方式排序,因此能够快速找到“顶部”的元素。通过使用6个算法可以对序列进行堆排序。
算法名称 | 算法概要 | 复杂度 |
---|---|---|
is_heap() |
检查某个范围的元素是否是一个堆 | 线性 |
is_heap_until() |
在给定范围内的元素中查找最大的子范围 | 线性 |
make_heap() |
从一个范围的元素中创建堆 | 线性 |
push_heap() 、pop_heap() |
在堆中添加或删除元素 | 对数 |
sort_heap() |
把堆转换为升序排列的元素范围 | 线性对数 |
最大/最小算法
算法名称 | 算法概要 |
---|---|
min() 、max() |
返回两个值中最小值或最大值 |
minmax() |
以 pair 方式返回两个或多个值中的最小值和最大值 |
min_element() 、max_element() |
返回序列中最小或最大元素 |
minmax_element |
找到序列中最小和最大元素,把结果返回为 pair |
数值处理算法
<numeric>
,所有的算法都是线性复杂度,不要求排序源序列。
算法名称 | 算法概要 |
---|---|
accumulate() |
“累加”一个序列中所有的元素的值,默认行为是计算元素的和,但调用者可以提供不同的二元函数 |
adjacent_difference() |
生成一个新的序列,其中每一个元素都是对应元素和源序列中之前元素的差(或其它二元操作) |
inner_product() |
与 accumulate() 类似,但对两个序列操作。对序列中的并行元素调用二元函数(默认做乘法),通过另一个二元函数(默认加法)累加结果值。如果序列表示数学矢量,那么这个算法计算矢量的点积 |
置换算法
算法名称 | 算法概要 | 复杂度 |
---|---|---|
is_permutation() | 如果一个范围中的元素是另一个范围中的元素的转换,就返回true |
二次 |
next_permutation()、prev_permutation() | 修改序列,将序列转换为下一个或前一个排列。如果从正确排序的序列开始,连续调用可以获得所有可能的排列。如果没有更多排列,则返回false |
线性 |
STL中还缺什么
-
STL不能保证任何线程安全
-
STL没有提供任何泛型的树结构或图结构(不过大部分语言都没有提供),如果编写解析器,就需要自己实现或者寻找其他库。
最后
STL 是可扩展的,可以编写适用于现有算法和容器的容器和算法。如果 STL 没有提供需要的内容,可以考虑编写兼容 STL 的代码。