STL是什么?
STL即(Standard Template Library)标准模板库,提供了常见的数据结构和算法函数等,其下共包含六大组件:
- 容器
- 算法
- 迭代器
- 仿函数
- 适配器
- 空间配置器
本篇重点介绍容器的使用和简单的底层实现原理,其它的部分简略带过
六大组件互相的关系大致如下图:
容器
容器即是存储数据的类模板,本质即是封装了常用的数据结构,主要可以分为两类:
- 序列式容器
关联式容器将相同类型的元素以有序的方式线性组织起来,如下:- vector:动态数组,支持快速的随机访问
- deque:双端队列,支持快速插入和删除
- list:双向循环链表,支持快速插入和删除,但不支持随机访问
- 关联式容器
- set/multiset:集合/多重集合(key形式)
- map/multimap:映射/多重映射(key-value形式)
- unordered_set/unordered_multiset:无序集合/无序多重集合
unordered_map/unordered_multimap:无序映射/无序多重映射(哈希表)
还有些常见的“容器”,如stack/queue和priority_queue,这些则是属于容器适配器的组件,它们是在基本容器(vector,deque等)之上提供特定接口的适配器
迭代器
什么是迭代器?迭代器本质就是用于遍历和操作容器中的元素的一种工具,它提供了访问容器元素的方法,可以简单理解为数组名(即指针)
为什么需要迭代器?迭代器最重要的意义是提供了一种统一的方式来操作容器中的元素,无需在意元素类型,底层数据结构实现细节,都通过迭代器来进行访问
迭代器的类型大致分为以下五种:
- 单向迭代器:仅支持++操作(forward_list)
- 双向迭代器:仅支持++和–操作(list)
- 随机访问迭代器:支持+和-的操作(deque、vector)
- 反向迭代器:即与正向迭代器相反的操作的迭代器
- 常量迭代器:不支持修改的迭代器
不同的迭代器只是性质不一样,但使用起来时都是相同的使用iterator进行操作,iterator底层的实现可能是指针,也可能是封装的其它数据结构,如list的迭代器封装的即是链表节点,无论封装的什么,都是为了统一访问元素的方式
算法、仿函数
算法和仿函数即是为了方便C++开发,提前写好的一些常用算法和“函数”,便于使用
算法常见的即sort,swap,max/min等,详情可以去看官方文档,重点说一下仿函数
-
什么是仿函数?
仿函数又被称为函数对象,本质是一个类,重载了函数调用运算符operator(),使得这个类的对象可以像函数一样被调用,故此又称仿函数 -
常见的即自定义Compare比较类,实例如下(实际中可以提供更多样不同的类型的比较)
class Compare { public: bool operator()(int num1, int num2) { return num1 < num2; } }; int main() { Compare cmp; std::cout << cmp(1, 2) << std::endl; sort(v.begin(), v.end(), Compare()); sort(v.begin(), v.end(), greater<int>()); for(auto i : v){ cout << i << endl; } sort(v.begin(), v.end(), less<int>()); for(auto i : v){ cout << i << endl; } return 0; }
实例中的less和greater即是functional中为我们提供的仿函数类,一般都是用于sort等函数的函数比较器使用
适配器和空间配置器
适配器
在C++的STL中,适配器是一种设计模式,它的目的是使得原本接口不兼容的类可以一起工作。在STL中,适配器主要有三种类型:
-
容器适配器:容器适配器是一个封装了序列容器的类模板,它在一般序列容器的基础上提供了一些不同的功能²。例如,
stack
是一个封装了deque
容器的适配器类模板,默认实现的是一个后入先出(Last-In-First-Out,LIFO)的压入栈⁴。queue
和stack
大同小异,只是开放的函数是符合先进先出(FIFO)原则的函数。 -
函数对象适配器:函数对象适配器可以将一般迭代器的赋值(assign)操作,转化为插入(insert)操作²。有专司头端插入的
front_insert_iterator
,专司尾部插入的back_insert_iterator
,还有就是insert_iterator
,其可以实现从任意位置执行插入操作。 -
迭代器适配器:迭代器适配器可以将迭代器绑定到一个stream(数据流)对象身上。绑定到
istream
对象(例如std::cin
)者,称为istream_iterator
,拥有输入能力;绑定到ostream
对象(例如std::cout
)者,称为ostream_iterator
,拥有输出能力。
空间配置器
C++的STL中的空间配置器是一个非常重要的组件,它负责内存的配置和管理。空间配置器的存在可能平常并不太能感知到,但其为STL的操作对象提供了存储空间(内存池)。
什么是空间配置器?
空间配置器是一个实现了动态空间配置、空间管理、空间释放的class template。它的主要任务是为STL容器分配和释放内存。
为什么需要空间配置器?
空间配置器的存在可以帮助我们更有效地管理内存。它可以解决小块内存频繁申请释放带来的性能问题,以及小块内存带来的内存碎片问题。
空间配置器是如何工作的?
STL的空间配置器有两级:第一级配置器和第二级配置器。
-
第一级配置器:当配置区块大于128bytes时,将其视作足够大,直接使用malloc和free。
-
第二级配置器:当配置区块小于128bytes时,将其视作过小,为降低额外负担,采用内存池的管理方式。内存池的思想是一次向heap申请一块很大的内存,如果申请小块内存的话就直接到内存池中去要。
在STL中,空间配置器的基本操作包括:
- 内存配置:由成员函数allocate()负责。
- 内存释放:由成员函数deallocate()负责。
- 对象构造:由成员函数construct()负责。
- 对象析构:由成员函数destroy()负责。
关于容器的详解后续会补充STL各个常用的容器的模拟实现,以此了解其底层原理(待)