迭代器深入:迭代器分类与适配器实战

迭代器这东西,说白了就是STL的"万能胶"。它把容器和算法粘在一起,让一套算法能适配各种数据结构。我刚开始学STL时,总觉得迭代器不就是个指针吗?后来踩了不少坑才明白——迭代器远比指针复杂,而且分类体系才是它的精髓。

核心认知:迭代器分类决定了你能用什么算法。选错迭代器类型,轻则编译报错,重则运行时崩溃。

7.1 迭代器分类体系

STL定义了五种迭代器,按能力从弱到强排列。我习惯把它们分成三个梯队:

迭代器分类体系 第一梯队:基础读写 输入迭代器(istream_iterator) 输出迭代器(ostream_iterator) 第二梯队:单向遍历 前向迭代器(forward_list) 双向迭代器(list, set, map) 第三梯队:随机访问 随机访问迭代器(vector, deque, array) 支持:+、-、[]、<、> 等操作 能力从弱到强,高能力迭代器可替代低能力迭代器 但低能力迭代器不能用于需要高能力的算法

7.2 各分类详解

输入迭代器(Input Iterator)

只能读,不能写。而且读一次就没了——你想想看,从键盘读数据,读完了还能再读一遍吗?

// 典型例子:istream_iterator
std::istream_iterator<int> it(std::cin);
std::istream_iterator<int> end;  // 哨兵迭代器

// 只能单向遍历,不能回退
int val = *it;   // 读取
++it;            // 前进
// --it;         // 错误!不支持回退

实战经验:我曾在日志解析模块里用istream_iterator配合copy算法,一行代码就把文件里的数字全读进vector。但要注意——输入迭代器是"一次性"的,别想着反复遍历。

输出迭代器(Output Iterator)

只能写,不能读。说白了就是个"只管往外倒"的接口。

// 典型例子:ostream_iterator
std::ostream_iterator<int> it(std::cout, ", ");
*it = 42;  // 写入
++it;      // 前进
*it = 100; // 再写入

前向迭代器(Forward Iterator)

既能读又能写,还能反复遍历同一段数据。forward_list的迭代器就是典型。

双向迭代器(Bidirectional Iterator)

在前向迭代器基础上,多了个--操作。list、set、map的迭代器都属于这一类。

随机访问迭代器(Random Access Iterator)

这是"满血版"迭代器。vector、deque、array的迭代器都支持。你可以像操作指针一样:

std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
it += 3;        // 跳3步
int val = it[1]; // 下标访问
bool cmp = it < vec.end(); // 比较大小

避坑指南:我曾经在代码里用std::sort对std::list排序,结果编译报错。为什么?因为sort要求随机访问迭代器,而list只提供双向迭代器。后来改用list::sort()才搞定。记住:算法对迭代器类型有硬性要求。

7.3 迭代器适配器

迭代器适配器,就是在已有迭代器上"套一层",改变它的行为。我常用的有三个:

适配器 作用 典型场景
reverse_iterator 反向遍历 从尾到头处理数据
back_insert_iterator 自动调用push_back 向容器尾部插入数据
front_insert_iterator 自动调用push_front 向容器头部插入数据

reverse_iterator 实战

std::vector<int> vec = {1, 2, 3, 4, 5};

// 反向遍历
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    std::cout << *it << " ";  // 输出:5 4 3 2 1
}

// 注意:rbegin() 指向最后一个元素
// rend() 指向第一个元素之前

back_insert_iterator 实战

std::vector<int> src = {1, 2, 3};
std::vector<int> dst;

// 不用back_inserter的话,你得手动resize
// 用了它,自动push_back
std::copy(src.begin(), src.end(), std::back_inserter(dst));

// dst 现在包含 1, 2, 3

个人习惯:我写算法时,只要目标容器可能为空,一律用back_inserter。它省去了手动管理内存的麻烦,代码也更安全。

7.4 自定义迭代器实现

有时候STL提供的迭代器不够用,你得自己写。比如封装一个自定义容器,或者给现有容器加特殊行为。

下面我实现一个简单的"步进迭代器",每次跳2步:

template<typename Iterator>
class StepIterator {
public:
    using iterator_category = std::forward_iterator_tag;
    using value_type = typename std::iterator_traits<Iterator>::value_type;
    using difference_type = typename std::iterator_traits<Iterator>::difference_type;
    using pointer = typename std::iterator_traits<Iterator>::pointer;
    using reference = typename std::iterator_traits<Iterator>::reference;

    StepIterator(Iterator it) : current_(it) {}

    reference operator*() const { return *current_; }
    pointer operator->() const { return &(*current_); }

    // 每次跳2步
    StepIterator& operator++() {
        ++current_;
        ++current_;  // 跳2步
        return *this;
    }

    StepIterator operator++(int) {
        StepIterator tmp = *this;
        ++(*this);
        return tmp;
    }

    bool operator==(const StepIterator& other) const {
        return current_ == other.current_;
    }

    bool operator!=(const StepIterator& other) const {
        return !(*this == other);
    }

private:
    Iterator current_;
};

// 使用示例
std::vector<int> vec = {1, 2, 3, 4, 5, 6};
StepIterator<std::vector<int>::iterator> it(vec.begin());
StepIterator<std::vector<int>::iterator> end(vec.end());

for (; it != end; ++it) {
    std::cout << *it << " ";  // 输出:1 3 5
}

关键点:自定义迭代器必须正确声明iterator_category。这决定了它能用在哪些算法里。我上面声明的是forward_iterator_tag,所以只能用于前向遍历的算法。如果你需要随机访问能力,就得实现+、-、[]等操作。

7.5 迭代器失效问题

嗯,这里要注意。迭代器失效是C++里最隐蔽的坑之一。我当年在线上环境排查过一个bug,查了整整两天,最后发现是迭代器失效导致的。

常见的失效场景:

  • vector:插入/删除元素可能导致所有迭代器失效(因为可能重新分配内存)
  • deque:在中间插入/删除会使所有迭代器失效
  • list:只有被删除的那个迭代器失效,其他安全
  • map/set:只有被删除的那个迭代器失效
// 错误示范:迭代器失效
std::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 会导致未定义行为
    }
}

// 正确做法:利用erase返回下一个有效迭代器
for (auto it = vec.begin(); it != vec.end(); ) {
    if (*it == 3) {
        it = vec.erase(it);  // it 更新为下一个元素
    } else {
        ++it;
    }
}

血的教训:我曾经在遍历vector时删除了元素,然后继续用原来的迭代器++,结果程序随机崩溃。调试器里看迭代器指向的内存已经变成乱码了。记住:erase会返回下一个有效迭代器,一定要用它来更新。

迭代器是STL的骨架,理解透了,你写出来的代码才能既高效又安全。下一节我们聊聊算法,到时候你会发现——迭代器分类直接决定了你能用哪些算法,两者是密不可分的。


公众号:蓝海资料掘金营,微信deep3321