1. 项目概述为什么我们需要迭代器模式在C项目里尤其是涉及到游戏开发、数据处理或者任何需要管理复杂集合的场景我们经常会遇到一个头疼的问题如何优雅地遍历一个集合你可能会说这还不简单用for循环不就行了比如对一个std::vectorint直接for (int i 0; i vec.size(); i)或者用范围for循环for (auto val : vec)。但现实往往更复杂。想象一下你正在开发一个游戏引擎里面有一个GameObjectManager类它内部可能用std::vector存储活跃的游戏对象用std::list存储待销毁的对象甚至还有一个自定义的八叉树空间分割结构来加速碰撞检测。现在你的渲染模块需要遍历所有可见对象物理模块需要遍历所有刚体UI模块需要遍历所有UI元素。如果每个模块都直接去访问GameObjectManager内部的这些五花八门的容器会带来几个大问题暴露内部实现渲染模块的开发者必须知道GameObjectManager用的是vector、list还是别的什么这违反了封装原则。一旦GameObjectManager为了性能优化把vector换成了std::deque所有依赖它的模块代码都得跟着改维护成本爆炸。遍历逻辑重复不同的容器有不同的遍历方式数组用索引链表用指针树用递归。同样的“遍历所有对象”逻辑会在代码里到处重复一旦遍历逻辑需要修改比如增加过滤条件就得在所有地方修改。难以支持复杂遍历如果遍历不是简单的从头到尾而是需要按特定顺序如Z轴深度排序、过滤特定类型如只遍历敌人单位、或者遍历多个集合的组合结果直接操作底层容器会让代码变得极其臃肿和混乱。迭代器模式就是为了解决这些问题而生的。它的核心思想是将集合的遍历行为抽象出来封装到一个独立的“迭代器”对象中。客户端代码比如我们的渲染模块不再需要知道集合内部是数组、链表还是树它只需要拿到一个统一的迭代器接口然后用同样的方式First()、Next()、IsDone()、Current()去遍历。集合的内部结构再怎么变只要它提供的迭代器接口不变客户端代码就完全不受影响。这就像你去图书馆找书。你不需要知道图书管理员是把书按杜威十进制分类法放在A区3排5架还是按主题色彩放在“科幻蓝色区”。你只需要告诉管理员“我想找刘慈欣的小说”管理员迭代器会按照图书馆内部集合的规则一本一本地拿给你。图书馆的内部管理规则数据结构变了比如从按作者排序改成按出版日期排序对你找书的过程客户端遍历逻辑没有任何影响。在C标准库STL中迭代器模式已经被广泛应用和标准化。std::vector::iterator、std::list::iterator就是最典型的例子。我们平时用的范围for循环其底层就是依赖于容器的begin()和end()返回的迭代器。我们这篇文章要做的就是抛开STL这层“语法糖”从设计模式的角度亲手实现一个迭代器理解其背后的设计哲学和实现细节让你在需要为自定义数据结构提供遍历能力时能够自己造出合适的“轮子”。2. 迭代器模式的核心结构与角色解析要自己实现迭代器模式我们首先得搞清楚它由哪些“演员”组成以及他们各自扮演什么角色。经典的迭代器模式通常包含以下几个关键角色我们用C的视角来重新定义它们2.1 抽象迭代器 (Iterator)这是整个模式的“指挥棒”定义了遍历集合所需的基本操作接口。它不关心集合具体是什么只关心“如何走”和“怎么看”。template typename T class Iterator { public: virtual ~Iterator() default; // 将游标重置到第一个元素 virtual void First() 0; // 将游标移动到下一个元素 virtual void Next() 0; // 检查是否已经遍历完所有元素 virtual bool IsDone() const 0; // 获取当前游标指向的元素 virtual T Current() 0; };为什么设计成抽象类因为不同的集合数组、链表、树其遍历方式天差地别。数组迭代器可能就是一个索引而二叉树的中序遍历迭代器则需要一个栈来辅助。我们必须为每种具体的集合提供一个具体的迭代器实现所以需要一个统一的接口来约束它们。2.2 具体迭代器 (ConcreteIterator)这是抽象迭代器的具体实现它“知道”特定集合的内部结构并据此实现遍历逻辑。它持有对具体集合的引用或指针以便访问其元素。template typename T class VectorIterator : public IteratorT { public: explicit VectorIterator(const std::vectorT vec) : m_vector(vec), m_currentIndex(0) {} void First() override { m_currentIndex 0; } void Next() override { if (!IsDone()) { m_currentIndex; } } bool IsDone() const override { return m_currentIndex m_vector.size(); } T Current() override { // 注意这里没有做边界检查因为IsDone()已经由调用者检查。 // 在实际项目中这里可以加入断言(assert)来帮助调试。 return m_vector[m_currentIndex]; } private: const std::vectorT m_vector; // 持有集合的引用 size_t m_currentIndex; // 当前遍历位置 };注意这里的一个关键点VectorIterator持有了一个const std::vectorT。这意味着这个迭代器是只读的。如果你需要修改集合元素可以持有非const的引用但这就涉及到迭代器失效等更复杂的问题我们后面会讨论。2.3 抽象集合 (Aggregate)这个角色定义了创建相应迭代器对象的接口。通常就是一个名为CreateIterator的工厂方法。template typename T class Aggregate { public: virtual ~Aggregate() default; // 工厂方法用于创建遍历本集合的迭代器 virtual std::unique_ptrIteratorT CreateIterator() const 0; // 通常集合还会有添加、删除元素等方法这里省略 };2.4 具体集合 (ConcreteAggregate)这是我们要遍历的实际数据结构。它实现了CreateIterator方法返回一个绑定到自身this的具体迭代器。template typename T class NumberContainer : public AggregateT { public: void Add(const T value) { m_data.push_back(value); } std::unique_ptrIteratorT CreateIterator() const override { // 返回一个针对本容器m_data的迭代器 return std::make_uniqueVectorIteratorT(m_data); } // ... 其他容器操作方法 private: std::vectorT m_data; // 内部使用vector存储 };这里的设计精妙之处在于NumberContainer的内部存储是std::vector但它对外只承诺自己是一个Aggregate能创建Iterator。客户端代码通过CreateIterator()拿到一个迭代器后就可以遍历了完全不知道底层用的是vector。如果明天我把m_data从vector换成list我只需要修改CreateIterator让它返回一个ListIterator所有客户端代码一行都不用改。2.5 客户端 (Client)客户端是迭代器的使用者。它通过抽象接口Aggregate和Iterator与集合交互从而与具体的集合类解耦。void PrintAllNumbers(const Aggregateint container) { // 1. 获取迭代器 auto it container.CreateIterator(); // 2. 开始遍历 for (it-First(); !it-IsDone(); it-Next()) { // 3. 访问当前元素 std::cout it-Current() ; } std::cout std::endl; }看客户端代码多干净它不关心container是NumberContainer还是别的什么TreeContainer它只调用标准的迭代器接口。这就是迭代器模式带来的最大好处将遍历算法与数据结构分离。实操心得智能指针管理迭代器生命周期上面的例子中CreateIterator返回的是std::unique_ptrIteratorT。这是一个非常重要的现代C实践。迭代器通常是在堆上动态创建的如果返回原始指针很容易导致内存泄漏。使用std::unique_ptr可以明确所有权关系——迭代器的所有权从集合转移给了客户端客户端使用完毕后unique_ptr离开作用域会自动释放内存。如果你需要共享迭代器比如多个对象轮流遍历可以考虑std::shared_ptr但这种情况比较少见。3. 从理论到实践一个完整的C迭代器实现案例光说不练假把式。我们现在来构建一个更贴近实战的例子一个简单的“待办事项列表”(ToDoList)。这个列表内部可能用std::vector存储但我们希望提供多种遍历方式正序、逆序、以及只遍历高优先级的任务。我们将看到如何用迭代器模式优雅地实现这些需求。3.1 定义数据模型和抽象接口首先定义我们的数据模型Task任务和抽象迭代器接口。// task.h #pragma once #include string #include iostream enum class Priority { Low, Medium, High }; class Task { public: Task(const std::string desc, Priority prio) : description(desc), priority(prio), completed(false) {} void MarkComplete() { completed true; } bool IsCompleted() const { return completed; } Priority GetPriority() const { return priority; } const std::string GetDescription() const { return description; } void Print() const { std::cout [ (completed ? X : ) ] Prio: static_castint(priority) - description std::endl; } private: std::string description; Priority priority; bool completed; }; // iterator.h - 抽象迭代器 #pragma once #include task.h class TaskIterator { public: virtual ~TaskIterator() default; virtual void First() 0; virtual void Next() 0; virtual bool IsDone() const 0; virtual Task Current() 0; // 返回引用允许修改任务状态如标记完成 }; // aggregate.h - 抽象集合 #pragma once #include memory #include iterator.h class TaskAggregate { public: virtual ~TaskAggregate() default; virtual std::unique_ptrTaskIterator CreateIterator() 0; virtual void AddTask(const Task task) 0; // 其他必要接口... };3.2 实现具体集合和基础迭代器接着实现我们的具体待办事项列表ToDoList和最基本的正序迭代器。// todo_list.h / todo_list.cpp #pragma once #include vector #include memory #include aggregate.h class ToDoList : public TaskAggregate { public: std::unique_ptrTaskIterator CreateIterator() override; void AddTask(const Task task) override { tasks_.push_back(task); } // 提供一个获取内部任务数量的方法可能被某些迭代器用到 size_t GetTaskCount() const { return tasks_.size(); } // 注意这里直接暴露了内部vector的引用仅供迭代器“友元”使用。 // 更好的做法是声明迭代器为友元或提供begin/end风格的访问器。 const std::vectorTask GetTasks() const { return tasks_; } std::vectorTask GetTasks() { return tasks_; } // 非const版本 private: std::vectorTask tasks_; }; // 正向迭代器 class ForwardTaskIterator : public TaskIterator { public: explicit ForwardTaskIterator(ToDoList list) : list_(list), currentIndex_(0) {} void First() override { currentIndex_ 0; } void Next() override { if (!IsDone()) { currentIndex_; } } bool IsDone() const override { return currentIndex_ list_.GetTasks().size(); } Task Current() override { // 这里假设调用者遵守协议在调用Current前已检查!IsDone() return list_.GetTasks()[currentIndex_]; } private: ToDoList list_; size_t currentIndex_; }; // ToDoList的CreateIterator实现 std::unique_ptrTaskIterator ToDoList::CreateIterator() { // 默认返回正向迭代器 return std::make_uniqueForwardTaskIterator(*this); }3.3 实现变体迭代器逆序与过滤现在展示迭代器模式威力的时刻到了。我们可以轻松创建新的迭代器提供不同的遍历视图而无需修改ToDoList本身。// reverse_iterator.h / reverse_iterator.cpp #pragma once #include iterator.h #include todo_list.h class ReverseTaskIterator : public TaskIterator { public: explicit ReverseTaskIterator(ToDoList list) : list_(list) { // 初始化时游标指向最后一个元素 First(); } void First() override { auto tasks list_.GetTasks(); currentIndex_ tasks.empty() ? 0 : tasks.size() - 1; // 如果容器为空IsDone()应该为true。这里设置currentIndex_为0但size()也为0IsDone()逻辑会处理。 } void Next() override { if (!IsDone() currentIndex_ 0) { --currentIndex_; } else { // 如果已经是第一个元素再Next就“越界”将currentIndex_置为一个无效值 currentIndex_ list_.GetTasks().size(); // 使得IsDone()返回true } } bool IsDone() const override { return currentIndex_ list_.GetTasks().size(); } Task Current() override { return list_.GetTasks()[currentIndex_]; } private: ToDoList list_; size_t currentIndex_; // 注意当容器为空时此值可能为0但IsDone()会正确处理。 }; // filtered_iterator.h / filtered_iterator.cpp #pragma once #include iterator.h #include todo_list.h class HighPriorityIterator : public TaskIterator { public: explicit HighPriorityIterator(ToDoList list) : list_(list) { // 在构造时或First()时预先找到所有高优先级任务的索引 BuildIndex(); First(); } void First() override { currentPosInIndex_ 0; } void Next() override { if (!IsDone()) { currentPosInIndex_; } } bool IsDone() const override { return currentPosInIndex_ highPriorityIndices_.size(); } Task Current() override { size_t actualIndex highPriorityIndices_[currentPosInIndex_]; return list_.GetTasks()[actualIndex]; } private: void BuildIndex() { const auto tasks list_.GetTasks(); for (size_t i 0; i tasks.size(); i) { if (tasks[i].GetPriority() Priority::High) { highPriorityIndices_.push_back(i); } } } ToDoList list_; std::vectorsize_t highPriorityIndices_; // 存储高优先级任务在原始容器中的索引 size_t currentPosInIndex_ 0; };关键点解析ReverseTaskIterator它的遍历逻辑与正向迭代器相反。First()指向最后一个元素Next()是--currentIndex_。注意处理容器为空或遍历到头部边界的情况。HighPriorityIterator这是一个过滤迭代器的典型例子。它在构造时遍历一次原始集合将所有高优先级任务的索引缓存起来。之后的First、Next、Current操作都基于这个缓存索引列表。这样做的好处是遍历过程的复杂度是O(n)预处理 O(1)每次遍历避免了每次Next()都去检查优先级的开销。这是一种“空间换时间”和“初始化开销换遍历效率”的权衡在集合不常变动但需要频繁按条件遍历的场景下非常有用。3.4 客户端代码与使用示例最后看看客户端如何以一种统一、优雅的方式使用这些不同的迭代器。// main.cpp #include iostream #include todo_list.h #include reverse_iterator.h #include filtered_iterator.h int main() { ToDoList myList; myList.AddTask(Task(Buy groceries, Priority::Medium)); myList.AddTask(Task(Fix critical bug, Priority::High)); myList.AddTask(Task(Read design patterns book, Priority::Low)); myList.AddTask(Task(Prepare presentation, Priority::High)); myList.AddTask(Task(Call mom, Priority::Medium)); std::cout Forward Iteration std::endl; auto forwardIt myList.CreateIterator(); // 默认正向 for (forwardIt-First(); !forwardIt-IsDone(); forwardIt-Next()) { forwardIt-Current().Print(); } std::cout \n Reverse Iteration std::endl; ReverseTaskIterator reverseIt(myList); // 使用反向迭代器 for (reverseIt.First(); !reverseIt.IsDone(); reverseIt.Next()) { reverseIt.Current().Print(); } std::cout \n High Priority Only Iteration std::endl; HighPriorityIterator highPrioIt(myList); // 使用过滤迭代器 for (highPrioIt.First(); !highPrioIt.IsDone(); highPrioIt.Next()) { highPrioIt.Current().Print(); } // 使用迭代器修改元素 std::cout \n Marking first high-priority task as complete std::endl; highPrioIt.First(); if (!highPrioIt.IsDone()) { highPrioIt.Current().MarkComplete(); highPrioIt.Current().Print(); } return 0; }输出结果将会是 Forward Iteration [ ] Prio: 1 - Buy groceries [ ] Prio: 2 - Fix critical bug [ ] Prio: 0 - Read design patterns book [ ] Prio: 2 - Prepare presentation [ ] Prio: 1 - Call mom Reverse Iteration [ ] Prio: 1 - Call mom [ ] Prio: 2 - Prepare presentation [ ] Prio: 0 - Read design patterns book [ ] Prio: 2 - Fix critical bug [ ] Prio: 1 - Buy groceries High Priority Only Iteration [ ] Prio: 2 - Fix critical bug [ ] Prio: 2 - Prepare presentation Marking first high-priority task as complete [X] Prio: 2 - Fix critical bug通过这个例子你可以清晰地看到解耦main函数客户端只知道TaskIterator接口和如何创建迭代器通过构造函数或工厂方法。它完全不知道ToDoList内部是用vector存的也不知道反向和过滤迭代器内部复杂的索引计算逻辑。灵活性新增一种遍历方式比如“只遍历未完成的任务”只需要新增一个UnfinishedTaskIterator类客户端代码几乎不用动。可维护性遍历逻辑被封装在各个迭代器类中。如果ToDoList的内部数据结构从vector改为list我们只需要修改ForwardTaskIterator、ReverseTaskIterator和HighPriorityIterator的内部实现以及ToDoList::CreateIterator的返回类型而所有使用TaskIterator接口的客户端代码都安然无恙。4. 深入探讨C STL迭代器与自定义迭代器的进阶话题我们上面实现的是一个经典的、教学式的迭代器模式。但在实际的C工程中我们更多是直接使用或模仿STL迭代器的风格。理解这两者之间的联系与差异能让你更好地在实战中应用这一模式。4.1 STL迭代器一种更泛化的设计STL的迭代器并不是通过一个抽象的Iterator基类和多态来实现的它采用的是概念(Concepts)和模板。它定义了几类迭代器输入、输出、前向、双向、随机访问每种类型支持一组特定的操作如*it、it、it-member、it n等。算法如std::sort、std::find通过模板参数接受迭代器类型并在编译期通过迭代器标签(iterator_category)和特性(iterator_traits)来分派最有效的实现。例如一个简单的STL风格迭代器可能长这样template typename T class MyVectorIterator { public: // 定义迭代器类别随机访问迭代器 using iterator_category std::random_access_iterator_tag; using value_type T; using difference_type std::ptrdiff_t; using pointer T*; using reference T; // 构造函数、拷贝构造、赋值等... MyVectorIterator(pointer ptr) : ptr_(ptr) {} // 必需的操作符重载 reference operator*() const { return *ptr_; } pointer operator-() const { return ptr_; } MyVectorIterator operator() { ptr_; return *this; } // 前置 MyVectorIterator operator(int) { auto tmp *this; ptr_; return tmp; } // 后置 // ... 还需要实现 --, , -, [], , !, 等操作符以满足随机访问迭代器要求 private: pointer ptr_; };为什么STL不用多态性能虚函数调用有运行时开销查虚函数表。STL追求极致的性能编译期多态模板可以完全消除这部分开销。值语义STL迭代器通常被设计为轻量级的、可拷贝的“指针式”对象适合按值传递。而多态基类的指针或智能指针是引用语义。灵活性模板可以自动推导类型并与算法无缝结合形成std::sort(vec.begin(), vec.end())这样简洁的语法。4.2 何时使用经典模式何时模仿STL使用经典迭代器模式多态的场景需要运行时动态选择迭代器类型比如你的集合类可以根据配置决定返回一个正向迭代器还是一个过滤迭代器这个决定是在运行时做出的。迭代器类型构成一个复杂的层次结构比如有一个基础迭代器然后派生出过滤迭代器、转换迭代器等并且客户端代码需要通过基类指针来统一操作它们。系统已经严重依赖多态和继承引入模板会带来较大的重构成本。模仿STL风格模板的场景性能是关键考量你希望遍历操作能被编译器充分内联优化。你希望你的自定义容器和算法能与STL算法库如algorithm无缝协作。迭代器类型在编译期即可确定不需要运行时多态。你正在设计一个通用的、库级别的组件。实操建议对于大多数应用层业务代码如果你只是需要为某个特定的复杂数据结构提供一种清晰的遍历抽象经典的多态迭代器模式通常更简单、更直观也足够好用。它的代码结构清晰符合面向对象设计原则易于理解和维护。除非你有明确的性能瓶颈或需要与STL深度集成否则不必强求STL风格。4.3 迭代器失效问题一个必须面对的坑这是一个在修改集合时使用迭代器必须警惕的经典问题。迭代器失效指的是在通过迭代器遍历集合的过程中如果集合的结构发生了改变如添加、删除了元素可能会导致之前获取的迭代器变得无效野指针、悬空引用继续使用它将导致未定义行为崩溃或数据错误。不同容器的失效规则不同std::vector在中间插入/删除元素所有在插入/删除点之后的迭代器、指针、引用都会失效。在末尾push_back只有end()迭代器失效如果引起重新分配reallocation所有迭代器、指针、引用都失效。std::list、std::map、std::set插入操作不会使任何迭代器失效除了被删除元素的迭代器。删除操作只会使指向被删除元素的迭代器失效其他迭代器仍然有效。在我们的ToDoList例子中如何规避我们的ToDoList内部使用std::vector。如果我们在使用ForwardTaskIterator遍历列表时通过list.AddTask()添加了一个新任务可能会导致vector扩容重新分配内存从而使迭代器内部持有的索引currentIndex_指向了错误的内存地址。解决方案文档约束最简单也最常用的方法。在TaskIterator的接口文档中明确声明“在迭代器生命周期内不得修改其关联的ToDoList的结构即增删任务。” 这是STL容器迭代器约定俗成的规则。拷贝集合如果遍历过程中可能修改且集合不大可以先拷贝一份数据然后遍历拷贝。使用索引而非引用/指针我们的ForwardTaskIterator存储的是索引currentIndex_而不是指向vector元素的指针。即使vector重新分配索引值仍然是有效的只要不越界。但是如果在遍历时删除了当前索引之前的元素索引就会错位。所以索引法只能部分缓解问题不能完全解决。设计更鲁棒的迭代器可以为迭代器增加一个版本号机制。ToDoList内部维护一个version_计数器每次结构修改增删就递增。迭代器在创建时记录当前的version_每次操作前检查是否与集合的当前版本一致不一致则抛出异常或断言失败。// 版本号机制的简单示意 class ToDoList { // ... void AddTask(const Task task) { tasks_.push_back(task); version_; // 结构改变版本号增加 } size_t GetVersion() const { return version_; } private: std::vectorTask tasks_; size_t version_ 0; }; class ForwardTaskIterator { public: explicit ForwardTaskIterator(ToDoList list) : list_(list), savedVersion_(list.GetVersion()), currentIndex_(0) {} void Next() { CheckVersion(); // 每次操作前检查 // ... Next逻辑 } // ... 其他方法也要检查 private: void CheckVersion() const { if (savedVersion_ ! list_.GetVersion()) { throw std::runtime_error(Iterator invalidated: container modified!); } } ToDoList list_; size_t savedVersion_; size_t currentIndex_; };避坑指南迭代器失效是C新手最常见的崩溃原因之一。我的经验法则是“遍历时只读不写若要写用索引。”如果遍历过程中必须修改集合结构比如删除满足条件的元素一个安全的方法是先收集要删除的元素索引或键遍历结束后再统一删除。或者对于像std::list和关联容器可以使用it container.erase(it)这种返回下一个有效迭代器的方式。5. 迭代器模式的变体、应用场景与局限性5.1 常见变体内部迭代器 vs 外部迭代器外部迭代器我们实现的这种迭代器对象独立于集合客户端控制迭代过程调用Next。更灵活可以同时进行多个遍历。内部迭代器集合提供一个ForEach方法接受一个函数对象回调集合内部控制遍历过程对每个元素调用该函数。更简洁但控制权在集合手中。C11的范围for循环和std::for_each算法结合lambda表达式在某种程度上模拟了内部迭代器。空迭代器 (Null Iterator)对于没有子元素的集合如叶子节点可以返回一个IsDone()始终为true的迭代器。这在组合模式Composite中遍历树形结构时非常有用可以统一叶子节点和复合节点的接口。过滤迭代器与转换迭代器我们实现的HighPriorityIterator就是过滤迭代器。你还可以实现转换迭代器Transform Iterator它在Current()时不是返回原始元素而是返回一个转换后的值例如返回任务描述的长度而不是任务对象。C Boost库中的transform_iterator就是这种思想。5.2 典型应用场景复杂集合的多种遍历方式如前所述这是最直接的场景。你的集合内部结构复杂如组合了多种数据结构需要提供不同顺序正序、逆序、按优先级、按深度等的遍历。隐藏集合的内部表示当你不想暴露底层数据结构可能是为了安全也可能是为了未来可以自由更换实现时提供一个迭代器接口是完美的抽象层。并行遍历多个线程可以持有同一个集合的不同迭代器并发地进行遍历前提是遍历操作是只读的或者集合是线程安全的。惰性求值/流处理迭代器可以用于实现类似“流”(Stream)的概念。例如从一个巨大的文件中逐行读取数据迭代器可以在每次Next()时读取下一行而不是一次性加载整个文件到内存。5.3 局限性增加复杂性对于简单的、只有一种遍历方式的集合如一个普通的数组使用迭代器模式无疑是杀鸡用牛刀直接使用索引或范围for循环更简单。性能开销多态迭代器有虚函数调用开销即使是STL风格的迭代器如果设计不当如过滤迭代器需要缓存索引也会有额外的内存和初始化开销。只能单向移动我们实现的简单迭代器通常只支持向前移动(Next)。要实现双向支持Previous或随机访问支持it 5接口和实现都会更复杂。破坏封装的风险迭代器的Current()方法通常返回集合内部元素的引用或指针。如果客户端通过这个引用修改了元素可能会破坏集合的不变性invariant。因此有时需要提供const_iterator只读迭代器。6. 总结与最佳实践建议迭代器模式是一个强大的工具它通过将遍历职责分离极大地提高了代码的灵活性和可维护性。在C中我们有两种主要实现路径经典的面向对象多态实现以及STL风格的基于模板和概念的实现。给C开发者的几点实践建议优先使用STL迭代器对于标准容器或模仿STL风格的自定义容器直接使用或提供STL兼容的迭代器。这能让你的容器与庞大的STL算法生态无缝对接是性价比最高的选择。多态迭代器用于解耦当你需要为复杂的业务对象集合提供多种动态可选的遍历视图并且客户端代码需要通过统一接口操作时经典的多态迭代器模式非常合适。明确迭代器的有效性范围在你的设计文档和接口注释中必须清晰说明迭代器在什么情况下会失效。强制约定“遍历期间不修改集合”是最简单有效的规则。考虑提供const迭代器如果你的集合有状态不变性要求或者希望支持只读遍历应该提供const_iterator类型其Current()返回const T。利用现代C特性使用std::unique_ptr管理迭代器资源使用final防止迭代器被意外继承使用override明确重写虚函数让代码更安全、更清晰。不要过度设计如果一个for循环就能清晰表达意图就不要引入迭代器模式。模式是解决复杂问题的工具而不是增加代码复杂度的借口。最后迭代器模式的思想其实早已渗透到现代编程的方方面面。C#的IEnumerator、Java的Iterator、Python的生成器(yield)甚至是函数式编程中的map、filter、reduce操作其核心思想都与迭代器模式一脉相承——将数据的生产与消费分离提供一种统一的访问序列元素的方式。理解并掌握好这个模式对你写出干净、解耦、易于扩展的C代码大有裨益。下次当你面对一个需要多种方式遍历的复杂集合时不妨想一想是不是该请出迭代器这个“遍历指挥官”了