首页 > 编程笔记 > C++笔记 阅读:60

STL是什么,C++ STL简介(新手必看)

通义灵码
STL 是一个具有工业强度的、高效的 C++ 函数库。据 C++ 世界的“前辈”们介绍,STL 最初诞生于惠普实验室,是由 Alexander Stepanov、Meng Lee 和 David R.Musser 在惠普实验室工作时开发出来的。

经过不断的发展,STL 形成了不同的版本,并得到广泛的应用。如今,它已被纳入 C++ 标准函数库(C++ Standard Library),是 ANSI/ISO C++ 标准中最具创新性的组成部分。

从广义上讲,STL 主要分成三大核心部分:算法(algorithm)、容器(container)和迭代器(iterator)。此外,还有容器适配器(container adaptor)、函数对象(functor)等组件。

几乎 STL 的所有代码都采用模板类和模板函数的方式,与传统的由函数和类库相比,它提供了更好的代码复用机会。

接下来,我们将对STL的三大核心部分做一个简单的了解。

C++ STL容器

在程序中,我们通常使用各种数据结构(例如链表、队列等)来组织和管理数据。常用的数据结构数量有限,并且每个人实现的相同数据结构的代码都十分相似,只是为了适应不同的数据类型变化而在细节上有所不同。

正是因为如此,STL 容器允许重复利用已有的实现,它提供了一些基础数据结构的模板类,例如 list<T>、queue<T> 等。通过设置特定数据类型为模板参数 T,我们可以构造出适用于特定类型的数据结构。这种方式可以简化那些重复而乏味的基础数据结构的构造工作,从而大大提高开发效率。

C++ STL算法

算法是对容器中的数据进行处理的各种方法。与数据结构类似,程序中的算法也具有很大的通用性。不同的人开发的稳定排序算法在本质上是一样的,最多只是在操作不同数据类型的数据上有所差异。没必要重复发明轮子两次。

STL 将编程时常见的一些通用算法收集起来,并通过与迭代器和容器配合使用,使它能够适应不同的数据结构和数据类型。当我们需要使用这些通用算法时,直接使用 STL 提供的版本即可。

例如,直接使用 STL 中的 stable_sort() 函数配合容器就能完成数据的稳定排序,无须我们重新实现一次。这样做不仅提高了代码复用率,也提高了开发效率和质量。

C++ STL迭代器

如果容器用于容纳数据,算法用于处理数据,那么迭代器就像胶水一样将算法和容器紧密结合在一起。

迭代器提供了一种统一的接口,供算法访问各种容器中的数据。这使得算法的实现与容器的具体类型无关。如果没有迭代器,处理数据的算法就很可能与容纳数据的容器搅和在一起。为了处理容器中的数据,我们不得不为每种特定的容器实现专门的算法,这样会导致数据和算法紧密耦合,算法也就丧失了它的通用性,无法适用于其他容器。

STL 中的这三大组成部分,容器负责容纳数据,算法负责处理数据,而迭代器将两者联系起来。正是因为算法、容器和迭代器这三个核心部分相互配合、相互作用,使得 STL 成为一个有机的整体,如下图所示。


图 1 STL的三大核心部分

C++ STL的使用

因为 STL 已经成为 C++ 标准库的一部分,所以在 C++ 代码中可以直接使用 STL,无须额外的操作。STL 中的各个容器和算法根据功能的不同被组织在多个头文件中。要在程序代码中使用 STL,只需包含相应的头文件并使用对应的 std 命名空间即可。

常用的 STL 头文件及其作用如下表所示。

表 1 STL 常用的头文件及其作用
头文件 说明
<queue> 队列容器,按照先进先出的规则排列容器中的数据
<stack> 堆栈容器,按照后进先出的规则排列容器中的数据
<vector> 动态数组容器,也称为向量容器,连续存储容器中的元素,并支持随机访问。同时,可以根据需要动态改变容器的大小。它是 C++中最常用的容器,通常用于保存一些相互之间没有关联的批量数据
<forward_list> forward_list 是一个基本的单向链表容器。只提供了前向迭代器,只能前向访问数据元素。在执行插入/删除操作后,容器中的其他节点不会受到影响,因而其相关操作的性能较高
<map> 映射容器,由(键,值)数据对组成的集合,这些数据以键值的某种规律排列,可以通过键值对中的键访问对应的值,因而被称为映射容器。其中,map 容器中的键值对是一一对应的关系,而 multimap 容器中一个键可以对应多个值。
<multimap> 作为映射容器,map 和 multimap 由来已久,其底层由红黑树实现,而 unordered_map 和 unordered_multimap 是最新的 C++标准新添加到 STL 的映射容器,其底层由哈希表实现
<unordered_set> 集合容器,其中的数据元素分布在一棵红黑树的各个节点,节点之间以某种规则排序。因为其中的数据都已经排序完成,所以 set 容器的查找操作特别高效。在 set 容器中,不能含有两个相同的元素,而 multi_set 容器允许含有相同元素。其中,unordered_set 和 unordered_multiset 是 C++标准新加入 STL 中的集合容器,其底层由哈希表实现
<algorithm> 它是所有 STL 头文件中最大的一个,也是最常用的一个。它由很多模板函数组成,这些函数相互独立构成 STL 中的通用算法,包括比较、交换、查找、排序等
<functional> 定义了一些类模板,用于创建函数对象
<string> 字符串类,用于处理和操作字符串
<regex> 正则表达式,用于对字符串进行模式匹配和处理
<memory> 其中定义了与内存操作相关的组件,如智能指针

笔者一直在强调 STL 的优点及其简单易用性,但仅凭言语可能难以让大家真正感受到。接下来,我们将通过一个实际的例子,将 STL 应用到工资程序中,以展示它如何使程序变得更加优雅。
  1. // ...
  2. #include <vector> // 为了使用 vector 容器
  3. #include <algorithm> // 为了使用 count_if() 算法
  4.  
  5. using namespace std; // 使用 std 命名空间
  6.  
  7. // 使用 STL 改写后的可以统计高工资员工数的 salarySys 类
  8. class SalarySys
  9. {
  10. // ...
  11. public:
  12. // ...
  13. int GetHighSalaryrCount()
  14. {
  15. int nTotal = 0;
  16. // 使用 count_if() 算法统计工资大于 1000 元的员工人数
  17. // 员工数据已经事先保存到 m_vecEmp 容器中
  18. nTotal = count_if(m_vecEmp.begin(), // 统计范围开始
  19. m_vecEmp.end(), // 统计范围结束
  20. [](Employee* p) -> bool // 统计规则
  21. {
  22. return p->GetSalary() > 1000;
  23. });
  24. return nTotal; // 返回统计结果
  25. }
  26. // ...
  27. private:
  28. vector<Employee*> m_vecEmp; // 使用容器取代数组
  29. };
在这段程序中,我们使用了 vector 容器代替数组来组织和管理员工数据,同时使用 count_if() 算法代替 for 循环来统计符合条件(工资大于 1000 元)的员工人数。正是它们的配合使用,使得整个程序变得“优雅”起来。

读者可能会问,STL 的使用确实简单,但其“优雅”之处在哪里?STL 的优势究竟体现在哪些方面?STL 的优势有以下几点:
1) 使用 vector 容器替换数组来管理数据,使内存空间的使用更加合理。数组的一个缺点是其大小固定,必须在定义时指定大小,这可能导致内存资源浪费或空间不足。

相比之下,在“优雅”的工资程序中使用的 vector 容器则很好地弥补了这一缺点,其大小可以根据需要动态调整,避免了内存浪费和容量不足的问题。

2) 使用 count_if() 算法替代 for 循环,提高了开发效率并增强了程序的可维护性。在“粗鲁”的工资程序中,我们使用 for 循环统计工资大于 1000 元的员工人数,这虽然有效,但显得生硬。

使用 STL 中的 count_if() 算法配合 vector 容器,我们可以利用 begin() 和 end() 函数方便地指定统计范围,并通过 Lambda 表达式灵活定义统计规则。

STL 的优势在于容器和算法的紧密配合,它不仅弥补了传统编程方法的固有缺点,提高了开发效率,还由于容器和算法的通用性,使程序更易于维护和扩展。

相关文章