16、迭代器模式:容器遍历、统一接口、链表迭代器实现

迭代器模式,说白了就是给容器遍历定个规矩。

我刚开始写C代码那会儿,遍历链表、数组、树,每个容器都得写一套不同的循环逻辑。后来维护一个老项目,光遍历方式就有七八种,改个需求得翻遍所有遍历代码。嗯,那时候我就意识到——遍历这件事,必须统一。

什么是迭代器模式?

迭代器模式的核心思想很简单:把遍历行为从容器中抽离出来。你不需要关心底层是数组还是链表,只需要调用统一的 next()、hasNext() 接口就能拿到下一个元素。

说白了,就是给容器配个“导游”。你跟着导游走就行,不用管路怎么修、车怎么开。

核心三要素:

  • 容器(Aggregate):负责存储数据,提供创建迭代器的接口
  • 迭代器(Iterator):封装遍历逻辑,提供统一的访问接口
  • 客户端(Client):通过迭代器访问容器元素,不直接操作容器内部

为什么C语言也需要迭代器?

你可能会问:C语言又不是面向对象,搞什么设计模式?

我在项目中遇到过好几次这样的场景:一个模块用链表存数据,另一个模块用动态数组。后来业务变了,要把链表改成数组。结果呢?所有遍历代码全得重写。改一处还好,改几十处就真要命了。

迭代器模式解决的就是这个问题——遍历逻辑与容器解耦。你换容器,迭代器接口不变,客户端代码一行都不用改。

场景 没有迭代器 有迭代器
链表遍历 while(p) { p = p->next; } while(it.hasNext(&it)) { it.next(&it); }
数组遍历 for(i=0; i while(it.hasNext(&it)) { it.next(&it); }
树遍历 递归/栈模拟 while(it.hasNext(&it)) { it.next(&it); }

看到没?接口统一了,代码可维护性直接上一个台阶。

链表迭代器的实现

咱们直接上代码。我习惯用结构体+函数指针的方式来实现C语言的迭代器模式。这样既保留了C的简洁,又有了面向对象的味道。

// 链表节点
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 链表容器
typedef struct {
    Node* head;
    int size;
} LinkedList;

// 迭代器结构体
typedef struct {
    Node* current;      // 当前节点
    int index;          // 当前索引(可选)
    int (*hasNext)(struct LinkedListIterator* self);
    int (*next)(struct LinkedListIterator* self);
} LinkedListIterator;

// 判断是否还有下一个元素
int hasNext(LinkedListIterator* self) {
    return self->current != NULL;
}

// 获取下一个元素并移动指针
int next(LinkedListIterator* self) {
    if (self->current == NULL) {
        return -1;  // 或者用错误码
    }
    int data = self->current->data;
    self->current = self->current->next;
    self->index++;
    return data;
}

// 创建迭代器
LinkedListIterator createIterator(LinkedList* list) {
    LinkedListIterator it;
    it.current = list->head;
    it.index = 0;
    it.hasNext = hasNext;
    it.next = next;
    return it;
}

我的习惯:迭代器里放函数指针,这样每个迭代器实例可以有自己的行为。比如你可以实现一个“过滤迭代器”,只返回偶数元素,接口完全不变。

使用示例

int main() {
    LinkedList list;
    // 初始化链表并添加数据...
    
    LinkedListIterator it = createIterator(&list);
    
    while (it.hasNext(&it)) {
        int value = it.next(&it);
        printf("%d ", value);
    }
    
    return 0;
}

你看,客户端代码完全不知道底层是链表还是数组。它只关心 hasNext 和 next 两个接口。这就是迭代器模式的魅力。

迭代器模式的知识体系

下面这张图是我自己总结的迭代器模式核心逻辑,你看一眼就能明白整个架构:

迭代器模式核心架构 客户端 (Client) 迭代器接口 (Iterator) hasNext() | next() | reset() 具体迭代器 (LinkedListIterator) 持有 current 指针,实现遍历逻辑 容器 LinkedList 客户端通过迭代器接口遍历容器,不直接操作容器内部结构

避坑指南

我曾经踩过的坑:

  • 迭代器失效问题:遍历过程中如果修改了容器(比如删除当前节点),迭代器指针就悬空了。我建议遍历时只读不改,真要改就标记后统一处理。
  • 多线程遍历:迭代器不是线程安全的。如果多个线程同时遍历同一个容器,加锁是必须的。我习惯在迭代器里加个“版本号”,容器修改时版本号递增,迭代器检测到版本变化就报错。
  • 内存管理:迭代器本身不拥有数据,别在迭代器里 free 节点。谁创建谁释放,这个原则不能乱。

什么时候用迭代器模式?

我个人总结了几条经验:

  • 容器类型可能变化:今天用链表,明天可能改数组。迭代器帮你隔离变化。
  • 需要多种遍历方式:正序遍历、逆序遍历、过滤遍历。每种遍历写一个迭代器,互不干扰。
  • 遍历逻辑复杂:比如树的层序遍历、图的深度优先遍历。把这些逻辑封装到迭代器里,客户端调用就一行 while 循环。

一个小技巧:如果你觉得写迭代器太麻烦,可以先写一个简单的 for 循环。等发现这段遍历代码被复制粘贴了三次以上,再重构出迭代器。别过度设计,但也不要等到代码烂成一锅粥才动手。

迭代器模式,说白了就是给遍历这件事定个标准。你想想看,如果每个容器都有一套自己的遍历方式,那代码得多乱?统一接口之后,不管是链表、数组还是树,客户端代码都长一个样。维护起来轻松多了。

嗯,这一章就到这里。记住:遍历是小事,统一是大事

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