STL基本概念
什么是STL
STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件组件统称,设计目标是提升代码重用性。
为建立数据结构和算法的统一标准,降低组件间耦合度,提升独立性、弹性和交互操作性,STL应运而生。其核心优势是采用模板类/模板函数,相比传统函数库/类库提供了更强大的代码复用能力。
STL三大核心组件
STL从广义上分为三大核心组件,三者通过迭代器无缝衔接:
- 容器(Container):封装特定数据结构,用于存储和管理数据(如数组、链表、队列)
- 算法(Algorithm):用于操作容器中数据的函数(如排序、查找、遍历)
- 迭代器(Iterator):连接容器和算法的桥梁,提供统一的方式访问容器元素
容器的概念
容器是实现特定算法的数据结构集合,具备以下特点:
- 可容纳任意类型数据(文件、进程、自定义类等)
- 提供统一操作接口(如赋值、清除、插入、删除)
- 屏蔽内部实现细节,用户无需关注容器逻辑,专注于数据处理
容器的分类
| 容器类型 |
核心特征 |
典型示例 |
内部结构 |
| 顺序容器 |
元素存储位置取决于存取顺序,与元素本身属性无关 |
array/vector、list、deque |
数组、链表 |
| 关联容器 |
元素存储位置取决于元素本身属性(大小关系/键值对应) |
set、map |
平衡二叉树 |
| 无序关联容器 |
采用Hash结构,极致提升检索速度 |
unordered_set、unordered_map |
哈希表 |
| 容器适配器 |
对顺序容器封装特定接口,转换为专用容器 |
stack(栈)、queue(队列) |
基于vector/deque/list |
迭代器
概念与作用
迭代器是广义指针,是高度抽象的容器元素指涉器,核心作用是让C++程序通过统一方式处理不同数据结构。
使用迭代器的优势
- 算法独立于容器内元素类型(模板特性)和容器本身类型(迭代器特性)
- 支持继承扩展,可构建输入/输出迭代器、正向/反向迭代器、随机访问迭代器等
- 屏蔽指涉对象细节,无需关心元素类型即可用指针语法操作
核心接口与区间特性
- 所有迭代器均提供
begin() 和 end() 接口:
begin():指向容器第一个元素
end():指向容器最后一个元素的下一个位置(半开半闭区间 [begin, end))
迭代器类型说明
| 迭代器类型 |
特点 |
支持的容器 |
| 正向迭代器 |
从前往后遍历,支持 ++ |
所有容器 |
| 反向迭代器 |
从后往前遍历,支持 ++(实际移动方向相反),接口为 rbegin()/rend() |
vector、list、deque等 |
| 常量迭代器 |
仅可读,不可修改元素,前缀为 c(如 crbegin()) |
所有容器 |
| 随机访问迭代器 |
支持跳跃访问(+n/-n)、比较操作(</>) |
vector、array、deque |
| 双向迭代器 |
支持前后移动(++/--),不支持跳跃 |
list、forward_list(仅正向) |
迭代器遍历示例(vector)
#include <iostream>
#include <vector>
using namespace std;
int main(void) {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// 1. 数组下标遍历(仅支持随机访问容器)
for(int i=0; i<v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
// 2. 正向迭代器遍历
for(vector<int>::iterator it=v.begin(); it!=v.end(); it++) {
cout << *it << " ";
}
cout << endl;
// 3. 反向迭代器遍历
for(vector<int>::reverse_iterator it=v.rbegin(); it!=v.rend(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
顺序容器详解
顺序容器的核心特征:元素存储位置由存取顺序决定,支持在头部、尾部或中间位置进行元素操作。
数组容器
静态数组 array(C++11起)
核心特性
- 容量固定,初始化后大小不可修改(与C语言数组类似)
- 不自动退化为指针,支持容器化操作(如
swap()、size())
- 需显式指定元素类型和容量
接口规范
#include <array>
template<typename T, size_t N> // T:元素类型,N:固定容量
struct array;
核心接口表格
| 接口名 |
功能描述 |
备注 |
at(size_t i) |
返回下标i的元素 |
越界抛出 out_of_range 异常 |
operator[](i) |
返回下标i的元素 |
越界行为未定义(效率更高) |
front() |
返回第一个元素 |
容器非空时使用 |
back() |
返回最后一个元素 |
容器非空时使用 |
begin()/end() |
返回正向迭代器(起始/末尾) |
支持遍历 |
rbegin()/rend() |
返回反向迭代器(起始/末尾) |
从后往前遍历 |
crbegin()/crend() |
返回常量反向迭代器 |
不可修改元素(const 特性) |
size()/max_size() |
返回元素个数 |
两者结果相同(容量固定) |
swap(array& other) |
交换两个数组元素 |
要求类型和容量完全一致 |
fill(const T& val) |
用val填充整个数组 |
覆盖所有元素 |
empty() |
判断容器是否为空 |
仅当容量N=0时返回true(关键!) |
完整示例代码
#include <iostream>
#include <array>
using namespace std;
int main(int argc, char const *argv[]) {
// 1. 创建并初始化静态数组
array<int, 10> arr{1,2,3,4,5,6,7,8,9,10};
array<int, 10> arr1{11,22,33,44,55,66,77,88,99,0};
// 2. 获取数组大小
cout << "数组大小:" << arr.size() << endl;
cout << "最大容量:" << arr.max_size() << endl;
cout << "++++++++++++++++++++" << endl;
// 3. 下标访问(安全方式)
for (int i = 0; i < arr.size() ; i++) {
cout << arr.at(i) << " ";
}
cout << "\n++++++++++++++++++++" << endl;
// 4. 迭代器遍历
for (array<int,10>::iterator it = arr.begin(); it != arr.end(); it++) {
cout << *it << " ";
}
cout << "\n++++++++++++++++++++" << endl;
// 5. 获取首尾元素
cout << "第一个元素:" << arr.front() << endl;
cout << "最后一个元素:" << arr.back() << endl;
cout << "++++++++++++++++++++" << endl;
// 6. 交换两个数组
arr.swap(arr1);
cout << "交换后arr元素:";
for (auto it = arr.begin(); it != arr.end(); it++) {
cout << *it << " ";
}
cout << "\n++++++++++++++++++++" << endl;
// 7. 填充数组
arr.fill(666);
cout << "填充后反向遍历:";
for (auto it = arr.rbegin(); it != arr.rend(); it++) {
cout << *it << " ";
}
cout << "\n++++++++++++++++++++" << endl;
// 8. 空判断(关键示例)
array<int, 32> arr2; // 未初始化,但容量32≠0
if (arr2.empty()) {
cout << "数组为空.." << endl;
} else {
cout << "数组不为空.." << endl; // 实际输出此句
}
return 0;
}
注意事项
- 常量迭代器(如
crbegin())返回的元素不可修改,否则编译报错
empty() 仅判断容量是否为0,而非元素是否初始化
- 不支持动态扩容,适合数据量固定的场景
动态数组 vector
核心特性
- 容量动态变化(按需扩容/收缩)
- 元素连续存储,支持随机访问(
v[i])
- 内存紧凑,尾部插入/删除效率高,中间插入/删除效率低(需移动元素)
接口规范
#include <vector>
template<typename T,
typename Allocator = std::allocator<T> // 分配器(默认提供)
> class vector;
核心接口表格
| 接口名 |
功能描述 |
备注 |
push_back(const T& val) |
在尾部追加元素 |
触发扩容时可能导致迭代器失效 |
pop_back() |
移除尾部元素 |
不触发扩容,效率高 |
capacity() |
返回当前总容量(已分配内存可存储的元素数) |
大于等于 size() |
size() |
返回当前元素个数 |
实际存储的元素数量 |
begin()/end() |
返回正向迭代器 |
支持随机访问(it + n) |
rbegin()/rend() |
返回反向迭代器 |
从后往前遍历 |
swap(vector& other) |
交换两个vector元素 |
类型需一致 |
clear() |
清空所有元素 |
size() 变为0,但 capacity() 不变 |
reserve(size_t n) |
预分配n个元素的内存 |
避免频繁扩容,提升性能 |
resize(size_t n) |
调整元素个数为n |
超出原size时补默认值,不足时截断 |
基础示例代码
#include <iostream>
#include <vector>
using namespace std;
int main(void) {
vector<int> v; // 空vector,初始capacity=0
// 尾部插入元素
v.push_back(1);
v.push_back(2);
v.push_back(3);
cout << "元素个数:" << v.size() << endl; // 输出3
cout << "当前容量:" << v.capacity() << endl; // 输出4(不同编译器可能不同,通常翻倍扩容)
// 遍历元素(三种方式)
// 1. 下标遍历
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
// 2. 迭代器遍历
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
// 3. 范围for遍历(C++11起)
for (int val : v) {
cout << val << " ";
}
cout << endl;
// 交换vector
vector<int> w{4,5,6};
v.swap(w);
cout << "交换后v的元素:";
for (int val : v) {
cout << val << " ";
}
cout << endl;
return 0;
}
拓展:vector扩容机制
- 扩容逻辑:当
size() == capacity() 时,触发扩容,通常是翻倍扩容(如capacity从4→8→16...)
- 扩容过程:分配新内存 → 拷贝原元素到新内存 → 释放原内存
- 影响:扩容后原迭代器、指针、引用会失效(指向已释放内存)
- 优化方案:提前用
reserve(n) 预分配足够内存,避免频繁扩容
双端队列 deque
核心特性
- 双端操作:支持队头/队尾的插入、删除操作
- 支持随机访问(
dq[i]),但内存布局非连续(效率低于vector)
- 无需扩容:采用分段内存存储,插入元素时按需分配新段
接口规范
#include <deque>
template<typename T,
typename Allocator = std::allocator<T>
> class deque;
核心接口表格
| 接口名 |
功能描述 |
备注 |
push_front(const T& val) |
在队头插入元素 |
效率高(无需移动元素) |
push_back(const T& val) |
在队尾插入元素 |
效率高 |
pop_front() |
移除队头元素 |
效率高 |
pop_back() |
移除队尾元素 |
效率高 |
front()/back() |
返回队头/队尾元素 |
容器非空时使用 |
operator[](size_t i) |
返回下标i的元素 |
支持随机访问 |
at(size_t i) |
返回下标i的元素 |
越界抛出 out_of_range 异常 |
erase(iterator it) |
擦除指定位置元素 |
效率低于list(需移动分段内元素) |
insert(iterator it, const T& val) |
在指定位置插入元素 |
效率低于list |
empty() |
判断队列是否为空 |
size() == 0 时返回true |
示例代码
#include <iostream>
#include <deque>
using namespace std;
int main(void) {
deque<int> dq;
// 双端插入
dq.push_front(1); // 队头:1
dq.push_back(2); // 队尾:2
dq.push_front(0); // 队头:0
dq.push_back(3); // 队尾:3
// 随机访问
cout << "下标1的元素:" << dq[1] << endl; // 输出1
cout << "下标2的元素:" << dq.at(2) << endl; // 输出2
// 遍历
cout << "队列元素:";
for (deque<int>::iterator it = dq.begin(); it != dq.end(); it++) {
cout << *it << " "; // 输出:0 1 2 3
}
cout << endl;
// 双端删除
dq.pop_front(); // 移除0
dq.pop_back(); // 移除3
cout << "删除后元素:";
for (int val : dq) {
cout << val << " "; // 输出:1 2
}
cout << endl;
return 0;
}
链表容器
单链表 forward_list(C++11起)
核心特性
- 单向链表,仅支持正向遍历(无反向迭代器)
- 内存非连续,中间插入/删除效率高(无需移动元素)
- 不支持随机访问,访问元素需从头遍历
- 相比list更节省内存(无需存储前驱指针)
接口规范
#include <forward_list>
template<typename T,
typename Allocator = std::allocator<T>
> class forward_list;
核心接口表格
| 接口名 |
功能描述 |
备注 |
push_front(const T& val) |
在表头插入元素 |
效率O(1) |
pop_front() |
移除表头元素 |
效率O(1) |
insert_after(iterator pos, const T& val) |
在pos位置后插入元素 |
需先找到目标位置,效率O(n) |
erase_after(iterator pos) |
擦除pos位置后的元素 |
效率O(1) |
sort() |
升序排序 |
需重载 < 运算符(自定义类型) |
unique() |
移除相邻重复元素 |
保留一个,需元素可比较 |
begin()/end() |
返回正向迭代器 |
无反向迭代器(rbegin()/rend()) |
实例:存储自定义类并排序
#include <iostream>
#include <forward_list>
#include <string>
using namespace std;
// 排序关键字枚举
enum SortKey { NUM, SCORE, NAME_LENGTH };
class Student {
private:
int num; // 学号
string name; // 姓名
int score; // 分数
public:
static SortKey sortKey; // 静态成员:排序关键字(类共享)
// 构造函数
Student(int n, int s, string nm) : num(n), score(s), name(nm) {}
// 访问器
int getNum() const { return num; }
string getName() const { return name; }
int getScore() const { return score; }
// 重载 < 运算符(用于sort())
bool operator<(const Student& other) const {
switch (sortKey) {
case NUM:
return this->num < other.num;
case SCORE:
return this->score < other.score;
case NAME_LENGTH:
return this->name.length() < other.name.length();
default:
return false;
}
}
};
// 静态成员类外初始化
SortKey Student::sortKey = NUM;
int main() {
// 创建单链表并初始化
forward_list<Student> fl = {
Student(1, 35, "Evenvv"),
Student(3, 22, "Jacyd"),
Student(5, 17, "Jackjjjjj"),
Student(2, 78, "TieZhu")
};
// 按姓名长度排序
Student::sortKey = NAME_LENGTH;
fl.sort();
// 遍历输出
cout << "按姓名长度排序结果:" << endl;
for (const auto& stu : fl) {
cout << "学号:" << stu.getNum()
<< " 姓名:" << stu.getName()
<< " 分数:" << stu.getScore() << endl;
}
return 0;
}
双链表 list
核心特性
- 双向链表,支持正向/反向遍历
- 内存非连续,任意位置插入/删除效率高(O(1),仅需修改指针)
- 不支持随机访问,访问元素需遍历(效率O(n))
- 相比forward_list功能更全,但内存开销更大(需存储前驱+后继指针)
接口规范
#include <list>
template<typename T,
typename Allocator = std::allocator<T>
> class list;
核心接口表格
| 接口名 |
功能描述 |
备注 |
push_front(const T& val) |
在表头插入元素 |
效率O(1) |
push_back(const T& val) |
在表尾插入元素 |
效率O(1) |
pop_front() |
移除表头元素 |
效率O(1) |
pop_back() |
移除表尾元素 |
效率O(1) |
insert(iterator pos, const T& val) |
在pos位置插入元素 |
需先找到pos,效率O(n),插入本身O(1) |
erase(iterator pos) |
擦除pos位置元素 |
效率O(1) |
sort() |
升序排序 |
自定义类型需重载 < 运算符 |
reverse() |
反转链表 |
效率O(n) |
unique() |
移除相邻重复元素 |
需元素可比较 |
begin()/end() |
正向迭代器(起始/末尾) |
|
rbegin()/rend() |
反向迭代器(起始/末尾) |
支持反向遍历 |
示例代码
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> myList;
// 两端插入
myList.push_front(3);
myList.push_back(1);
myList.push_front(2);
myList.push_back(4);
// 遍历(正向)
cout << "正向遍历:";
for (list<int>::iterator it = myList.begin(); it != myList.end(); it++) {
cout << *it << " "; // 输出:2 3 1 4
}
cout << endl;
// 排序
myList.sort();
cout << "排序后:";
for (int val : myList) {
cout << val << " "; // 输出:1 2 3 4
}
cout << endl;
// 插入元素(在2后面插入5)
auto it = myList.begin();
++it; // 指向2
myList.insert(it, 5);
// 反向遍历
cout << "反向遍历(含插入元素):";
for (list<int>::reverse_iterator rit = myList.rbegin(); rit != myList.rend(); rit++) {
cout << *rit << " "; // 输出:4 3 2 5 1
}
cout << endl;
return 0;
}
顺序容器特性对比
| 容器类型 |
存储结构 |
随机访问 |
头部操作效率 |
尾部操作效率 |
中间插入/删除效率 |
迭代器类型 |
适用场景 |
array |
连续数组(固定) |
支持(O(1)) |
O(1)(仅访问) |
O(1)(仅访问) |
O(n)(需移动元素) |
随机访问迭代器 |
数据量固定、需频繁随机访问 |
vector |
连续数组(动态) |
支持(O(1)) |
O(n)(需移动) |
O(1)(扩容时O(n)) |
O(n)(需移动元素) |
随机访问迭代器 |
数据量动态、频繁尾部操作/随机访问 |
deque |
分段连续数组 |
支持(O(1)) |
O(1) |
O(1) |
O(n)(分段内移动) |
随机访问迭代器 |
需双端操作、偶尔随机访问 |
forward_list |
单向链表 |
不支持(O(n)) |
O(1) |
O(n)(需遍历) |
O(1)(找到位置后) |
正向迭代器 |
内存紧张、仅需正向遍历/中间操作 |
list |
双向链表 |
不支持(O(n)) |
O(1) |
O(1) |
O(1)(找到位置后) |
双向迭代器 |
频繁任意位置插入/删除、双向遍历 |
常见问题与拓展知识
迭代器失效问题
| 容器类型 |
可能导致迭代器失效的操作 |
避免方案 |
vector |
1. push_back() 触发扩容;2. insert()/erase() 中间操作 |
1. 预分配 reserve();2. 操作后重新获取迭代器 |
deque |
1. 头部/尾部插入可能导致迭代器失效;2. 中间插入/删除 |
操作后重新获取迭代器 |
list/forward_list |
仅被删除元素的迭代器失效 |
保存下一个迭代器再删除(如 it = list.erase(it)) |
内存使用优化
vector:用 reserve(n) 预分配内存,避免频繁扩容;用 shrink_to_fit() 释放多余内存(C++11起)
list:避免存储大量小对象(内存开销大),可考虑用 vector 替代
array:适合栈上存储(无需动态内存分配),但容量固定需提前规划
自定义类型作为容器元素的要求
- 必须提供默认构造函数(
vector/deque 扩容时需要)
- 若使用
sort()/unique() 等算法,需重载 < 运算符或提供比较函数
- 若涉及拷贝操作,需确保拷贝构造函数/赋值运算符正确实现(避免浅拷贝)
容器适配器与顺序容器的关系
容器适配器(stack/queue/priority_queue)是对顺序容器的封装:
stack:基于 deque(默认)/vector/list,仅支持尾部操作(LIFO)
queue:基于 deque(默认)/list,仅支持队尾插入、队头删除(FIFO)
priority_queue:基于 vector(默认)/deque,是优先级队列(堆结构)