第十四章:迭代器模式——在C语言中实现迭代器接口

迭代器模式,说白了就是给容器装上一个统一的“遥控器”。

你想想看,我们平时写嵌入式代码,经常要遍历数组、链表、环形缓冲区。每个容器的遍历方式都不一样——数组用下标,链表用指针跳转,环形缓冲区还得算模。每次换个容器,遍历代码就得重写。这合理吗?

我个人习惯是:只要容器数量超过两个,就上迭代器。别等到项目后期再重构,那时候改起来就痛苦了。

14.1 为什么需要迭代器?

先看一个真实场景。我在做智能家居网关项目时,需要同时管理传感器数据队列、设备列表和事件日志。这三个容器类型完全不同:

  • 传感器数据:环形缓冲区
  • 设备列表:双向链表
  • 事件日志:动态数组

如果没有迭代器,每个遍历操作都要写三套代码。更麻烦的是,如果后续要增加一种容器(比如哈希表),所有遍历代码都得改一遍。

迭代器要解决的核心问题就两个:

  1. 统一遍历接口——不管底层是什么容器,遍历方式都一样
  2. 解耦算法与容器——算法只依赖迭代器,不依赖具体容器

核心思想:将“如何访问元素”与“元素本身”分离。迭代器负责访问逻辑,容器只负责存储逻辑。

14.2 迭代器接口设计

在C语言中实现迭代器,我建议用结构体+函数指针的方式。这样既灵活,又不会像C++模板那样增加代码体积。

先定义迭代器的操作接口:

// iterator.h
#ifndef ITERATOR_H
#define ITERATOR_H

#include <stdbool.h>

// 前向声明
struct iterator;

// 迭代器操作函数指针类型
typedef void* (*iterator_next_fn)(struct iterator* it);
typedef bool  (*iterator_has_next_fn)(struct iterator* it);
typedef void  (*iterator_reset_fn)(struct iterator* it);
typedef void  (*iterator_destroy_fn)(struct iterator* it);

// 迭代器结构体
typedef struct iterator {
    void* context;              // 迭代器内部状态(指向具体容器的遍历上下文)
    iterator_next_fn next;      // 获取下一个元素
    iterator_has_next_fn has_next;  // 是否还有下一个
    iterator_reset_fn reset;    // 重置到起始位置
    iterator_destroy_fn destroy; // 销毁迭代器
} iterator_t;

#endif // ITERATOR_H

嗯,这里要注意:context字段是关键。它指向一个“遍历上下文”结构体,里面保存了当前遍历位置等信息。不同的容器,这个上下文结构体完全不同。

14.3 为数组实现迭代器

先来个最简单的——数组迭代器。我在项目中经常用这个来遍历配置表。

// array_iterator.c
#include "iterator.h"
#include <stdlib.h>

// 数组迭代器的上下文
typedef struct {
    int* data;      // 数组指针
    int   size;     // 数组长度
    int   index;    // 当前索引
} array_context_t;

// 获取下一个元素
static void* array_next(iterator_t* it) {
    array_context_t* ctx = (array_context_t*)it->context;
    if (ctx->index >= ctx->size) return NULL;
    return &ctx->data[ctx->index++];
}

// 是否还有下一个
static bool array_has_next(iterator_t* it) {
    array_context_t* ctx = (array_context_t*)it->context;
    return ctx->index < ctx->size;
}

// 重置迭代器
static void array_reset(iterator_t* it) {
    array_context_t* ctx = (array_context_t*)it->context;
    ctx->index = 0;
}

// 销毁迭代器
static void array_destroy(iterator_t* it) {
    if (it->context) free(it->context);
    free(it);
}

// 创建数组迭代器
iterator_t* array_iterator_create(int* data, int size) {
    iterator_t* it = (iterator_t*)malloc(sizeof(iterator_t));
    if (!it) return NULL;

    array_context_t* ctx = (array_context_t*)malloc(sizeof(array_context_t));
    if (!ctx) {
        free(it);
        return NULL;
    }

    ctx->data = data;
    ctx->size = size;
    ctx->index = 0;

    it->context = ctx;
    it->next = array_next;
    it->has_next = array_has_next;
    it->reset = array_reset;
    it->destroy = array_destroy;

    return it;
}

个人经验:迭代器的next函数返回void*,这样任何类型的数组都能用。调用方拿到指针后自己强转回去。虽然有点“不安全”,但C语言嘛,灵活性和性能优先。

14.4 为链表实现迭代器

链表迭代器稍微复杂一点。我记得有一次调试一个设备管理模块,链表遍历总是漏掉最后一个节点,查了半天发现是has_next判断条件写错了。

// list_iterator.c
#include "iterator.h"
#include <stdlib.h>

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

// 链表迭代器上下文
typedef struct {
    list_node_t* head;      // 链表头
    list_node_t* current;   // 当前节点
} list_context_t;

// 获取下一个元素
static void* list_next(iterator_t* it) {
    list_context_t* ctx = (list_context_t*)it->context;
    if (!ctx->current) return NULL;

    void* data = &ctx->current->data;
    ctx->current = ctx->current->next;
    return data;
}

// 是否还有下一个
static bool list_has_next(iterator_t* it) {
    list_context_t* ctx = (list_context_t*)it->context;
    return ctx->current != NULL;
}

// 重置迭代器
static void list_reset(iterator_t* it) {
    list_context_t* ctx = (list_context_t*)it->context;
    ctx->current = ctx->head;
}

// 销毁迭代器
static void list_destroy(iterator_t* it) {
    if (it->context) free(it->context);
    free(it);
}

// 创建链表迭代器
iterator_t* list_iterator_create(list_node_t* head) {
    iterator_t* it = (iterator_t*)malloc(sizeof(iterator_t));
    if (!it) return NULL;

    list_context_t* ctx = (list_context_t*)malloc(sizeof(list_context_t));
    if (!ctx) {
        free(it);
        return NULL;
    }

    ctx->head = head;
    ctx->current = head;

    it->context = ctx;
    it->next = list_next;
    it->has_next = list_has_next;
    it->reset = list_reset;
    it->destroy = list_destroy;

    return it;
}

14.5 统一遍历——算法与容器解耦

现在,不管底层是数组还是链表,遍历代码完全一样:

// 统一的遍历函数——完全不关心底层容器
void print_all(iterator_t* it) {
    it->reset(it);  // 从头开始

    while (it->has_next(it)) {
        int* value = (int*)it->next(it);
        printf("%d ", *value);
    }
    printf("\n");
}

// 使用示例
void demo() {
    // 遍历数组
    int arr[] = {1, 2, 3, 4, 5};
    iterator_t* arr_it = array_iterator_create(arr, 5);
    print_all(arr_it);
    arr_it->destroy(arr_it);

    // 遍历链表
    list_node_t n1 = {10, NULL};
    list_node_t n2 = {20, NULL};
    list_node_t n3 = {30, NULL};
    n1.next = &n2;
    n2.next = &n3;

    iterator_t* list_it = list_iterator_create(&n1);
    print_all(list_it);
    list_it->destroy(list_it);
}

你看,print_all函数完全不知道底层是数组还是链表。它只依赖迭代器接口。这就是解耦——算法只跟接口打交道,不跟具体容器打交道。

14.6 迭代器模式的核心结构

下面这张图展示了迭代器模式的整体架构:

迭代器模式架构图 客户端代码 迭代器接口 next() has_next() reset() 数组迭代器 context: {data, size, index} 实现 next/has_next/reset 链表迭代器 context: {head, current} 实现 next/has_next/reset 数组容器 链表容器 客户端通过统一接口操作不同容器,算法与容器完全解耦

14.7 避坑指南与最佳实践

我在多个项目中用过迭代器模式,踩过不少坑。这里分享几个关键点:

我曾经犯过的错:

  • 迭代器失效问题:遍历过程中如果容器被修改(比如删除了当前节点),迭代器就废了。解决方案:要么禁止遍历时修改,要么让迭代器自己管理容器锁。
  • 内存泄漏:迭代器内部malloc了context,调用方用完必须调用destroy。我建议在文档里明确标注“谁创建谁销毁”。
  • 多线程安全:如果迭代器被多个任务同时使用,context里的状态会乱掉。要么加锁,要么每个任务创建自己的迭代器实例。

我的建议:

  • 迭代器接口尽量精简——只保留next和has_next,reset和destroy按需提供
  • 对于嵌入式系统,考虑用静态分配代替malloc,避免堆碎片
  • 如果容器元素类型固定,可以用宏模板生成类型安全的迭代器,避免void*的强制转换

14.8 迭代器模式的适用场景

场景 推荐使用 原因
多种容器需要统一遍历 ✅ 强烈推荐 解耦算法与容器,代码复用率高
容器类型可能变化 ✅ 推荐 切换容器不影响遍历代码
只有一种容器且固定 ⚠️ 按需使用 引入迭代器会增加代码复杂度
性能极端敏感(如中断处理) ❌ 不推荐 函数指针调用有额外开销

说白了,迭代器模式不是银弹。如果你的项目里只有一种容器,而且永远不会换,那直接写遍历代码更简单。但如果你像我一样,经常要跟多种容器打交道——嗯,迭代器能省下你大量时间。

我个人习惯是:在模块接口层使用迭代器,在模块内部实现层直接用原生遍历。这样既保持了接口的统一性,又避免了内部性能损失。

最后说一句:迭代器模式的核心价值不是“遍历”,而是“解耦”。你想想看,当你的算法不再依赖具体容器时,代码的灵活性和可维护性会提升一个档次。


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