C++: STL 迭代器
第34-35课我们学了STL容器和算法。
现在,我们要深入STL的" glue"——迭代器。
理解了迭代器,才能真正理解STL的设计哲学。
1. 迭代器概述
(1) 1.1 什么是迭代器?
迭代器是STL的核心概念,它连接容器和算法。
生活类比:
- 容器 = 仓库
- 算法 = 工人
- 迭代器 = 工人手中的提货单(告诉工人去哪个货架取货)
(2) 1.2 迭代器的基本操作
所有迭代器都支持以下操作:
| 操作 | 说明 | 示例 |
|---|---|---|
*it |
解引用 | int x = *it; |
it++ |
前进到下一个元素 | ++it; |
it-- |
后退到前一个元素 | --it; |
it1 == it2 |
比较相等 | if (it1 == it2) |
it1 != it2 |
比较不等 | while (it != end()) |
2. 迭代器分类
(1) 2.1 五种迭代器
STL定义了5种迭代器,功能从弱到强:
| 迭代器类型 | 功能 | 代表容器 |
|---|---|---|
| 输入迭代器 | 只读,单向 | istream_iterator |
| 输出迭代器 | 只写,单向 | ostream_iterator |
| 前向迭代器 | 读写,单向 | forward_list |
| 双向迭代器 | 读写,双向 | list、set、map |
| 随机访问迭代器 | 读写,随机访问 | vector、deque、array |
(2) 2.2 迭代器能力对比
TEXT
📖 仅展示
输入迭代器 ← 最弱
↓
前向迭代器
↓
双向迭代器
↓
随机访问迭代器 ← 最强
能力越强,支持的操作越多:
| 操作 | 输入 | 前向 | 双向 | 随机访问 |
|---|---|---|---|---|
解引用 * |
✅ | ✅ | ✅ | ✅ |
前进 ++ |
✅ | ✅ | ✅ | ✅ |
后退 -- |
❌ | ❌ | ✅ | ✅ |
| 随机访问 `` | ❌ | ❌ | ❌ | ✅ |
算术运算 + - |
❌ | ❌ | ❌ | ✅ |
(3) 2.3 示例:不同容器的迭代器
CPP
#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
int main() {
std::vector<int> v = {1, 2, 3};
std::list<int> l = {1, 2, 3};
std::forward_list<int> fl = {1, 2, 3};
// vector:随机访问迭代器
auto it_v = v.begin();
std::cout << it_v[2] << std::endl; // 可以随机访问
// list:双向迭代器
auto it_l = l.begin();
++it_l; // 可以前进
--it_l; // 可以后退
// it_l[2]; // ❌ 错误!list不支持随机访问
return 0;
}
输出:
TEXT
📖 仅展示
3
3. 迭代器失效
(1) 3.1 什么是迭代器失效?
迭代器失效是指:容器操作后,迭代器指向的位置变得无效。
常见原因:
- 内存重新分配(
vector的push_back) - 元素删除(
erase)
(2) 3.2 不同容器的迭代器失效规则
| 容器 | 操作 | 失效情况 |
|---|---|---|
vector |
push_back |
可能失效(重新分配时) |
vector |
erase |
被删元素及之后的迭代器失效 |
list |
push_back |
不失效 |
list |
erase |
只有被删元素的迭代器失效 |
map/set |
erase |
只有被删元素的迭代器失效 |
(3) 3.3 示例:vector迭代器失效(难度⭐⭐)
▶ 示例 1:STL容器使用(难度⭐)
CPP
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
std::cout << "*it = " << *it << std::endl; // 输出:1
// push_back可能导致重新分配,迭代器失效
v.push_back(6);
// ❌ 危险!it可能失效
// std::cout << "*it = " << *it << std::endl; // 未定义行为
// ✅ 正确做法:重新获取迭代器
it = v.begin();
std::cout << "*it = " << *it << std::endl; // 输出:1
return 0;
}
输出:
TEXT
📖 仅展示
*it = 1
*it = 1
(4) 3.4 安全删除元素
错误做法:
CPP
for (auto it = v.begin(); it != v.end(); ++it) {
### ▶ 示例 2:现代C++特性应用(难度⭐)
if (*it % 2 == 0) {
v.erase(it); // ❌ it失效!
}
}
正确做法:
CPP
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // ✅ erase返回下一个有效迭代器
} else {
++it;
}
}
输出:
TEXT
📖 仅展示
(程序输出)
4. 反向迭代器
(1) 4.1 什么是反向迭代器?
反向迭代器从容器末尾向前遍历。
示例:逆序输出(难度⭐)
CPP
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用反向迭代器
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
运行结果:
TEXT
📖 仅展示
5 4 3 2 1
5. 插入迭代器
(1) 5.1 什么是插入迭代器?
插入迭代器是一种输出迭代器,用于向容器插入元素。
三种插入迭代器:
| 迭代器 | 功能 | 示例 |
|---|---|---|
back_inserter |
尾部插入 | std::back_inserter(v) |
front_inserter |
头部插入 | std::front_inserter(l) |
inserter |
指定位置插入 | std::inserter(v, v.begin()) |
(2) 5.2 示例:用back_inserter复制(难度⭐⭐)
CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst; // 空容器
// ❌ 错误!dst空间不足
// std::copy(src.begin(), src.end(), dst.begin());
// ✅ 正确!用back_inserter自动扩容
std::copy(src.begin(), src.end(), std::back_inserter(dst));
std::cout << "复制结果:";
for (int x : dst) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
输出:
TEXT
📖 仅展示
1 2 3 4 5
6. 流迭代器
(1) 6.1 输入流迭代器
功能: 从输入流读取数据。
示例:从标准输入读取(难度⭐⭐)
CPP
#include <iostream>
#include <vector>
#include <iterator>
int main() {
std::vector<int> v;
std::cout << "输入几个数字(Ctrl+Z结束):" << std::endl;
// 从标准输入读取
std::copy(std::istream_iteratorint(std::cin),
std::istream_iteratorint(),
std::back_inserter(v));
std::cout << "你输入了:";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 6.2 输出流迭代器
功能: 向输出流写入数据。
示例:输出到文件(难度⭐⭐)
CPP
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 输出到标准输出
std::copy(v.begin(), v.end(),
std::ostream_iteratorint(std::cout, " "));
std::cout << std::endl;
// 输出到文件
std::ofstream file("output.txt");
std::copy(v.begin(), v.end(),
std::ostream_iteratorint(file, "\n"));
file.close();
return 0;
}
❓ 常见问题
Q 为什么list不支持随机访问?
A
list 是链表,元素在内存中不连续,无法用索引直接访问。Q 迭代器失效怎么避免?
A 1. 每次容器操作后,重新获取迭代器 2. 用
erase 的返回值更新迭代器 3. 尽量用算法,少手动操作迭代器Q:const_iterator是什么? A:
const_iterator是只读迭代器,不能修改元素值。
CPP
std::vector<int> v = {1, 2, 3};
std::vector<int>::const_iterator it = v.cbegin();
// *it = 10; // ❌ 错误!不能修改
▶ 示例 3:迭代器遍历(难度⭐)
CPP
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
输出:
TEXT
📖 仅展示
1 2 3 4 5
💡 提示:迭代器像指针一样,用
* 解引用,用 ++ 移动,用 != 比较位置。
| 知识点 | 要点 |
|---|---|
| 迭代器分类 | 输入→前向→双向→随机访问 |
| 迭代器失效 | vector可能失效,list不会 |
| 反向迭代器 | rbegin()/rend() |
| 插入迭代器 | back_inserter等 |
| 流迭代器 | 连接STL和IO |
📖 小节
- 迭代器:类似指针,用于遍历容器元素
- 迭代器分类:输入/输出/前向/双向/随机访问
- begin()/end():获取容器首尾迭代器
- 迭代器失效:插入/删除可能导致迭代器失效
📝 作业
-
**基础题 (Difficulty ⭐):用迭代器遍历 vector
<int>并输出所有元素。分别用 begin/end 和范围 for 两种方式。 -
**进阶题 (Difficulty ⭐⭐):用反向迭代器 rbegin/rend 倒序遍历 vector 并输出。观察与正序遍历的区别。
-
**挑战题 (Difficulty ⭐⭐⭐):实现一个自定义迭代器,包装一个整数范围(如 1 到 10),支持 ++ 和 * 操作符。
- 迭代器是容器和算法的桥梁
- 五种迭代器:输入/输出/前向/双向/随机访问
- 范围 for 循环底层用迭代器实现
- 迭代器失效:插入/删除后某些迭代器不可用
- const_iterator 只读访问元素
下一课:STL函数对象(#38)