
什么是 STL?
- C++ 保留了 C 的标准库,另外还提供了一个基于模板实现的标准模板库(Standard Template Library,简称 STL):
- 实现了一些面向序列数据的表示及常用的操作。
- 支持了一种抽象的编程模式,该模式隐藏了一些低级的程序元素,如数组、链表、循环等,使得编程效率更高。
- STL 由多人参与设计,C++ 设计者更看重 C++ 的基于 STL 的编程。这些年的 C++ 更新,主要是扩充了 C++ 的模板库。
STL 包含什么?
- 容器
- 用于存储序列化的数据元素,如:向量、队列、栈等。
- 算法
- 用于实现对容器中的数据元素进行一些常用的操作,如:查找、排序、统计、求和等一系列算术运算。
- 迭代器
- 属于一种 智能指针,它们指向容器中的数据元素,用于对容器中的数据元素进行遍历和访问。
- 迭代器是容器和算法之间的 桥梁:传给算法的不是容器,而是指向容器中元素的迭代器,这样使得算法不依赖于具体的容器,从而提高算法的通用性。(使用迭代器有时会让简单的程序显得繁琐,因此 C++20 保留了迭代器的用法,也增加了直接将容器赋给算法的方式)
基于 STL 编程的例子
从键盘输入一批正整数,然后对它们求最大数、求和、排序、输出
1 |
|
1 | // 利用算法 max_element 计算并输出容器 v 中的最大元素 |
用 STL 来实现上述程序的功能不需要涉及一些低级的程序元素,如数组、链表、循环,使得程序设计变得更抽象、便捷和可靠!
容器
容器是由同类型元素构成的、长度可变的元素序列。容器是用类模板来实现的,模板的参数包含了容器中元素的类型。
STL 中提供了很多种容器,虽然这些容器都具有一些相同的操作,但由于它们采用了不同的内部实现方法,因此,不同的容器往往适合于不同的应用场合。
STL 的主要容器
vector< 元素类型 >
-
用于需要 快速定位(访问)任意位置上的元素 以及主要在元素序列的 尾部增加 / 删除元素 的场合。
-
在头文件
vector
中定义,用 动态数组 实现。
list< 元素类型 >
-
用于经常在元素序列中 任意位置上插入 / 删除元素 的场合。
-
在头文件
list
中定义,用 双向链表 实现。
deque< 元素类型 >
-
用于主要在元素序列的 两端增加 / 删除元素 以及需要 快速定位(访问)任意位置上的元素 的场合。
-
在头文件
deque
中定义,用 分段的连续空间结构 实现(大体是链表,每个节点内部使用数组)。
stack< 元素类型 >
-
用于仅在元素序列的 尾部增加 / 删除元素 的场合。
-
在头文件
stack
中定义,基于 deque、list 或 vector 来实现。
queue< 元素类型 >
-
用于仅在元素序列的 尾部增加 、 头部删除元素 的场合。
-
在头文件
queue
中定义,基于 deque 或 list 来实现。
priority_queue< 元素类型 >
-
它与 queue 的操作类似,不同之处在于:每次增加 / 删除元素之后,它将对元素位置进行调整,使得头部元素总是最大的。也就是说,每次删除的总是最大(优先级最高)的元素。
-
在头文件
queue
中定义,基于 deque 或 vector 来实现。 -
上面三类容器是作为适配器提供的,方便使用。
map< 关键字类型,值类型 > 和multimap< 关键字类型,值类型 >
-
用于需要 根据关键字来访问元素 的场合。
-
容器中每个元素由关键字和值构成(它属于一个 pair 结构类型,该结构有两个成员:first 和 second,first 对应关键字,second 对应值),元素是根据其关键字排序的。
-
对于 map,不同元素的关键字 不能 相同;对于 multimap,不同元素的关键字 可以 相同。
-
它们在头文件
map
中定义,常常用 某种二叉树 来实现。
set< 元素类型 > 和multiset< 元素类型 >
-
它们分别是 map 和 multimap 的特例,每个元素只有关键字而没有值,或者说,关键字与值合一了。
-
在头文件
set
中定义。
basic_string< 字符类型 >
-
与 vector 类似,不同之处在于其元素为字符类型,并提供了一系列与字符串相关的操作。
-
string 和 wstring 分别是它的两个实例:
basic_string<char>
basic_string<wchar_t>
- wchar_t 是 C++ 和 C 编程语言中用来表示宽字符(wide character)的一种数据类型。它设计用于处理比常规字符(char)占用更多存储空间的字符,主要用于支持多种语言的多字节或宽字符编码(如 Unicode)。
-
在头文件
string
中定义。
容器的基本操作
容器类提供了一些成员函数来实现容器的基本操作,其中包括:
-
往容器中增加元素
-
从容器中删除元素
-
获取容器中指定位置的元素
-
在容器中查找元素
-
......
这些成员函数往往具有通用性(大部分容器类都包含它们)。
注意:如果容器的元素类型是一个 类,则针对该类可能需要:
-
自定义拷贝构造函数 和赋值操作符重载函数
- 容器内部进行的一些操作中可能会创建新的元素(对象的拷贝构造)或进行元素间的赋值(对象赋值)。
-
重载小于操作符(<)
- 容器内部进行的一些操作中可能要对元素进行“小于”比较运算。
例:利用容器 vector 来实现序列元素的存储与处理(不采用 STL 的算法)
1 |
|
例:利用容器 map 来实现电话号码簿功能
1 |
|
程序也可以写成:
1 |
|
string 的基本操作
在 C++ 标准库中还提供了一个基于字符数组实现的字符串类型 string:
1 |
string 会自动管理字符串的内存空间,并提供了字符串的基本操作:
变量的定义和初始化
1 | string s1("1234"),s2(s1),s3; |
取元素
1 | s1[i] |
取长度
1 | s1.length() |
输入 / 输出
1 | cin >> s1; |
拷贝
1 | s3 = "abcd"; // 把 "abcd" 复制到 s3 中 |
拼接
1 | s1 += s3; // 把 s3 拼接到 s1 上 |
比较
1 | ... s1 < s2 ... |
子串查找
1 | s1.find(s2) // 从 s1 的头开始查找 s2,返回找的位置或 string::npos(未找到) |
子串替换
1 | s1.replace(i,count,s2) // 把 s1 中从位置 i 开始的 count 个字符替换成 s2 |
迭代器
迭代器(iterator)属于一种 智能指针,它们指向容器中的元素,用于对容器中的元素进行访问和遍历。在 STL 中,迭代器是作为类模板来实现的(在头文件 iterator 中定义),它们可分为以下几种类型:
-
输出迭代器(output iterator,记为:OutIt)
- 只能修改 它所指向的容器元素
- 间接访问(
*
) ++
-
输入迭代器(input iterator,记为:InIt)
- 只能读取 它所指向的容器元素
- 间接访问(
*
)和元素成员间接访问(->
) ++
、==
、!=
。
-
前向迭代器(forward iterator,记为:FwdIt)
- 可以读取 / 修改 它所指向的容器元素,只能 ++,不能 --
- 元素间接访问(
*
)和元素成员间接访问(->
) ++
、==
、!=
-
双向迭代器(bidirectional iterator,记为:BidIt)
可以读取 / 修改
它所指向的容器元素,可以 ++ 和 --- 元素间接访问(
*
)和元素成员间接访问(->
) ++
、--
、==
、!=
操作
-
随机访问迭代器(random-access iterator,记为:RanIt)
- 可以读取 / 修改 它所指向的容器元素
- 元素间接访问(
*
)、元素成员间接访问(->
)和下标访问元素([]
) ++
、--
、+
、-
、+=
、-=
、==
、!=
、<
、>
、<=
、>=
注意:指向数组元素的普通指针可以看成是随机访问迭代器
各容器允许的迭代器类型
由于采用了不同的内部实现,因此,不同的容器所提供的迭代器类型会有所不同:
-
对于
vector
、deque
以及basic_string
容器类,与它们关联的迭代器类型 只能为随机访问迭代器(RanIt) -
对于
list
、map/multimap
以及set/multiset
容器类,与它们关联的迭代器类型 只能为双向迭代器(BidIt)。 -
queue
、stack
和priority_queue
容器类,不支持迭代器 可通过容器类的成员函数begin
和end
等获得容器的 首尾迭代器。
迭代器之间的相容关系
迭代器类型之间存在下面的相容关系:
![[Pasted image 20241125163641.png]]
在需要箭头左边迭代器的地方可以用箭头右边的迭代器去替代。
例:利用 STL 的容器 list 和迭代器实现求解约瑟夫问题
约瑟夫(Josephus)问题:有 N 个小孩(编号为 0~N-1)围坐成一圈,从某个小孩开始顺时针报数(1、2、...),报到 M 的小孩从圈子离开,然后,从下一个小孩开始重新报数,每报到 M,相应的小孩从圈子离开,......,最后一个离开圈子的小孩为胜者,问胜者是哪一个小孩?
1 |
|
上面程序中的 list 也可以换成 vector,但由于需要经常在容器的任意位置上删除元素,而 list 容器的双向链表结构使得这个操作的效率高一些!
算法
在 STL 中,除了用容器类自身提供的成员函数来操作容器元素外,还提供了一系列通用的对容器中元素进行操作的 全局函数,称为算法(algorithm)。
STL 算法实现了对序列元素的一些常见操作,主要包括:
-
调序算法
-
编辑算法
-
查找算法
-
算术算法
-
集合算法
-
堆算法
-
元素遍历算法算法是用函数模板实现的,除了算术算法在头文件
numeric
中定义外,其它算法都在头文件algorithm
中定义。
大部分 STL 算法都是遍历指定容器中某范围内的元素,对满足条件的元素执行某项操作。例如:
1 | bool f(int x) { return x > 0; } |
另外,如果容器中的元素是自定义类的对象,则针对相应的类可能要重载一些操作符(如 <、==、= 等),因为有些算法内部会用到这些操作。