前向迭代器、双向迭代器与随机访问迭代器
较难2迭代器三兄弟:前向、双向和随机访问
什么是迭代器?为什么要有等级?
迭代器就像你在程序中“走路”的工具。你有一个容器(比如一个装满零食的篮子、一堆成绩单、或一串游戏角色),你想一个一个地访问里面的元素,或者修改它们。迭代器就是帮你做这件事的“导航仪”。
但是,不同容器的“内部结构”不一样:有些像一本只能往前翻的书(单向链表),有些像可以前进后退的遥控车(双向链表),有些像电梯可以直接去任意楼层(数组)。所以迭代器也分成了不同等级,每种等级能做的事情不同。等级越高,功能越强大,但并不是所有容器都能提供最高等级的迭代器。就好像你不能要求一辆遥控车直接“瞬移”到20米外一样——它只能一步一步走。
下面我们用三个生活例子来认识这三位“兄弟”:
1. 前向迭代器:只能向前翻的书
想象你有一本只有单面页码的笔记本,你从第一页开始看,只能往后翻(++),不能往前翻。你可以随时停下来读这一页的内容(解引用),也可以在这一页上写笔记(写入)。你可以反复翻看同一页很多次(多遍扫描),甚至可以用书签标记某一页,回头再来看(复制迭代器)。但如果你翻过一页后想回到前一页,对不起,你只能从头重新翻一遍。
前向迭代器就是这样的工具:它只能单向移动(++),但支持读、写和多遍扫描。
支持的操作:
*it:读取当前元素*it = 新值:修改当前元素(如果允许修改)++it或it++:移到下一个元素it1 == it2,it1 != it2:比较两个迭代器是否指向同一位置it1 = it2:赋值,让it1指向it2的位置- 可以复制迭代器(保存副本),各自独立前进
典型容器:forward_list(单向链表)、unordered_set、unordered_map(无序集合/映射)
生活场景:你在排队做核酸,队伍很长,你只能往前移动,不能后退。你可以记住当前位置(比如记下前面人的衣服颜色),然后继续走,回头再用这个记忆找到那个位置。
新手常见错误:
- 对前向迭代器使用
--(会编译错误) - 以为只能遍历一次(实际上可以保存副本多次遍历,但不能后退)
2. 双向迭代器:可以前进后退的遥控车
遥控车可以向前开(++),也可以倒车(--),但你不能直接“传送”到很远的地方,必须一步一步地移动。你可以随时停在某个位置,查看或修改周围的东西(比如检查车底下有没有石头)。
双向迭代器支持所有前向操作,再加上--操作。
支持的操作:
- 所有前向迭代器的操作
--it或it--:移到上一个元素
典型容器:list(双向链表)、set、map、multiset、multimap
生活场景:你玩一个关卡游戏,可以按“前进”键走到下一关,按“后退”键回到上一关,但不能直接跳到第10关。
3. 随机访问迭代器:直达的电梯
电梯可以直接按任意楼层,不需要一层层经过。你可以比较不同楼层(比如12楼在15楼前面),计算两楼层之间隔了几层。这就是随机访问迭代器:支持“跳转”和“算术运算”。
支持的操作:
- 所有双向迭代器的操作
it + n或it - n:向前或向后跳n个元素(O(1)时间)it[n]:等价于*(it + n)it1 < it2、it1 > it2、it1 <= it2、it1 >= it2:比较位置前后it2 - it1:返回两个迭代器之间的距离(整数值)it += n、it -= n:一次跳多个位置
典型容器:vector、deque、array、string
生活场景:你翻开一本新华字典,可以直接翻到第100页,不用一页一页翻。你还可以比较“猫”字在第多少页,“狗”字在第多少页,算出它们相隔几页。
为什么需要这么多等级?—— 容器的“物理限制”
- 链表(
forward_list、list)的节点在内存里是分散的,每个节点只记录下一个(或上一个)节点的地址。所以你想访问第5个节点,只能从头开始一个一个走,没办法直接跳到5。这就是为什么链表只提供低等级的迭代器。 - 数组(
vector、array)的所有元素在内存里是连续排列的,就像一排连续的门牌号。你知道第一个门牌号,想知道第10个,直接加9就能得到地址。所以它可以提供随机访问迭代器。
这种分级设计让C++的算法很聪明:一个算法如果只需要前向迭代器,它就能用在所有容器上(比如std::find)。如果它需要随机访问迭代器,就只能用在vector等少数容器上(比如std::sort)。这样算法写一次,能用的容器更多,代码更通用。
算法对迭代器等级的要求(一张表)
| 算法 | 所需最低迭代器等级 | 时间复杂度 | 举例生活场景 |
|---|---|---|---|
find | 输入迭代器 | O(n) | 在一堆作业本里找小明的那本,只能一本一本地翻 |
for_each | 输入迭代器 | O(n) | 给每个同学发一颗糖,一个一个发 |
copy | 输入/输出迭代器 | O(n) | 把成绩单抄一份到另一个本子上 |
reverse | 双向迭代器 | O(n) | 把一摞书从第一本到最后一本翻转顺序,需要能后退 |
sort | 随机访问迭代器 | O(n log n) | 给一叠扑克牌排序,需要能直接交换两张牌的位置,快速找到中间牌 |
binary_search | 随机访问迭代器 | O(log n) | 在电话本里找“张三”,直接翻到中间比较 |
C++完整代码示例(带详细中文注释)
#include <iostream>
#include <vector> // 向量,提供随机访问迭代器
#include <list> // 双向链表,提供双向迭代器
#include <forward_list> // 单向链表,提供前向迭代器
#include <algorithm> // 算法库,如 find, sort, reverse
using namespace std;
int main() {
// ---------- 1. 前向迭代器示例:forward_list ----------
forward_list<int> flist = {5, 2, 8, 1}; // 单向链表
cout << "forward_list (只能向前): ";
auto it_f = flist.begin(); // 获取起始迭代器
auto saved = it_f; // 保存副本(可以多次遍历)
while (it_f != flist.end()) { // 第一次遍历
cout << *it_f << " "; // 读取元素
++it_f; // 前进一步
}
cout << endl;
// 用保存的副本再次遍历
cout << "第二次遍历 saved: ";
while (saved != flist.end()) {
cout << *saved << " ";
++saved;
}
cout << endl;
// ---------- 2. 双向迭代器示例:list ----------
list<int> lst = {9, 4, 7, 2}; // 双向链表
auto it_l = lst.begin(); // 起始迭代器
// 向前走两步
++it_l; // 现在指向 4
++it_l; // 现在指向 7
cout << "list 当前元素: " << *it_l << endl; // 输出 7
// 后退一步
--it_l; // 现在指向 4
cout << "后退后元素: " << *it_l << endl; // 输出 4
// 反向遍历(使用双向迭代器的递减)
cout << "反向遍历list: ";
auto rit = lst.end(); // 注意:end() 指向最后一个元素之后
while (rit != lst.begin()) {
--rit; // 先退一步,再读取
cout << *rit << " ";
}
cout << endl;
// ---------- 3. 随机访问迭代器示例:vector ----------
vector<int> vec = {1, 2, 3, 4, 5, 6}; // 动态数组
auto it_v = vec.begin(); // 起始迭代器
// 直接跳转
cout << "vector 第3个元素 (it+2): " << *(it_v + 2) << endl; // 输出 3
cout << "vector 第5个元素 via []: " << it_v[4] << endl; // 输出 5
// 计算两个迭代器之间的距离
cout << "两个迭代器距离 (end - begin): " << (vec.end() - vec.begin()) << endl; // 输出 6
// sort 需要随机访问迭代器,vector 满足
sort(vec.begin(), vec.end()); // 默认从小到大排序
cout << "排序后 vector: ";
for (int x : vec) cout << x << " ";
cout << endl;
// ---------- 4. 算法对不同迭代器等级的要求 ----------
// find 只需要输入迭代器,vector 的随机访问迭代器也可以使用
auto found = find(vec.begin(), vec.end(), 4);
if (found != vec.end()) {
cout << "找到4在位置: " << (found - vec.begin()) << endl;
}
// reverse 需要双向迭代器,list 满足
reverse(lst.begin(), lst.end()); // 翻转 list
cout << "翻转后 list: ";
for (int x : lst) cout << x << " ";
cout << endl;
// 下面这行会编译错误,因为 forward_list 不是双向:
// reverse(flist.begin(), flist.end()); // 错误!
return 0;
}
代码解释关键点
forward_list的迭代器只能++,不能--。你仍然可以保存副本多次遍历,但无法后退。list的迭代器支持++和--,但list没有sort成员函数?不,list有自己的sort()成员(lst.sort()),但标准库的std::sort不能用,因为需要随机访问。vector的迭代器支持所有运算,甚至可以用<比较大小(如it1 < it2),但上面没演示。
Python 中的类似概念(虽然等级不严格)
Python 的列表(list)天生就是随机访问的,可以下标访问、切片、反向遍历。但 Python 没有像 C++ 那样把迭代器分成明确的等级。不过我们可以用不同类型来感受差异:
from collections import deque
# 1. 前向迭代器模拟(用生成器只能向前)
def forward_iterator(iterable):
"""只允许前向移动的迭代器(通过yield from)"""
it = iter(iterable) # 获取迭代器
for x in it: # 只能往前
yield x
# 使用
flist = [5, 2, 8, 1]
print("前向迭代器遍历:")
for x in forward_iterator(flist):
print(x, end=" ")
print()
# 2. 双向迭代器模拟(deque 支持正向和反向遍历)
d = deque([9, 4, 7, 2])
print("\ndeque 正向遍历:")
for x in d:
print(x, end=" ")
print("\ndeque 反向遍历:")
for x in reversed(d):
print(x, end=" ")
print()
# 3. 随机访问迭代器(Python 列表天然支持)
vec = [1, 2, 3, 4, 5, 6]
print("\n向量第3个元素:", vec[2]) # 下标访问
print("向量第5个元素:", vec[4])
print("索引距离:", len(vec)) # 长度
vec.sort() # 排序
print("排序后:", vec)
print("倒数第2个元素:", vec[-2]) # 也支持负索引
新手常犯的错误
- 对前向迭代器用
--:比如对forward_list的迭代器执行--it,编译器会报错“no operator--”。 - 对双向迭代器用
it + 5:比如对list的迭代器写it + 5,会编译错误,因为不是随机访问。 - 用
std::sort对list排序:很多人想当然地说“用sort(lst.begin(), lst.end())”,但list的迭代器不是随机的,所以会报错。正确做法是用lst.sort()成员函数。 - 忘记
end()指向最后一个元素之后:反向遍历时,常常需要先--rit再读,否则会越界。 - 混淆迭代器与指针:虽然随机访问迭代器支持
[]和算术,但不代表它就是原生指针,比如vector<int>的迭代器在调试模式下可能是一个类对象。
总结要点
- 前向迭代器:只能
++,支持多遍扫描,适用forward_list、unordered_set等。 - 双向迭代器:能
++和--,适用list、set、map等。 - 随机访问迭代器:能跳转、比较、计算距离,适用
vector、deque、array、string。 - 迭代器等级决定了容器可以搭配哪些算法:
sort需要随机访问,reverse需要双向,find只需要输入。 - 用
auto自动推导迭代器类型,省时省力。
相关指引
- 如果你对迭代器分类还不太熟,可以先学习输入迭代器和输出迭代器(本章的前置知识)。
- 下一节我们会介绍反向迭代器(
rbegin()/rend())和插入迭代器(back_inserter等),它们让遍历和插入变得非常方便。 - 想深入了解各容器迭代器特性的同学,可以查看C++参考手册中关于
iterator tags的内容。
例题精讲
在C++中,前向迭代器(Forward Iterator)只能向前移动,不能后退。以下哪个操作是前向迭代器的合法操作?
以下关于随机访问迭代器(Random Access Iterator)的描述中,哪个是错误的?
双向迭代器(Bidirectional Iterator)在功能上包含了前向迭代器(Forward Iterator)的所有操作,因此任何使用前向迭代器的代码都可以替换为双向迭代器。
以下代码使用前向迭代器(假设容器为std::forward_list)遍历元素并求和。请补全空白处的代码。
std::forward_list<int> flist = {1,2,3,4,5};
int sum = 0;
for (auto it = flist.begin(); it != flist.end(); ___) {
sum += *it;
}下面代码利用随机访问迭代器实现二分查找(假设数组已排序)。请补全函数签名中迭代器类型的声明。
template <typename ___>
bool binary_search(Iterator first, Iterator last, int target) {
auto left = first;
auto right = last - 1;
while (left <= right) {
auto mid = left + (right - left) / 2;
if (*mid == target) return true;
else if (*mid < target) left = mid + 1;
else right = mid - 1;
}
return false;
}