引言
在C++编程中,标准模板库是不可或缺的核心组成部分。无论你是参加算法竞赛、开发大型项目,还是准备技术面试,STL都能极大地提升你的开发效率。
STL发展历程中的几个重要版本:
| 版本 | 开发者/应用场景 | 特点 |
|---|---|---|
| HP版本 | 惠普公司原始开发 | 是STL的原始实现,后被纳入C++标准 |
| SGI版本 | Linux系统常用实现 | 开源实现,被GCC编译器采用 |
| PJ版本 | Visual Studio配套实现 | Windows平台主流实现 |
今天我们重点讲解的是在实际开发中最常用的容器------vector和list,以及配套的迭代器、算法等核心组件。
第一部分:STL的核心组成
一、STL的四大组件
| 组件 | 作用 | 举例 |
|---|---|---|
| 容器 | 存储数据的数据结构 | vector、list、map、set |
| 迭代器 | 统一访问容器元素的接口 | begin()、end() |
| 算法 | 通用的数据处理函数 | sort、find、copy |
| 仿函数 | 行为类似函数的对象 | greater、less |
二、迭代器(Iterator)
迭代器是面向对象化的指针,支持解引用、移动等操作,是连接容器与算法的桥梁。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {1, 2, 3, 4, 5};
// 迭代器的基本使用
vector<int>::iterator it = arr.begin();
cout << "第一个元素: " << *it << endl;
// 遍历所有元素
for (it = arr.begin(); it != arr.end(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}
三、算法(Algorithm)
算法库提供了大量常用操作,直接调用即可,无需重复造轮子。
| 分类 | 算法函数 | 功能 |
|---|---|---|
| 排序 | sort() |
排序 |
| 查找 | find() |
查找元素 |
| 拷贝 | copy() |
复制元素 |
| 删除 | remove() |
删除元素 |
| 统计 | count() |
计数 |
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> arr = {5, 2, 8, 1, 9, 3};
// 排序(默认升序)
sort(arr.begin(), arr.end());
// 结果:1 2 3 5 8 9
// 降序排序
sort(arr.begin(), arr.end(), greater<int>());
// 结果:9 8 5 3 2 1
return 0;
}
第二部分:vector容器详解
一、vector的底层结构
vector是动态数组,在内存中开辟连续空间存储元素。其底层由三个关键指针管理:

| 指针 | 含义 |
|---|---|
start |
堆空间首地址 |
finish |
最后一个元素的后继地址(决定size) |
end_of_storage |
空间容量末尾地址(决定capacity) |
重要概念:
-
size():实际存储的元素个数 -
capacity():预分配的空间大小(size <= capacity)
二、vector的扩容机制
当空间不足时,vector会自动扩容(通常按2倍或1.5倍增长)。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr;
cout << "初始: size=" << arr.size() << ", capacity=" << arr.capacity() << endl;
for (int i = 0; i < 10; i++) {
arr.push_back(i);
cout << "push_back(" << i << "): size=" << arr.size()
<< ", capacity=" << arr.capacity() << endl;
}
return 0;
}
扩容过程的影响:
-
存储对象时:扩容需要逐个迁移对象并调用拷贝构造函数,性能开销大
-
存储指针时:仅需迁移指针地址,性能更优(但需注意内存释放问题)
三、vector的初始化方式
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 1. 默认构造
vector<int> v1;
// 2. 指定大小
vector<int> v2(10); // 10个元素,值为0
vector<int> v3(10, 5); // 10个元素,值都为5
// 3. 列表初始化(C++11)
vector<int> v4 = {1, 2, 3, 4, 5};
vector<int> v5{1, 2, 3, 4, 5};
// 4. 拷贝构造
vector<int> v6(v4);
// 5. 迭代器范围构造
vector<int> v7(v4.begin(), v4.end());
return 0;
}
四、vector的元素访问方式
| 访问方式 | 语法 | 边界检查 | 效率 |
|---|---|---|---|
| 下标运算符 | arr[i] |
❌ 无检查 | 最高 |
| at()方法 | arr.at(i) |
✅ 抛出异常 | 较高 |
| front() | arr.front() |
空容器未定义 | 高 |
| back() | arr.back() |
空容器未定义 | 高 |
| data() | arr.data() |
返回底层数组指针 | 高 |
cpp
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
int main() {
vector<int> arr = {10, 20, 30, 40, 50};
// 下标访问(无边界检查)
cout << arr[0] << endl; // 10
// arr[10] = 100; // 越界,未定义行为
// at()方法(有边界检查)
try {
cout << arr.at(2) << endl; // 30
cout << arr.at(10) << endl; // 抛出out_of_range异常
} catch (const out_of_range& e) {
cout << "越界: " << e.what() << endl;
}
// 首尾元素
cout << "第一个元素: " << arr.front() << endl; // 10
cout << "最后一个元素: " << arr.back() << endl; // 50
return 0;
}
五、vector的遍历方式
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {1, 2, 3, 4, 5};
// 方式1:下标遍历
cout << "下标遍历: ";
for (size_t i = 0; i < arr.size(); i++) {
cout << arr[i] << " ";
}
cout << endl;
// 方式2:迭代器遍历
cout << "迭代器遍历: ";
for (vector<int>::iterator it = arr.begin(); it != arr.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 方式3:范围for循环(C++11)
cout << "范围for(值传递): ";
for (auto x : arr) { // 拷贝每个元素
cout << x << " ";
}
cout << endl;
// 方式4:范围for(引用传递,避免拷贝)
cout << "范围for(引用传递): ";
for (const auto& x : arr) { // 只读,不拷贝
cout << x << " ";
}
cout << endl;
// 修改元素需要引用
for (auto& x : arr) {
x *= 2;
}
return 0;
}
效率对比:
-
值传递
for (auto x : arr):每次迭代都会拷贝元素,效率低 -
引用传递
for (auto& x : arr):不拷贝,效率高 -
const引用
for (const auto& x : arr):只读场景,效率高且语义明确
六、vector的容量管理
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr;
// 预分配容量(避免频繁扩容)
arr.reserve(100);
cout << "reserve后: capacity=" << arr.capacity() << endl;
// 添加元素
for (int i = 0; i < 50; i++) {
arr.push_back(i);
}
cout << "size=" << arr.size() << ", capacity=" << arr.capacity() << endl;
// 释放多余内存
arr.shrink_to_fit();
cout << "shrink_to_fit后: capacity=" << arr.capacity() << endl;
return 0;
}
| 方法 | 作用 | 说明 |
|---|---|---|
reserve(n) |
预分配容量 | 避免频繁扩容,不改变size |
shrink_to_fit() |
释放多余内存 | 将capacity缩小到size |
clear() |
清空元素 | size变为0,capacity不变 |
七、vector的插入与删除
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {1, 2, 3, 4, 5};
// 尾部操作(高效,O(1))
arr.push_back(6); // 尾部插入
arr.pop_back(); // 尾部删除
// 中间操作(低效,O(n))
arr.insert(arr.begin() + 2, 100); // 在位置2插入100
arr.erase(arr.begin() + 3); // 删除位置3的元素
// 遍历
for (int x : arr) {
cout << x << " ";
}
cout << endl;
return 0;
}
复杂度总结:
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
push_back() / pop_back() |
O(1) | 尾部操作,高效 |
insert() / erase()(中间) |
O(n) | 需要移动元素,低效 |
第三部分:迭代器详解
一、迭代器的基本使用
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {10, 20, 30, 40, 50};
// 正向迭代器
cout << "正向遍历: ";
for (vector<int>::iterator it = arr.begin(); it != arr.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 只读迭代器
cout << "只读遍历: ";
for (vector<int>::const_iterator it = arr.cbegin(); it != arr.cend(); ++it) {
cout << *it << " ";
// *it = 100; // 错误!const_iterator不能修改
}
cout << endl;
// 反向迭代器
cout << "反向遍历: ";
for (vector<int>::reverse_iterator it = arr.rbegin(); it != arr.rend(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}
二、迭代器类型总结
| 迭代器类型 | 获取方式 | 可读 | 可写 | 方向 |
|---|---|---|---|---|
iterator |
begin() / end() |
✅ | ✅ | 正向 |
const_iterator |
cbegin() / cend() |
✅ | ❌ | 正向 |
reverse_iterator |
rbegin() / rend() |
✅ | ✅ | 反向 |
三、迭代器失效问题
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {1, 2, 3, 4, 5};
// 获取迭代器
auto it = arr.begin() + 2; // 指向元素3
cout << "*it = " << *it << endl;
// 插入操作可能导致迭代器失效
arr.insert(arr.begin(), 0); // 头部插入,可能导致所有迭代器失效
// 危险!it可能已经失效
// cout << "*it = " << *it << endl;
// 重新获取迭代器
it = arr.begin() + 3;
cout << "重新获取后: *it = " << *it << endl;
return 0;
}
迭代器失效场景:
-
插入元素导致扩容时,所有迭代器失效
-
删除元素后,被删除位置之后的迭代器失效
第四部分:list容器详解
一、list的底层结构
list是双向链表,节点在内存中不连续,通过指针连接。
cpp
// list节点的简化结构
template<typename T>
struct ListNode {
T data; // 数据域
ListNode* prev; // 前驱指针
ListNode* next; // 后继指针
};
// 带哨兵节点的双向链表
// head指向头节点(哨兵),size记录元素个数
cpp
list内存布局(逻辑结构):
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ 节点1 │◄──►│ 节点2 │◄──►│ 节点3 │◄──►│ 节点4 │
│ data │ │ data │ │ data │ │ data │
└──────┘ └──────┘ └──────┘ └──────┘
↑ ↑
└──────────────┐ ┌──────────────────┘
▼ ▼
┌──────────┐
│ 哨兵节点 │
└──────────┘
二、list的基本操作
cpp
#include <iostream>
#include <list>
using namespace std;
int main() {
// 初始化
list<int> lst = {1, 2, 3, 4, 5};
// 插入操作(高效,O(1))
lst.push_back(6); // 尾部插入
lst.push_front(0); // 头部插入
// 删除操作(高效,O(1))
lst.pop_back(); // 尾部删除
lst.pop_front(); // 头部删除
// 访问(只能访问首尾)
cout << "第一个元素: " << lst.front() << endl;
cout << "最后一个元素: " << lst.back() << endl;
// ❌ list不支持下标访问
// cout << lst[2] << endl; // 错误!
// 遍历(使用迭代器或范围for)
cout << "遍历: ";
for (int x : lst) {
cout << x << " ";
}
cout << endl;
return 0;
}
三、list vs vector
| 特性 | vector | list |
|---|---|---|
| 底层结构 | 动态数组(连续内存) | 双向链表(非连续) |
| 随机访问 | ✅ O(1) | ❌ O(n) |
| 中间插入/删除 | ❌ O(n) | ✅ O(1) |
| 尾部插入/删除 | ✅ O(1)(扩容时O(n)) | ✅ O(1) |
| 内存占用 | 较少(无指针开销) | 较大(前后指针) |
| 适用场景 | 频繁访问、尾部操作 | 频繁中间插入删除 |
四、list的特殊操作
cpp
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
int main() {
list<int> lst = {5, 2, 8, 1, 3, 2, 5, 2};
// list专属排序(不能用标准库的sort)
lst.sort(); // 升序排序
// lst.sort(greater<int>()); // 降序排序
// 去重(必须先排序)
lst.unique(); // 删除相邻重复元素
// 删除指定值的所有元素
lst.remove(2); // 删除所有值为2的元素
// 合并两个有序list
list<int> lst2 = {10, 20, 30};
lst.merge(lst2); // lst2被清空,元素合并到lst
// 反转
lst.reverse();
for (int x : lst) {
cout << x << " ";
}
cout << endl;
return 0;
}
第五部分:关联容器(set/map)
一、set容器(集合)
set中的元素唯一且自动排序,底层基于红黑树实现。
cpp
#include <iostream>
#include <set>
using namespace std;
int main() {
vector<int> arr = {5, 2, 8, 1, 5, 2, 9, 3};
// 使用set去重
set<int> s;
for (int x : arr) {
s.insert(x);
}
cout << "去重后: ";
for (int x : s) {
cout << x << " ";
}
cout << endl;
// 输出:1 2 3 5 8 9(自动排序)
// 查找元素
if (s.find(5) != s.end()) {
cout << "5存在" << endl;
}
return 0;
}
二、map容器(映射)
map存储键值对,键唯一且自动排序。
cpp
#include <iostream>
#include <map>
using namespace std;
int main() {
vector<int> arr = {1, 2, 3, 2, 1, 3, 4, 2, 1};
// 统计每个元素出现的次数
map<int, int> freq;
for (int x : arr) {
freq[x]++; // 使用[]操作符
}
// 遍历map
for (const auto& kv : freq) {
cout << kv.first << "出现" << kv.second << "次" << endl;
}
return 0;
}
三、unordered_set/unordered_map
无序版本,底层基于哈希表实现,查找更快(O(1)),但不保证顺序。
| 容器 | 底层结构 | 有序性 | 查找复杂度 |
|---|---|---|---|
set / map |
红黑树 | 有序 | O(log n) |
unordered_set / unordered_map |
哈希表 | 无序 | O(1) 平均 |
cpp
#include <iostream>
#include <unordered_set>
using namespace std;
int main() {
unordered_set<int> us = {5, 2, 8, 1, 9};
// 顺序不确定
for (int x : us) {
cout << x << " ";
}
cout << endl;
return 0;
}
总结
一、容器特性对比
| 容器 | 底层 | 随机访问 | 中间插入/删除 | 适用场景 |
|---|---|---|---|---|
vector |
动态数组 | ✅ O(1) | ❌ O(n) | 频繁访问、尾部操作 |
list |
双向链表 | ❌ O(n) | ✅ O(1) | 频繁中间插入删除 |
deque |
双端队列 | ✅ O(1) | 两端O(1) | 双端操作 |
set / map |
红黑树 | ❌ | 插入/查找O(log n) | 有序唯一集合 |
unordered_set / unordered_map |
哈希表 | ❌ | 插入/查找O(1) | 快速查找、去重 |
二、迭代器类型
| 迭代器 | 获取方式 | 支持操作 |
|---|---|---|
iterator |
begin() / end() |
读+写 |
const_iterator |
cbegin() / cend() |
只读 |
reverse_iterator |
rbegin() / rend() |
反向遍历 |
三、vector常用操作速查
| 操作 | 方法 | 复杂度 |
|---|---|---|
| 尾部插入 | push_back() |
O(1) |
| 尾部删除 | pop_back() |
O(1) |
| 中间插入 | insert() |
O(n) |
| 中间删除 | erase() |
O(n) |
| 访问元素 | [] / at() |
O(1) |
| 大小 | size() |
O(1) |
| 容量 | capacity() |
O(1) |
| 预分配 | reserve() |
O(n) |
四、list常用操作速查
| 操作 | 方法 | 复杂度 |
|---|---|---|
| 头部插入 | push_front() |
O(1) |
| 头部删除 | pop_front() |
O(1) |
| 尾部插入 | push_back() |
O(1) |
| 尾部删除 | pop_back() |
O(1) |
| 排序 | sort() |
O(n log n) |
| 去重 | unique() |
O(n) |
| 合并 | merge() |
O(n) |
STL是C++标准库的核心,掌握STL能够极大地提升开发效率。
STL使用三步法:
-
掌握基本使用方法
-
熟练运用各类容器和算法
-
理解源码实现并实现功能扩展
学习建议:
-
优先级:
vector>list>map>unordered_map -
迭代器遍历时注意引用传递避免拷贝
-
使用
reserve()预分配容量避免频繁扩容 -
优先使用
emplace_back()替代push_back()减少拷贝 -
根据场景选择合适的关联容器(有序选
map,无序选unordered_map)