一、前言
STL:Standard Template Library(标准模板库或泛型库),是C++标准库的组成部分。STL借助模板把常用的数据结构及其算法实现了一次,并且做到了数据结构和算法的分离。STL已完全被内置到支持 C++ 的编译器中,无需额外安装,这可能也是STL被广泛使用的原因之一。STL是以源代码的形式提供的。根本上来说,STL是一些容器(封装有数据结构的模板类)、算法和其他的一些组件的集合,基本达到了各种存储方法和相关算法的高度优化。
STL的版本
HP STL
Alexandar Stepanov(STL 标准模板库之父)与 Meng Lee 合作完成。是开放源码的。
SGI STL
HP STL的一个继承版本,开源、可读性好
STLport
为了使 SGI STL 的基本代码都适用于 VC++ 和 C++ Builder 等多种编译器而提出的,开源
PL STL
参照 HP STL 实现出来的,也是 HP STL 的一个继承版本,因此该头文件中不仅含有 HP STL 的相关授权信息,同时还有 P.J.Plauger 本人的版权信息。非开源。
Rouge Wave STL
由 Rouge Wave 公司开发的,也是继承 HP STL 的一个版本,它也不是开源的。
二、STL的基本组成
STL是由容器、算法、迭代器、函数对象、适配器、内存分配器6部分构成,其中迭代器、函数对象、适配器、内存分配器是为容器、算法服务。
组成部分 | 含义 |
---|---|
容器 | 一些封装数据结构的模板类 |
算法 | STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 <algorithm> 中,少部分位于头文件 <numeric> 中 |
迭代器 | 对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂 |
函数对象 | 一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数) |
适配器 | 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器 |
内存分配器 | 为容器类模板提供自定义的内存申请和释放功能 |
在 C++ 标准中,STL被重新组织为13个头文件,如下:
<iterator> | <functional> | <vector> | <deque> | <list> |
---|---|---|---|---|
<queue> | <stack> | <set> | <map> | <algorithm> |
<numeric> | <memory> | <utility> |
注意:
或许是为了向下兼容,或许是为了内部组织规划,某些 STL 版本同时存储具备扩展名和无扩展名的两份文件,建议最好遵照C++规范,使用无扩展名的头文件。
三、容器是什么
在实际的开发过程中,存取数据的方式会直接影响到对它们进行增删改查操作的复杂程度和时间消耗。
简单的理解容器,就是一些模板类的集合,但和普通模板类不同的是,容器中封装的是组织数据的方法(也就是数据结构)。STL 提供有3类标准容器,分别是序列容器、排序容器和哈希容器,其中后两类容器有时也统称为关联容器,如下表:
容器种类 | 功能 |
---|---|
序列容器 | 主要包括 vector 向量容器、list 列表容器以及 deque 双端队列容器。之所以被称为序列容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。 |
排序容器 | 包括 set 集合容器、multiset多重集合容器、map映射容器以及 multimap 多重映射容器。排序容器中的元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以关联容器在查找时具有非常好的性能。 |
哈希容器 | C++ 11 新加入 4 种关联式容器,分别是 unordered_set 哈希集合、unordered_multiset 哈希多重集合、unordered_map 哈希映射以及 unordered_multimap 哈希多重映射。和排序容器不同,哈希容器中的元素是未排序的,元素的位置由哈希函数确定。 |
以上3类容器的存储方式完全不同,因此使用不同容器完成相同操作的效率也大不相同。因此在实际使用中,要根据具体的功能选择最适合的容器。
四、迭代器
无论是序列容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。
虽然不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,所以容器有一些操作方法是类似的,因此完全可以利用泛型的技术,将其设计成适用所有容器的通用算法,从未将容器和算法分离。
1、迭代器类别
STL标准库为每一种标准容器定义了一种迭代器类型,即不同容器的迭代器不同,因此容器的迭代器功能的强弱也不同,进而决定了容器是否哦支持STL中的某些算法。
常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 5 种。输入迭代器和输出迭代器比较特殊,它们不是把数组或容器当做操作对象,而是把输入流/输出流作为操作对象。关于输入迭代器和输出迭代器在这里暂时不多讲。
假设存在一个迭代器Iter
1)前向迭代器(forward iterator)
Iter支持++Iter、Iter++、*Iter操作、被复制或赋值、可使用==和!=运算符进行比较
2)双向迭代器(bidirectional iterator)
双向迭代器具有正向迭代器的全部功能,除此之外,还可以进行 --Iter 或者 Iter-- 操作(即一次向后移动一个位置)。
3)随机访问迭代器(random access iterator)
随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 i 是一个整型变量或常量,则 Iter 还支持以下操作:
操作 | 说明 |
---|---|
Iter+=i | 使得 Iter 往后移动 i 个元素 |
Iter-=i | 使得 Iter 往前移动 i 个元素 |
Iter+i | 返回 Iter 后面第 i 个元素的迭代器 |
Iter-i | 返回 Iter 前面第 i 个元素的迭代器 |
Iter[i] | 返回 Iter 后面第 i 个元素的引用 |
两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。另外,表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差。
五、容器对应的迭代器
1、对应关系
容器 | 迭代器类别 |
---|---|
array | 随机访问迭代器 |
vector | 随机访问迭代器 |
deque | 随机访问迭代器 |
list | 双向迭代器 |
set / multiset | 双向迭代器 |
map / multimap | 双向迭代器 |
forward_list | 前向迭代器 |
unordered_map / unordered_multimap | 前向迭代器 |
unordered_set / unordered_multiset | 前向迭代器 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
注意:
容器适配器 stack 和 queue 没有迭代器,它们包含有一些成员函数,可以用来对元素进行访问。
2、迭代器的定义方式
迭代器类别 | 格式 |
---|---|
正向迭代器 | 容器类名::iterator 迭代器名; |
常量正向迭代器 | 容器类名::const_iterator 迭代器名; |
反向迭代器 | 容器类名::reverse_iterator 迭代器名; |
常量反向迭代器 | 容器类名::const_reverse_iterator 迭代器名; |
说明:
*迭代器名:迭代器指向的元素
常量迭代器和非常量迭代器的分别在于:通过非常量迭代器还能修改其指向的元素
反向迭代器和正向迭代器的区别在于:对正向迭代器进行 ++ 操作时,迭代器会指向容器中的后一个元素;而对反向迭代器进行 ++ 操作时,迭代器会指向容器中的前一个元素。
以上 4 种定义迭代器的方式,并不是每个容器都适用。