C++初阶(十)--初识STL
目录
一、STL 是什么?
STL 是 C++ 标准库的一部分,它提供了一系列通用的模板类和函数模板,这些模板都是经过精心设计和优化的,可以用来处理各种常见的编程任务,比如数据存储(像数组、链表、树这些结构相关的事儿)、数据处理(排序、查找等操作)以及算法实现等。简单说呢,就是别人已经帮咱们把很多常用的、好用的代码模板都准备好了,咱们只要知道怎么用就行了。
二、STL的六大组件
STL(Standard Template Library,标准模板库)主要由以下六大组件构成,每个组件在 STL 体系中都起着至关重要的作用,它们相互协作,为 C++ 程序员提供了强大且便捷的编程工具。
容器(Containers)
容器是用于存储数据元素的对象,STL 提供了多种不同类型的容器,以满足各种数据存储和操作需求。
-
顺序容器(Sequential Containers):
- vector:类似于动态数组,可以在运行时动态调整大小。元素在内存中是连续存储的,这使得它能够快速地随机访问元素,但在中间插入或删除元素时,可能需要移动大量其他元素。例如,
vector<int> vec; vec.push_back(1); vec.push_back(2);
创建了一个存放整数的vector
容器,并依次添加了两个元素。 - list:是一个双向链表结构,每个节点包含一个数据元素以及指向前一个和后一个节点的指针。在链表中间插入和删除元素相对容易,只需调整相关节点的指针即可,不过随机访问元素的速度相对较慢,因为需要从链表头或链表尾逐个遍历节点才能访问到目标元素。比如,
list<int> lst; lst.push_back(1); lst.push_back(2);
创建了一个存放整数的list
容器并添加了两个元素。 - deque:即双端队列,它结合了数组和链表的一些特点。在两端(头部和尾部)可以像链表一样方便地插入和删除元素,同时又能像数组一样支持快速的随机访问(但随机访问速度比
vector
稍慢一些)。例如,deque<int> dq; dq.push_back(1); dq.push_back(2); dq.push_front(3);
创建了一个存放整数的deque
容器,不仅能从尾部添加元素,还能从头部添加元素。
- vector:类似于动态数组,可以在运行时动态调整大小。元素在内存中是连续存储的,这使得它能够快速地随机访问元素,但在中间插入或删除元素时,可能需要移动大量其他元素。例如,
-
关联容器(Associative Containers):
- set:是一种存储唯一元素的容器,它会自动按照元素的某种特定顺序(通常是根据元素的比较函数确定的顺序,如对于整数是按照大小顺序,对于字符串是按照字典序)对元素进行排序。例如,
set<int> s; s.insert(3); s.insert(1); s.insert(2);
创建了一个存放整数的set
容器,并插入了几个元素,插入后元素会自动按照从小到大的顺序排列。当试图插入一个已经存在于set
中的元素时,插入操作将不会成功,因为set
要保证元素的唯一性。 - map:它是一种键值对容器,每个元素由一个键(key)和一个值(value)组成。通过键可以快速地找到对应的 值,并且键在总体内是唯一的。例如,
map<int, string> m; m.insert(make_pair(1, "one")); m.insert(make_pair(2, "one"));
创建了一个存放整数键和字符串值的map
容器,并插入了两对键值对。类似于set
,map
也会根据键的比较函数自动对键值对进行排序,以便于快速查找。
- set:是一种存储唯一元素的容器,它会自动按照元素的某种特定顺序(通常是根据元素的比较函数确定的顺序,如对于整数是按照大小顺序,对于字符串是按照字典序)对元素进行排序。例如,
迭代器(Iterators)
迭代器是一种类似于指针的对象,它提供了一种统一的方式来访问容器中的元素,使得程序员可以在不了解容器内部实现细节的情况下,对不同类型的容器进行遍历、修改等操作。
- 不同类型的容器有不同类型的迭代器,比如:
- 正向迭代器:用于按照容器中元素的存储顺序从前往后遍历元素。例如,对于
vector<int>
容器,vector<int>::iterator it = vec.begin();
,这里vec.begin()
返回的就是指向容器第一个元素的正向迭代器,通过it++
可以逐个访问后面的元素。 - 反向迭代器:用于按照与容器中元素存储顺序相反的方向从后往前遍历元素。例如,对于
list<int>
容器,list<int>::reverse_iterator rit = lst.rbegin();
,这里lst.rbegin()
返回的就是指向容器最后一个元素的反向迭代器,通过rit--
可以逐个访问前面的元素。 - 常量迭代器:用于在不修改容器元素的情况下遍历元素,有正向常量迭代器和反向常量迭代器之分。例如,对于
set<int>
容器,set<int>::const_iterator cit = s.begin();
,在这种情况下,虽然可以访问元素,但不能对其进行修改。
- 正向迭代器:用于按照容器中元素的存储顺序从前往后遍历元素。例如,对于
算法(Algorithms)
STL 提供了大量的算法来对容器中的元素进行各种操作,这些算法通过迭代器来指定操作的范围,从而实现了对不同类型容器的通用性。
-
排序算法(Sorting Algorithms):
std::sort
:这是最常用的排序算法之一,它可以对一个给定范围内的元素进行快速排序。例如,对于vector<int>
容器vec
,std::sort(vec.begin(), vec.end());
会将容器中的整数按照从小到大的顺序排列。它的时间复杂度通常为 O (n log n),其中 n 是要排序的元素个数。std::stable_sort
:与std::sort
类似,但它在排序过程中会保证相等元素的相对顺序不变。例如,在对一个包含重复元素的容器排序时,如果使用std::stable_sort
,那么原来在前面的重复元素在排序后仍然会在前面。
-
查找算法(Searching Algorithms):
std::find
:用于在一个给定范围内寻找指定的元素。例如,对于list<int>
容器lst
,std::find(lst.begin(), lst.end(), 5);
会在容器中寻找整数 5,如果找到则返回指向该元素的迭代器,否则返回一个特殊的标志(如list<int>::end()
),表示没有找到。std::binary_search
:适用于已经排序的容器,它利用二分法来快速查找指定的元素。例如,对于已经排序好的vector<int>
容器vec
,std::binary_search(vec.begin(), vec.end(), 5);
会判断容器中是否存在整数 5。如果存在,返回 true;否则,返回 false。
-
其他算法(Other Algorithms):
std::copy
:用于将一个范围内的元素复制到另一个位置。例如,对于vector<int>
容器vec1
和vec2
,std::copy(vec1.begin(), vec1.end(), vec2.begin());
会将vec1
的所有元素复制到vec2
的开头部分。std::accumulate
:用于计算一个范围内的元素的总和或其他可累积的属性。例如,对于vector<int>
容器vec
,std::accumulate(vec.begin(), vec.end(), 0);
会计算容器中所有元素的总和,这里第二个参数 0 表示初始值。
函数对象(Function Objects)
函数对象,也称为仿函数(Functors),是一种可以像函数一样被调用的对象。它们在 STL 中有着重要的作用,常常被用来替代普通的函数作为算法的参数,以实现更灵活的操作。
- 例如,定义一个比较函数对象来实现特定的比较规则:
class MyComparator { public: bool operator()(int a, int b) { return a > b; } };
- 这里定义了一个名为
MyComparator
的函数对象,它实现了一个比较规则,即比较两个整数时,返回较大的数在前的结果。可以将这个函数对象作为参数传给std::sort
算法,例如:vector<int> vec; vec.push_back(1); vec.push_back(2); std::sort(vec.begin(), vec.end(), MyComparator());
,这样就会按照我们定义的比较规则(较大数在前)对容器中的元素进行排序。
适配器(Adapters)
适配器是一种将一种接口转换为另一种接口的组件,在 STL 中主要用于将不同类型的容器、迭代器或函数对象进行转换,以满足特定的应用需求。
-
容器适配器(Container Adapters):
- stack:它是基于其他容器(如
vector
、list
等)实现的一种后进先出(LIFO)的数据结构。例如,以vector
为基础实现的stack
可以这样创建:stack<int, vector<int>> stk;
,这里第一个参数表示栈中元素的类型,第二个参数表示所基于的容器类型。通过push
、pop
等操作可以对栈进行操作。 - queue:也是基于其他容器(如
vector
、list
等)实现的一种数据结构,不过它是先进先出(FIFO)的。例如,以list
为基础实现的queue
可以这样创建:queue<int, list<int>> q;
,同样,通过push
、push_front
、pop
等操作可以对队列进行操作。
- stack:它是基于其他容器(如
-
迭代器适配器(Iterator Adapters):
- reverse_iterator:它是一种将正向迭代器转换为反向迭代器的适配器。例如,对于
vector<int>
容器vec
,vector<int>::reverse_iterator rit = vec.rbegin();
,这里vec.rbegin()
就是利用reverse_iterator
适配器将正向迭代器转换为反向迭代器的结果,通过rit--
可以按照与容器中元素存储顺序相反的方向从后往前遍历元素。
- reverse_iterator:它是一种将正向迭代器转换为反向迭代器的适配器。例如,对于
-
函数对象适配器(Function Object Adapters):
- bind:它是一种将函数对象与参数进行绑定的适配器。例如,假设有一个函数对象
MyFunction
和一些参数,通过bind
可以将这些参数绑定到MyFunction
上,形成一个新的函数对象,以便于在特定的应用场景下使用。
- bind:它是一种将函数对象与参数进行绑定的适配器。例如,假设有一个函数对象
空间配置器(Allocator)
空间配置器是负责为容器分配和回收内存空间的组件。它就像是一个幕后的 “内存管家”,当容器需要存储新的元素、扩展容量等情况时,就由空间配置器来处理相关的内存操作,确保容器能够顺利地获取到所需的内存空间,并且在元素被移除或容器被销毁时,合理地回收内存,避免内存泄漏等问题。
- 一级空间配置器(__malloc_alloc_template):实际上是对
malloc
和free
函数的一种包装。它在内部基本就是直接调用malloc
函数来分配内存,调用free
函数来回收内存。例如,当容器需要分配一定大小的内存空间时,一级空间配置器会像这样操作:void* ptr = __malloc_alloc_template<>::allocate(n);
,这里n
是需要分配的 ,__malloc_alloc_template<>::allocate
函数内部就是调用malloc
函数来完成分配的。 - 二级空间配置器(__allocator_base):引入了内存池的概念。它会预先分配一块较大的内存池,然后从这个内存池中为容器分配所需的内存空间。比如,它可能会先分配一块 16KB 的内存池,当容器需要分配内存时,就从这个内存池中取出合适大小的 ,以便给容器。对于较小的内存块(通常定义为小于等于 128 字节),二级空间配置器会使用一种叫做 “自由链表” 的结构来管理。自由链表就是把同类型(即相同大小)的空闲内存块用链表的形式连接起来,当需要分配这种小内存块时,就从相应的自由链表中取出一个空闲块给容器;当容器释放这种小内存块时,又会把它放回相应的自由链表中,以便下次再利用。对于较大的内存块(大于 128 字节),二级空间配置器会像一级空间配置器一样,直接调用
malloc
函数来分配内存,并在回收时调用free
函数。
这就是 STL 的六大组件啦,它们相互配合,为 C++ 程序员提供了高效、便捷的编程体验,使得处理各种数据结构和算法问题变得更加容易。
三、为什么要用 STL?
- 提高开发效率:不用再从头去写那些处理数据结构和算法的代码啦,直接拿 STL 里的现成模板来用就行,能节省大量的时间和精力,让我们可以把更多的心思放在解决具体的业务问题上呢。
- 代码质量保证:STL 里的模板都是经过专业人员精心设计和优化的,所以在性能、稳定性等方面都有很好的表现。使用 STL 可以让我们的代码质量更高,减少因为自己编写代码不当而导致的各种问题呢。
- 代码的通用性和可移植性:因为 STL 是 C++ 标准库的一部分,所以在任何支持 C++ 标准的环境下都可以使用,这使得我们的代码具有很好的通用性和可移植性,不管是在 Windows、Linux 还是其他操作系统上,只要是 C++ 能跑的地方,STL 就能派上用场啦。
本篇博客只是对STL初步认识,后面会对STL中的东西具体讲解,欢迎评论区留言
原文地址:https://blog.csdn.net/weixin_65132948/article/details/143508096
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!