三种迭代器:指针、std::iterator 和自定义迭代器的完整对比与实践

在 C++ 开发中,迭代器是容器访问的核心工具。虽然最常见的用法是使用标准容器(如 std::vector、std::list 等)自带的迭代器,但真正的强大之处在于我们可以自行定义迭代器来满足特定需求。下面从三个层面——指针、标准库提供的 std::iterator 基础类以及自定义迭代器——对比它们的实现细节、适用场景以及如何组合使用,帮助你在项目中更灵活地操纵数据。

1. 指针作为迭代器

1.1 简单易用

在数组和 C 风格的容器中,原始指针就天然地充当了迭代器。它们满足了所有 C++ 迭代器概念所要求的成员操作:

  • *it 取值
  • ++it 前置递增
  • it++ 后置递增
  • it == other 比较
int arr[5] = {1,2,3,4,5};
for (int* it = arr; it != arr + 5; ++it) {
    std::cout << *it << ' ';
}

1.2 局限性

  • 仅支持顺序访问,无法轻易实现随机访问的其他约束。
  • 与容器生命周期绑定,容器变动时可能导致悬空指针。
  • 缺乏类型安全,容易出现越界或空指针访问。

2. std::iterator(或更现代的 std::iterator_traits)

2.1 继承自 std::iterator

在 C++17 之前,创建自定义迭代器最常用的做法是继承 std::iterator,并提供所需的 value_typepointerreferencedifference_typeiterator_category 等 typedefs。

template <typename T>
class SimpleIter : public std::iterator<std::random_access_iterator_tag, T> {
public:
    SimpleIter(T* ptr) : ptr_(ptr) {}
    T& operator*() const { return *ptr_; }
    SimpleIter& operator++() { ++ptr_; return *this; }
    // 其余操作略
private:
    T* ptr_;
};

2.2 现代实践

C++20 推出了更简洁的方式:直接使用 std::iterator_traits,不再需要继承 std::iterator。只需满足以下成员即可:

template <typename T>
class SimpleIter {
public:
    using value_type = T;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using reference = T&;
    using iterator_category = std::random_access_iterator_tag;
    // 操作符实现
};

这种方式更符合现代 C++ 的“无继承”理念,减少了冗余代码。

3. 自定义迭代器:实战案例

3.1 场景

假设我们实现一个 “可逆链表”,其中每个节点除了指向下一个节点外,还指向上一个节点。我们希望迭代器既能正向遍历也能反向遍历。

3.2 设计思路

  • 迭代器类型:双向迭代器(std::bidirectional_iterator_tag)。
  • 内部维护 Node* current_
  • 支持 ++ 前置递增、-- 前置递减。
  • 需要实现比较、解引用、加减运算符(若实现随机访问可选)。

3.3 代码实现

template <typename T>
class ReversibleLinkedList {
    struct Node {
        T data;
        Node* next;
        Node* prev;
        Node(const T& v) : data(v), next(nullptr), prev(nullptr) {}
    };
    Node* head_;
    Node* tail_;
public:
    ReversibleLinkedList() : head_(nullptr), tail_(nullptr) {}
    // 省略 push_back/pop_front 等操作

    class Iterator {
    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using value_type = T;
        using difference_type = std::ptrdiff_t;
        using pointer = T*;
        using reference = T&;

        Iterator(Node* ptr) : node_(ptr) {}
        reference operator*() const { return node_->data; }
        pointer operator->() const { return &(node_->data); }

        // 前置递增
        Iterator& operator++() { node_ = node_->next; return *this; }
        // 后置递增
        Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; }

        // 前置递减
        Iterator& operator--() { node_ = node_->prev; return *this; }
        // 后置递减
        Iterator operator--(int) { Iterator tmp = *this; --(*this); return tmp; }

        bool operator==(const Iterator& other) const { return node_ == other.node_; }
        bool operator!=(const Iterator& other) const { return node_ != other.node_; }

    private:
        Node* node_;
    };

    Iterator begin() { return Iterator(head_); }
    Iterator end() { return Iterator(nullptr); }
    Iterator rbegin() { return Iterator(tail_); }
    Iterator rend() { return Iterator(nullptr); }
};

3.4 使用示例

ReversibleLinkedList <int> list;
list.push_back(10);
list.push_back(20);
list.push_back(30);

for (auto it = list.begin(); it != list.end(); ++it) {
    std::cout << *it << ' ';   // 10 20 30
}
std::cout << '\n';

for (auto it = list.rbegin(); it != list.rend(); --it) {
    std::cout << *it << ' ';   // 30 20 10
}

4. 何时使用哪种迭代器?

需求 推荐迭代器类型 说明
简单遍历固定大小数组 指针 代码简洁,性能最高
需要类型安全、跨容器一致接口 std::iterator/std::iterator_traits 与 STL 容器迭代器兼容
自定义容器或非标准数据结构 自定义迭代器 可实现任意访问策略、延迟加载等功能

5. 小结

  • 指针是最轻量的迭代器,适用于原始数组和已知生命周期的数据。
  • std::iterator/std::iterator_traits 为自定义迭代器提供标准接口,兼容 STL 生态。
  • 自定义迭代器让你可以为任何数据结构提供遍历能力,提升代码复用性与可维护性。

掌握这三种迭代器的使用方式,你就能在 C++ 项目中无论是快速原型还是大规模系统,都能灵活选择最合适的遍历工具。

发表评论