深入浅出STL标准模板库
前几天谈论了许多关于数论和数据结构的东西,这些内容可能对初学者而言比较晦涩难懂(毕竟是属于初高等算法/数据结构的范畴了)。今天打算来讲一些简单的内容 - STL 标准模板库。
STL 标准模板库
C++ 标准模板库 (Standard Template Library, STL),是 C++ 语言非常重要的一个构成部分。它提供了一组通用的类和函数,用于实现一些数据结构和算法。STL 主要包含以下五个部分:
- 容器 (Containers):STL 提供了不同的容器来供开发者根据所需要维护数据来选择存储。
- 算法 (Algorithms):STL 提供了许多通用算法,用于排序、搜索、复制、修改等操作。
- 迭代器 (Iterators):STL 迭代器的作用是遍历容器元素的对象。
- 函数对象 (Function Objects):这是一种行为类似于函数的对象(由于不太常见,因此本篇文章将不会详细讲解 STL 中的函数对象)。
- 适配器 (Adapters):STL 的适配器用于修改容器、迭代器或函数对象的接口。为方便起见,容器适配器将会与容器部分一同讲解。
综上所述,本文将会主要围绕 容器、算法和迭代器这三者来展开叙述 。本文只会阐述一些相对常见的模板/方法,部分生僻的内容还请各位自行上网搜索。
容器 Containers
STL 容器还可以被细分成三个类别,分别是 序列式容器 (Sequence Containers)、关联式容器 (Associative Containers) 和 无序关联式容器 (Unordered Associative Containers)。
序列式容器 (Sequence Containers) 与常见容器适配器。
vector
向量容器:向量容器可以被理解为我们常说的动态数组。与普通数组不同的是,动态数组的大小可以在运行过程中更改,并且用户可以任意地在此数组的末尾添加数据。它的优点是支持快速随机访问和在末尾插入删除元素。向量容器的基本使用方法如下:
#include <iostream>
#include <vector> // 引入头文件
using namespace std;
int main(){
// 创建一个整数类型,名为 vec 的向量容器,初始存放了数字1-5。
vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // 向容器末尾添加一个元素6。
vec.size(); // 获取容器的大小,即容器内元素的个数。
vec.resize(10); // 动态更改容器的大小,将容器的大小设置为10。
// 通过 for 循环对容器进行遍历:
for (int i=0; i<vec.size(); i++){}
for (int i : vec) {}
return 0;
}
queue
队列容器适配器:队列是一种基本的数据结构,其讲究 先进先出 (FIFO),即最先添加进适配器的元素会被第一个弹出,以此类推。在队列中,所有元素都会从队尾入队,并从对首出队。队列适配器的优点是支持随机访问队首和队尾元素。队列容器严格意义上并非序列式容器,而是一种容器适配器。队列容器适配器的基本使用方法如下:
#include <iostream>
#include <queue> // 引入头文件
using namespace std;
int main(){
// 创建一个存放整数类型,名为 que 的队列容器适配器,一开始默认为空。
queue<int> que;
que.push(10); // 向队尾添加一个新元素。
que.front(); // 获取队首元素,此时队首元素为数字10。
que.pop(); // 将队首元素出队,此时的队列容器为空。
que.size(); // 获取队列的长度,即对内元素的个数。
que.empty(); // 判断队列是否为空,为空返回真,否则返回假。
// 队列元素不支持直接遍历,只能从头一个一个弹出。
// 在弹出元素时需要保证队内不为空。
while (!que.empty()){
int t = que.front();
que.pop();
cout << t << endl;
}
return 0;
}
stack
栈容器适配器:栈也是一种基本的数据结构,栈实现的是 后进先出 (LIFO),即最后添加进该适配器的元素会最先弹出。在栈中,所有元素都会从栈顶入栈,并从栈顶出栈。栈容器严格意义上并非序列式容器,而是一种容器适配器。栈容器适配器的基本使用方法如下:
#include <iostream>
#include <stack> // 引入头文件
using namespace std;
int main(){
// 创建一个存放整数类型,名为 s 的栈容器适配器,一开始默认为空。
stack<int> s;
s.push(10); // 将元素压入栈顶。
s.top(); // 获取栈顶元素,此时的栈顶元素为数字10。
s.pop(); // 将栈顶元素出栈,此时栈内没有元素。
s.size(); // 获取栈内元素的个数。
s.empty(); // 判断栈是否为空,为空返回真,否则返回假。
// 与队列相同,栈内元素不支持直接遍历,只能从栈顶一个一个弹出。
// 在弹 出元素时需要保证栈内不为空。
while (!s.empty()){
int t = s.top();
s.pop();
cout << t << endl;
}
return 0;
}
deque
双端队列容器:与队列类似,双端对类容器支持快速随机访问和在两端插入删除。上面提及到的queue
容器适配器就是基于双端队列所实现的。双端队列容器的基本使用方法如下:
#include <iostream>
#include <deque>
int main() {
// 创建一个整数类型,名为 deq 的双端队列容器,初始存放了数字1-5。
deque<int> deq = {1, 2, 3, 4, 5};
deq.push_front(0); // 在前端添加元素。
deq.push_back(6); // 在末尾添加元素。
// 通过 for 循环对容器进行遍历:
for (int i : deq) cout << i << endl;
return 0;
}
list
双向链表容器:链表数据结构支持快速插入删除操作,但是通过索引访问元素会比较慢。双向链表的语法与双端队列相似。在一般算法实现上,双向链表的应用并不算广泛,因此不过多阐述。双向链表的基本使用方法如下:
#include <iostream>
#include <list> // 引入头文件
int main() {
// 创建一个整数类型,名为 lst 的双向队列容器,初始存放了数字1-5。
list<int> lst = {1, 2, 3, 4, 5};
lst.push_front(0); // 在前端添加元素。
lst.push_back(6); // 在末尾添加元素。
// 通过 for 循环对容器进行遍历:
for (int i : lst) cout << i << endl;
return 0;
}
关联式容器 (Associative Containers)
set
集合容器:集合容器的定义与数学中的集合完全相同。集合中相同的元素只会被存入一次。集合可以进行高效的查找操作。集合容器的基本使用方法如下:
#include <iostream>
#include <set> // 引入头文件
int main() {
// 创建一个整数类型,名为 s 的集合容器,初始存放了许多数字。
set<int> s = {3, 1, 4, 1, 5, 9};
s.insert(2); // 向集合内插入元素2。
s.erase(2); // 向集合中移除元素2。
s.count(2); // 检查元素是否存在于集合,如果返回真即存在,否则即不存在。
s.size(); // 获取集合内元素的个数。
s.clear(); // 清空集合。
// 通过 for 循环对容器进行遍历:
// 由于集合自带“去重”功能,最输出的结果为:1 3 4 5 9。
for (auto i : s) cout << i << " ";
return 0;
}
map
映射容器:除了集合容器之外,该容器也经常在算法中被应用。映射容器用于存储键值对。其中键在该容器中是唯一,同时它还支持高效的查找操作(对于某些数据如果懒得离散化一定要试一下这个容器,我觉得真的很好用)。以下是映射容器的基本用法:
#include <iostream>
#include <map> // 引入头文件
int main() {
// 创建一个键为整数类型、值为字符串类型,名为 m 的映射容器。
map<int, string> m;
m[1] = "Macw07"; // 插入键值对。
s.count(2); // 检查元素是否存在于集合,如果返回真即存在,否则即不存在。
s.size(); // 获取集合内元素的个数。
return 0;
}
无序关联式容器 (Unordered Associative Containers)
一般情况下,所有的关联式容器默认都会按照存放数组(值/键)从小到大进行排序。但如果将这些关联式容器变成无序关联式容器,那么 STL 内部就不会默认对其进行排序。一般情况下,如果没有特殊需求推荐使用无序关联式容器,因为它们的运行效率通常都比有序的关联式容器要高很多。
以下是常见的无序关联式容器和有序关联式容器的声明代码:
map
与 unordered_map
:
#include <map> // 有序的。
#include <unordered_map> // 无序的。
map<int, int> mp1; // 有序映射表的声明。
unordered_map<int, int> mp2; // 无序映射表的声明。
set
与 unordered_set
:
#include <set> // 有序的。
#include <unordered_set> // 无序的。
set<int> mp1; // 有序集合的声明。
unordered_set<int> mp2; // 无序集合的声明。