CC++ & Algorithm

迭代器概念与分类体系

中等2
语言版本:通用
概述:用“快递员送快递”的比喻,解释STL迭代器是什么、为什么需要它,以及迭代器的五种基本分类。

迭代器:让遍历容器变成“点外卖”一样简单

迭代器到底是什么?为什么需要它?

想象你是一个班级的值日生,需要把全班同学的作业本收齐。如果同学们坐成一排,你可以从第一个同学开始,一个一个往后收。但如果同学们坐成环形、或者分散在不同教室呢?你可能需要不同的“收作业方案”。作为“收作业程序”,你希望有一个统一的步骤:每次只关注当前同学,然后移动到下一个,而不必管座位怎么排的。

在编程中,迭代器(iterator) 就是这样一个“统一步骤”。它像一根魔法棒,指向容器(比如数组、链表、集合)中的某个元素,然后你可以:

  • 看看这个元素是什么(解引用)
  • 移到下一个元素(递增)
  • 判断是不是已经遍历完(比较结束迭代器)

有了迭代器,你不需要关心容器内部是内存连续的一块(像数组)还是用链条串起来的(像链表),你都能用完全相同的方式去遍历。就像点外卖,不管店家在后厨怎么炒菜,你只需要收餐、吃、然后给下一单。

STL中的迭代器有哪些“等级”?

C++的STL(标准模板库)给迭代器分了五个等级,能力越来越强。你可以想象成不同等级的“外卖骑士”:

等级能力生活类比
输入迭代器 (Input Iterator)只能向前走,只能读一次,不能回头像你看一次黑板上的题目,记下来后就不能再看,因为你擦掉了
输出迭代器 (Output Iterator)只能向前走,只能写一次,不能回头像用粉笔在黑板上写字,写完后不能擦掉重写
前向迭代器 (Forward Iterator)可以向前走多次,既能读也能写像一本只允许向前翻的书,可以反复看同一页,但不能翻回去
双向迭代器 (Bidirectional Iterator)可以向前和向后移动像遥控汽车,可以前进后退,但不能直接飞到终点
随机访问迭代器 (Random Access Iterator)可以一下子跳到任意位置,还能比较大小像电梯,可以直接按10楼,不用从1楼一层层爬

容器自带的“骑士等级”不同

不同的容器提供的迭代器等级不一样,就像不同的外卖店派出的骑士能力不同:

  • vector、deque、array:随机访问迭代器(最强)—— 可以直飞任何位置
  • list、set、map:双向迭代器 —— 只能往前或者往后走
  • forward_list:前向迭代器 —— 只能往前走,不能后退
  • istream_iterator(从输入流读):输入迭代器 —— 只能读一次
  • ostream_iterator(往输出流写):输出迭代器 —— 只能写一次

新手最容易犯的4个错误

初学迭代器时,下面这些坑很容易踩到:

错误1:把迭代器当成普通指针乱加减

list<int> lst = {1,2,3};
auto it = lst.begin();
// 错误!list的迭代器不支持 it+2,因为不是随机访问迭代器
// auto it3 = it + 2;  // 编译错误!

正确做法:对于list,只能一步一步移动:++it; ++it; 或者用 std::advance(it, 2);

错误2:在遍历时修改容器导致迭代器失效

vector<int> vec = {1,2,3,4,5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    if (*it == 3) {
        vec.erase(it);  // 删除元素后,it就“坏掉”了,不能再用它移动!
    }
}

结果:程序可能崩溃或出现奇怪行为。正确做法是用 it = vec.erase(it); 删除后更新迭代器,或者使用算法 remove-erase

错误3:错误理解 end() 的含义

end() 指向的是最后一个元素之后的位置,不是最后一个元素。初学者常会这样做:

auto it = vec.end();
cout << *it;  // 错误!end()指向的位置没有元素,解引用是未定义行为

正确做法:如果想访问最后一个元素,用 *(vec.end()-1)(仅限支持随机访问的容器)或者 vec.back()

错误4:在Python中一边遍历一边修改列表

vec = [10, 20, 30, 40, 50]
for x in vec:
    if x == 30:
        vec.remove(x)  # 这会导致遍历跳过下一个元素,结果不可预测

建议:如果要在遍历时修改,通常用列表推导式或复制一份。

完整示例:用C++和Python对比不同迭代器的用法

下面我们写一个完整的程序,展示不同容器的迭代器能力差异,以及常见操作。

C++ 完整代码

#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <iterator>
#include <algorithm> // 包含 find, sort 等算法
using namespace std;

int main() {
    // 1. vector 提供随机访问迭代器(最强)
    vector<int> vec = {10, 20, 30, 40, 50};
    cout << "=== vector ===" << endl;
    // 用迭代器遍历
    cout << "正向遍历: ";
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        cout << *it << " ";      // 解引用得元素
    }
    cout << endl;

    // 随机访问能力:直接跳转
    auto it = vec.begin();
    cout << "it[2] = " << it[2] << endl;          // 30,等于 *(it+2)
    cout << "*(it+3) = " << *(it+3) << endl;      // 40
    cout << "最后一个元素: " << *(vec.end()-1) << endl; // 50

    // 还能比较大小:it1 < it2 表示 it1 在 it2 前面
    auto it1 = vec.begin() + 1;  // 指向20
    auto it2 = vec.begin() + 3;  // 指向40
    cout << "it1 < it2 ? " << (it1 < it2) << endl; // 1 (true)

    // 2. list 提供双向迭代器(无随机访问)
    list<int> lst = {100, 200, 300, 400};
    cout << "\n=== list ===" << endl;
    cout << "正向遍历: ";
    for (auto it = lst.begin(); it != lst.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 支持 --it 后退
    auto lit = lst.end();
    --lit; // 指向400
    cout << "最后一个元素: " << *lit << endl;
    // 但不支持 it+2: // 错误!list的迭代器不能 +
    // auto lit2 = lit + 2; // 编译错误

    // 3. set 也提供双向迭代器,但元素是常量(不能修改)
    set<int> s = {9, 3, 7, 1};
    cout << "\n=== set ===" << endl;
    cout << "set元素自动排序: ";
    for (auto it = s.begin(); it != s.end(); ++it) {
        cout << *it << " ";  // 输出: 1 3 7 9
        // *it = 5; // 错误!set的迭代器是 const_iterator,不能修改
    }
    cout << endl;

    // 4. 使用通用算法 find(只需要输入迭代器能力)
    // vector 的迭代器是随机访问,肯定能满足输入迭代器的要求
    auto found = find(vec.begin(), vec.end(), 30);
    if (found != vec.end()) {
        // 计算位置:随机访问迭代器支持减法
        int position = found - vec.begin();
        cout << "\n在vector中找到30,位置是: " << position << endl;
    }

    // 5. 注意:算法 sort 需要随机访问迭代器,所以不能用于list
    // sort(lst.begin(), lst.end()); // 编译错误!list的迭代器不支持
    // list 有自己的sort成员函数
    lst.sort(); // 正确

    return 0;
}

输出示例

=== vector ===
正向遍历: 10 20 30 40 50 
it[2] = 30
*(it+3) = 40
最后一个元素: 50
it1 < it2 ? 1

=== list ===
正向遍历: 100 200 300 400 
最后一个元素: 400

=== set ===
set元素自动排序: 1 3 7 9 

在vector中找到30,位置是: 2

Python 等价功能

Python 没有像 C++ 那样严格的迭代器分类,但我们可以通过一些技巧模拟。Python 的 for 循环底层就是迭代器,但通常只能单向移动。可以用 reversed() 向后,用索引实现随机访问。

# Python 中的迭代器演示
from collections.abc import Iterator, Iterable

print("=== Python 迭代器基础 ===")

# 1. 列表(list)本身不是迭代器,但它是可迭代的,可以用 iter() 获得迭代器
vec = [10, 20, 30, 40, 50]
print("列表是可迭代的吗?", isinstance(vec, Iterable))   # True
print("列表本身是迭代器吗?", isinstance(vec, Iterator)) # False

# 获取迭代器
it = iter(vec)
print("\n手动用 next 遍历:")
print(next(it))  # 10
print(next(it))  # 20

# 用 for 循环(本质就是不断 next)
print("\nfor 循环遍历:")
for x in vec:
    print(x, end=" ")
print()

# 2. 随机访问——直接用下标(不是迭代器功能,而是容器本身支持)
print("\n随机访问:")
print("vec[2] =", vec[2])       # 30

# 3. 反向遍历——reversed() 返回反向迭代器(只能向后)
print("\n反向遍历:")
for x in reversed(vec):
    print(x, end=" ")
print()

# 4. 集合 set 也是可迭代的,但元素无序
s = {9, 3, 7, 1}
print("\nset 遍历(无序):")
for x in s:
    print(x, end=" ")
print()

# 5. 注意:Python 迭代器不能后退,但可以用 itertools 或者转换为列表再索引
# 比如要获取第三个元素,不能用迭代器跳转,只能用下标
print("\n用下标获取第三个元素:", vec[2])

# 6. 通用查找函数——用 in 操作符(需要容器支持迭代)
print("30 在 vec 中吗?", 30 in vec)  # 等价于遍历比较

输出

=== Python 迭代器基础 ===
列表是可迭代的吗? True
列表本身是迭代器吗? False

手动用 next 遍历:
10
20

for 循环遍历:
10 20 30 40 50 

随机访问:
vec[2] = 30

反向遍历:
50 40 30 20 10 

set 遍历(无序):
1 3 7 9 

用下标获取第三个元素: 30
30 在 vec 中吗? True

总结:记住三个要点

  1. 迭代器是“万能钥匙”:不管容器是数组、链表还是哈希表,只要它提供迭代器,你就可以用相同的方式遍历。
  2. 迭代器等级决定能力:C++ 中等级越高,能做的操作越多(比如跳跃、比较大小)。如果你需要排序,只能用随机访问迭代器(比如 vector 的);如果只是找元素,输入迭代器就够了。
  3. 小心迭代器“坏掉”:不要在遍历时随意修改容器(插入/删除),否则迭代器可能失效。C++ 中 vector 的 push_back 也可能导致所有迭代器失效。

接下来学什么?

  • 输入和输出迭代器:用于处理数据流(如文件读写),只能单次通过。
  • 迭代器适配器(如 back_inserterostream_iterator):让你把算法输出直接插入到容器或流中。
  • 迭代器失效规则:不同容器在插入/删除后,哪些迭代器会失效?这是写健壮代码的关键。

掌握了迭代器,你就拥有了操作容器的“通用语言”,后面学习算法(如排序、查找、复制)时会更加顺畅。

例题精讲

1单选题

在STL中,迭代器被比喻为“快递员送快递”。下列关于迭代器作用的描述,哪一项是正确的?

A迭代器是容器内部存储数据的实际对象
B迭代器是一种智能指针,用于遍历容器中的元素并屏蔽底层实现细节
C迭代器只能用于vector容器,不能用于其他容器
D迭代器的主要功能是修改容器的内存分配策略
2判断题

STL迭代器根据功能特性被划分为五种基本分类:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。

3单选题

下列哪种迭代器支持使用下标运算符(如it[3])直接访问容器元素?

A前向迭代器
B双向迭代器
C随机访问迭代器
D输入迭代器
4填空题
使用迭代器遍历一个vector<int>并打印每个元素,请补全以下代码:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<int> v = {10, 20, 30};
    for (___ it = v.begin(); it != v.end(); ++it) {
        cout << ___ << " ";
    }
    return 0;
}
5判断题

一个双向迭代器(Bidirectional Iterator)只能向前移动,不能向后移动。