- C++ 的 STL( 全称是 Standard Template Library 标准模板库,也叫 泛型库)是一套功能强大的 C++ 模板类,提供了 通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
- STL的分类
- 标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。这个函数库继承自 C 语言,C++ 标准库包含了所有的 C 标准库,为了支持类型安全,做了一定的添加和修改。
- 输入/输出 I/O
- 字符串和字符处理
- 数学
- 时间、日期和本地化
- 动态分配
- 其他
- 宽字符函数
- 面向对象类库: 这个库是类及其相关函数的集合。标准的 C++ 面向对象类库定义了大量支持一些常见操作的类,比如输入/输出 I/O、字符串处理、数值处理。面向对象类库包含以下内容:
- 标准的 C++ I/O 类
- String 类
- 数值类
- STL 容器类
- STL 算法
- STL 函数对象
- STL 迭代器
- STL 分配器
- 本地化库
- 异常处理类
- 杂项支持库
- 标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。这个函数库继承自 C 语言,C++ 标准库包含了所有的 C 标准库,为了支持类型安全,做了一定的添加和修改。
- C++ 标准模板库包括以下 6 个组件(核心是前三个组件),每个组件都带有丰富的预定义函数,帮助我们通过简单的方式处理复杂的任务:
组件 | 描述 |
---|---|
容器 (Containers) | 容器是用来 管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 list链表、stack栈、queue队列、vector向量、deque双向队列 等。 |
迭代器 (iterators) | 迭代器用于 遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
算法 (Algorithms) | 算法作用于容器,它们提供了 各种通用算法 。包括对容器内容执行初始化、排序、搜索和转换等操作。 |
仿函数 (Functor) | 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数) |
适配器 (Adaptor) | 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器; |
分配器 (allocator) | 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。 |
【 1. 容器 】
- 容器就是一些模板类的集合,但和普通模板类不同的是, 容器中封装的是组织数据的方法(也就是数据结构)。
- 序列式容器
包括 array、vector、list、deque 和 forward_list,其存储的都是 C++ 基本数据类型(诸如 int、double、float、string 等)或使用结构体自定义类型的元素。 - 关联式容器
关联式容器在存储元素值的同时,还会 为各元素额外再配备一个值,又称为 键,其本质也是一个 C++ 基础数据类型或自定义类型的元素。它的功能是在使用关联式容器的过程中,如果 已知目标元素的键的值,则直接通过该键就可以找到目标元素,而无需再通过遍历整个容器的方式。 - STL 容器种类和功能
排序容器和哈希容器有时也称为 关联容器 。
容器种类 | 功能 | 举例 |
---|---|---|
序列容器 | 序列容器 中的 元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。 | vector 向量容器 list 列表容器 deque 双端队列容器 |
排序容器 (有序关联式容器) | 排序容器 中的 元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以关联容器在查找时具有非常好的性能。 | set 集合容器 multiset多重集合容器 map映射容器 multimap 多重映射容器 |
哈希容器 (无序关联式容器) | 哈希容器 中的 元素是未排序的,元素的位置由哈希函数确定 。 | C++ 11 新加入 4 种无序关联式容器,分别是: unordered_set 哈希集合 unordered_multiset 哈希多重集合 unordered_map 哈希映射 unordered_multimap 哈希多重映射 |
无序关联容器/哈希容器
- 哈希容器的底层实现采用的是哈希表的存储结构。
- 哈希容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键。
- 和有序关联式容器相比,哈希容器擅长通过指定键查找对应的值(平均时间复杂度为 O(1));但对于使用迭代器遍历容器中存储的元素,哈希容器的执行效率则不如有序关联式容器。
【 2. 迭代器 】
- 简单来讲,迭代器和 C++ 的指针非常类似,它可以是需要的任意类型,通过迭代器可以指向容器中的某个元素,如果需要,还可以对该元素进行读/写操作。
- 详见:【C++ STL迭代器】iterator
【 3. 适配器 】
- 我们所说的这个“适配器”,和生活中常见的电源适配器中“适配器”的含义非常接近。我们知道,无论是电脑、手机还是其它电器,充电时都无法直接使用 220V 的交流电,为了方便用户使用,各个电器厂商都会提供一个适用于自己产品的电源线,它可以将 220V 的交流电转换成适合电器使用的低压直流电。
从用户的角度看,电源线扮演的角色就是将原本不适用的交流电变得适用,因此其又被称为电源适配器。
- 再举一个例子,假设一个代码模块 A,它的构成如下所示:
class A{ public: void f1(){} void f2(){} void f3(){} void f4(){} };
- 现在我们需要设计一个模板 B,但发现,其实只需要组合一下模块 A 中的 f1()、f2()、f3(),就可以实现模板 B 需要的功能。其中 f1() 单独使用即可,而 f2() 和 f3() 需要组合起来使用,如下所示:
class B { private: A * a; public: void g1() { a->f1(); } void g2() { a->f2(); a->f3(); } };
- 可以看到,就如同是电源适配器将不适用的交流电变得适用一样,模板 B 将不适合直接拿来用的模板 A 变得适用了,因此我们可以 将模板 B 称为 B 适配器。
- 容器适配器也是同样的道理,简单的理解 容器适配器,其就是 将不适用的序列式容器(包括 vector、deque 和 list)变得适用。容器适配器的底层实现和模板 A、B 的关系是完全相同的,即通过封装某个序列式容器,并重新组合该容器中包含的成员函数,使其满足某些特定场景的需要。
- 容器适配器本质上还是容器,只不过此容器模板类的实现,利用了大量其它基础容器模板类中已经写好的成员函数。当然,如果必要的话,容器适配器中也可以自创新的成员函数。
- 需要注意的是,STL 中的 容器适配器内部使用的基础容器并不是固定的,用户可以在满足特定条件的多个基础容器中自由选择。
- STL 提供了 3 种容器适配器,分别为 stack 栈适配器、queue 队列适配器以及 priority_queue 优先权队列适配器。其中,各适配器所使用的默认基础容器以及可供用户选择的基础容器,如表 所示。
容器适配器 | 基础容器筛选条件 基础容器需包含以下成员函数: | 满足条件的基础容器 | 默认使用的基础容器 |
---|---|---|---|
stack | empty() size() back() push_back() pop_back() | vector、deque、list。 | deque |
queue | empty() size() front() back() push_back() pop_front() | deque、list。 | deque |
priority_queue | empty() size() front() push_back() pop_back() | vector、deque。 | vector |
- 不同场景下,由于不同的序列式容器其底层采用的数据结构不同,因此容器适配器的执行效率也不尽相同。但通常情况下,使用默认的基础容器即可。当然,我们也可以手动修改,具体的修改容器适配器基础容器的方法,后续讲解具体的容器适配器会详细介绍。