设计模式(C++)详解——迭代器模式(4)
<摘要>
迭代器模式是设计模式中“行为型模式”的核心成员,它像一位“智能仓库管理员”——让我们无需知道仓库(聚合对象,如数组、链表、容器)内部如何存放货物(元素),只需通过管理员(迭代器)就能按顺序取出所有货物。其核心价值在于解耦“聚合对象的存储逻辑”与“元素的遍历逻辑”,不仅让代码更灵活(同一遍历方式适配不同容器),还让聚合对象的内部结构更安全(无需暴露细节)。
在C++世界里,迭代器模式更是“顶流”——STL(标准模板库)的“容器-算法-迭代器”三大支柱中,迭代器是连接容器与算法的桥梁:vector、list等容器通过begin()/end()提供迭代器,sort、for_each等算法通过迭代器操作容器元素,实现了“算法与容器无关”的奇迹。本文将从“仓库提货”的小故事切入,带大家吃透迭代器模式的起源、设计、实现与应用,甚至亲手写一个书架迭代器,感受它如何解决传统遍历的“痛点”。
<解析>
一、从“仓库提货”说起:迭代器模式的诞生背景
1.1 没有迭代器时,我们是怎么“提货”的?
假设你是一家书店老板,有两个“仓库”存书:
- 仓库A(数组):书按顺序堆在一排架子上,每个架子有编号(下标);
- 仓库B(链表):书用绳子串起来,每本书都知道下一本在哪(指针)。
某天你让员工去提货,员工得这么做:
// 提仓库A(数组)的书:必须知道“下标”
void getBooksFromArray(Book arr[], int size) {for (int i = 0; i < size; i++) { // 依赖数组的“下标特性”std::cout << arr[i].getName() << std::endl;}
}// 提仓库B(链表)的书:必须知道“节点结构”
void getBooksFromList(Node* head) {Node* current = head;while (current != nullptr) { // 依赖链表的“指针特性”std::cout << current->book.getName() << std::endl;current = current->next;}
}
问题来了:
- 员工必须熟记两个仓库的“内部结构”(数组要下标、链表要指针),一旦仓库换成“栈”或“树”,员工就得重新学新方法;
- 若仓库升级(比如数组改成动态数组),所有提货代码都要改(比如size变成动态获取);
- 仓库的内部细节完全暴露(员工知道数组下标、链表节点),安全性差。
这就是迭代器模式出现前的“遍历困境”——聚合对象(仓库)与遍历逻辑(提货方式)强耦合。
1.2 迭代器:给所有仓库配“统一管理员”
为了解决这个问题,“迭代器模式”应运而生:我们给每个仓库配一位“管理员”(迭代器),管理员只对外提供两个简单接口:
- “还有下一本书吗?”(hasNext());
- “把下一本书给我”(next())。
员工提货时,只需问管理员这两个问题,完全不用管仓库里书是堆的、串的还是叠的。哪怕仓库换成火箭筒仓库,只要管理员不变,员工提货方式都不变!
这就是迭代器模式的核心思想:将遍历逻辑从聚合对象中抽离,用统一接口封装遍历行为。
二、迭代器模式的核心概念:一张图看懂所有角色
2.1 核心角色(UML类图)
我们用Mermaid画一张“书架迭代器”的UML类图,清晰展示迭代器模式的5个核心角色(按规范检查3轮:全角括号、括号内双引号、语法合规):
classDiagramdirection TB% 1. 抽象聚合接口:定义创建迭代器的方法class "Aggregate(抽象聚合)" {+ createIterator():Iterator* "创建迭代器"+ ~Aggregate() "虚析构,确保子类释放"}% 2. 抽象迭代器接口:定义遍历方法class "Iterator(抽象迭代器)" {+ hasNext():bool "判断是否有下一个元素"+ next():Book* "获取下一个元素"+ ~Iterator() "虚析构,确保子类释放"}% 3. 具体聚合:实际的仓库(如书架)class "BookShelf(具体聚合-书架)" {- books:std::vector~Book~ "存储图书的容器"+ addBook(book:Book):void "添加图书"+ getBookAt(index:int):Book "获取指定位置图书"+ getLength():int "获取图书总数"+ createIterator():Iterator* "实现接口,创建书架迭代器"}% 4. 具体迭代器:仓库的管理员(书架管理员)class "BookShelfIterator(具体迭代器)" {- bookShelf:const BookShelf* "关联的书架"- index:int "当前遍历索引"+ BookShelfIterator(shelf:const BookShelf*) "绑定书架,初始化索引"+ hasNext():bool "判断是否有下一本书"+ next():Book* "获取下一本书"}% 5. 元素:仓库里的货物(图书)class "Book(元素-图书)" {- name:std::string "图书名称"+ getName():std::string "获取图书名称"}% 角色关系:继承、关联"Aggregate(抽象聚合)" <|-- "BookShelf(具体聚合-书架)" : 继承(实现createIterator)"Iterator(抽象迭代器)" <|-- "BookShelfIterator(具体迭代器)" : 继承(实现遍历方法)"BookShelf(具体聚合-书架)" ..> "BookShelfIterator(具体迭代器)" : 关联(创建迭代器)"BookShelfIterator(具体迭代器)" ..> "Book(元素-图书)" : 关联(获取图书)
2.2 关键术语解读(通俗版)
术语 | 通俗解释 |
---|---|
聚合对象(Aggregate) | “仓库”:存储多个元素的容器(如数组、链表、vector、书架),负责管理元素的存储。 |
迭代器(Iterator) | “仓库管理员”:负责遍历聚合对象的元素,对外隐藏仓库的内部结构。 |
hasNext() | 管理员的“灵魂拷问”:“后面还有货吗?”,避免提货时“摸空”(越界访问)。 |
next() | 管理员的“动作”:“把下一个货给你”,同时自己往前走一步(索引自增)。 |
const_iterator | “只读管理员”:只能看货、不能改货(比如员工只能提货、不能在仓库里涂改书名),避免误操作。 |
三、迭代器模式的设计意图:为什么要这么设计?
3.1 核心目标:解耦!解耦!解耦!
迭代器模式的终极目标是解耦“聚合对象”和“遍历逻辑”,具体体现在三个方面:
- 聚合对象无需关心遍历:书架(BookShelf)只需要管“存书”(addBook),不用管“怎么拿书”——遍历逻辑全交给BookShelfIterator;
- 遍历逻辑可复用:如果有另一个“杂志架”(MagazineShelf),只要它的迭代器也实现Iterator接口,员工用同样的“问管理员”方式就能提货;
- 聚合对象内部结构安全:员工看不到书架用vector还是list存书(不用知道getBookAt或index),只能通过迭代器拿书,避免内部细节暴露。
3.2 设计权衡:灵活与复杂的平衡
没有完美的设计,迭代器模式也需要权衡:
设计优势 | 潜在挑战 | 解决方案 |
---|---|---|
遍历接口统一(适配所有容器) | 迭代器失效问题(如vector扩容后,原迭代器指向无效内存) | 1. 遍历中避免修改容器;2. 使用“安全迭代器”(如Java的CopyOnWriteArrayList);3. C++中注意容器操作后的迭代器有效性。 |
聚合对象内部隐藏 | 实现复杂度增加(需写抽象接口+具体类) | 1. 复用现有库(如C++ STL已实现所有迭代器);2. 简单场景可用“内部类”简化迭代器实现。 |
支持多种遍历方式(如正序、逆序) | 多迭代器共存时的状态管理(如一个书架同时有正序和逆序管理员) | 每个迭代器独立维护索引,互不干扰(如BookShelfIterator和ReverseBookShelfIterator)。 |
四、实例实战:亲手实现迭代器模式
我们分两个案例讲解:自定义书架迭代器(理解原理)和C++ STL迭代器(实际应用),每个案例都带完整代码、注释和编译运行指南。
4.1 案例1:自定义书架迭代器(从0到1实现)
4.1.1 完整代码(带详细注释)
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> // 用于抛出越界异常/*** @brief 图书类(迭代器要遍历的元素)* * 封装图书的核心属性(名称),提供只读访问方法,确保图书信息不被随意修改。* 角色:迭代器模式中的“元素(Element)”。* * 成员变量说明:* - name: 图书名称,私有成员,仅通过getName()访问* * 成员函数说明:* - Book(const std::string& bookName): 构造函数,初始化图书名称* - getName() const: 获取图书名称,返回字符串,const修饰确保不修改对象*/
class Book {
private:std::string name; // 图书名称,私有变量,隐藏实现细节
public:// 构造函数:创建图书时必须指定名称Book(const std::string& bookName) : name(bookName) {}// 获取图书名称:只读接口,外部无法修改namestd::string getName() const {return name;}
};/*** @brief 抽象聚合接口(Aggregate)* * 定义所有聚合对象(如书架、杂志架)必须实现的接口,核心是创建迭代器。* 作用:统一聚合对象的“迭代器创建入口”,让客户端无需关心具体聚合类型。* * 成员函数说明:* - createIterator() const: 纯虚函数,返回迭代器指针,子类必须实现* - ~Aggregate(): 虚析构函数,确保子类对象销毁时,父类指针能正确调用子类析构*/
class Aggregate {
public:virtual ~Aggregate() {} // 虚析构:避免内存泄漏的关键!virtual class Iterator* createIterator() const = 0; // 纯虚函数,创建迭代器
};/*** @brief 抽象迭代器接口(Iterator)* * 定义所有迭代器必须实现的遍历接口,客户端通过这两个方法遍历元素,* 无需知道聚合对象的内部结构。* * 成员函数说明:* - hasNext() const: 判断是否还有下一个元素,返回true/false* - next(): 获取下一个元素,返回Book指针;若没有下一个元素,抛出异常* - ~Iterator(): 虚析构函数,确保子类迭代器能正确销毁*/
class Iterator {
public:virtual ~Iterator() {} // 虚析构:避免迭代器对象内存泄漏virtual bool hasNext() const = 0; // 纯虚函数:是否有下一个元素virtual Book* next() = 0; // 纯虚函数:获取下一个元素
};/*** @brief 具体聚合:书架(BookShelf)* * 实际的“仓库”,用vector存储图书,实现Aggregate接口创建书架迭代器。* 角色:迭代器模式中的“具体聚合(ConcreteAggregate)”。* * 成员变量说明:* - books: 用vector存储图书,私有变量,外部无法直接访问* * 成员函数说明:* - addBook(const Book& book): 向书架添加图书,将book存入vector* - getBookAt(int index) const: 按索引获取图书,仅提供给迭代器使用(内部接口)* - getLength() const: 获取书架上图书总数,仅提供给迭代器使用(内部接口)* - createIterator() const: 实现Aggregate接口,创建BookShelfIterator对象*/
class BookShelf : public Aggregate {
private:std::vector<Book> books; // 存储图书的容器,私有,隐藏内部结构
public:// 向书架添加图书:外部唯一添加图书的接口void addBook(const Book& book) {books.push_back(book);}// 按索引获取图书:仅给迭代器调用,外部无法直接访问(保护内部结构)Book getBookAt(int index) const {// 边界检查:避免索引越界if (index < 0 || index >= static_cast<int>(books.size())) {throw std::out_of_range("BookShelf index out of range");}return books[index];}// 获取图书总数:给迭代器判断是否遍历结束int getLength() const {return static_cast<int>(books.size());}// 创建书架迭代器:实现抽象聚合的接口Iterator* createIterator() const override {// 将当前书架对象(this)传给迭代器,让迭代器能访问书架的图书return new BookShelfIterator(this);}
};/*** @brief 具体迭代器:书架迭代器(BookShelfIterator)* * 书架的“管理员”,负责遍历书架上的图书,实现Iterator接口。* 角色:迭代器模式中的“具体迭代器(ConcreteIterator)”。* * 成员变量说明:* - bookShelf: 指向关联书架的指针,用于获取图书信息* - index: 当前遍历的索引,初始为0(从第一本开始)* * 成员函数说明:* - BookShelfIterator(const BookShelf* shelf): 构造函数,绑定书架并初始化索引* - hasNext() const: 判断当前索引是否小于图书总数(是否有下一本书)* - next(): 获取当前索引的图书,然后索引自增;若没有下一本书,抛出异常*/
class BookShelfIterator : public Iterator {
private:const BookShelf* bookShelf; // 关联的书架,const确保不修改书架int index; // 当前遍历的索引,初始为0
public:// 构造函数:绑定书架,初始化索引为0(从第一本开始遍历)BookShelfIterator(const BookShelf* shelf) : bookShelf(shelf), index(0) {}// 判断是否有下一本书:索引 < 图书总数 → 有下一本bool hasNext() const override {return index < bookShelf->getLength();}// 获取下一本书:先检查是否有下一本,再返回图书并自增索引Book* next() override {if (!hasNext()) {// 没有下一本书时抛出异常,避免返回无效指针throw std::runtime_error("No more books in BookShelf");}// 动态创建Book对象(避免返回局部对象引用导致悬空)Book* currentBook = new Book(bookShelf->getBookAt(index));index++; // 索引自增,下次获取下一本return currentBook;}
};/*** @brief 主函数:演示书架迭代器的使用流程* * 客户端使用迭代器的核心逻辑:* 1. 创建聚合对象(书架)→ 2. 向聚合对象添加元素(图书)→ 3. 获取迭代器(管理员)→ 4. 用迭代器遍历元素 → 5. 释放资源* * 运行流程说明:* - 先创建书架并添加4本经典书籍* - 获取书架的迭代器,通过hasNext()和next()遍历所有图书* - 遍历后释放迭代器和动态创建的Book对象,避免内存泄漏*/
int main() {try {// 1. 创建书架(聚合对象)BookShelf myBookShelf;// 2. 向书架添加图书(元素)myBookShelf.addBook(Book("《C++ Primer 第6版》"));myBookShelf.addBook(Book("《算法导论 第3版》"));myBookShelf.addBook(Book("《设计模式:可复用面向对象软件的基础》"));myBookShelf.addBook(Book("《深入理解计算机系统 第3版》"));// 3. 获取迭代器(管理员):客户端无需知道是BookShelfIterator,只需用Iterator接口Iterator* shelfIterator = myBookShelf.createIterator();// 4. 遍历书架:客户端只调用hasNext()和next(),完全不关心书架用vector存储std::cout << "我的书架上有这些书:\n";while (shelfIterator->hasNext()) {Book* currentBook = shelfIterator->next();std::cout << "→ " << currentBook->getName() << "\n";delete currentBook; // 释放动态创建的Book对象,避免内存泄漏}// 5. 释放迭代器:避免迭代器对象内存泄漏delete shelfIterator;} catch (const std::exception& e) {// 捕获异常并打印错误信息,增强程序健壮性std::cerr << "程序出错:" << e.what() << std::endl;return 1; // 异常退出,返回非0状态码}return 0; // 正常退出
}
4.1.2 Makefile(编译运行指南)
# Makefile:编译自定义书架迭代器代码
# 适用环境:Linux/macOS(需安装g++)# 1. 编译器设置:使用g++,指定C++11标准(支持vector、异常等特性)
CXX = g++
CXXFLAGS = -std=c++11 -Wall -g # -Wall:开启所有警告;-g:生成调试信息# 2. 目标文件设置:可执行文件名、源文件、目标文件
TARGET = bookshelf_iterator # 最终生成的可执行文件
SRCS = main.cpp # 源文件(上面的代码保存为main.cpp)
OBJS = $(SRCS:.cpp=.o) # 目标文件(自动将.cpp换成.o)# 3. 默认目标:输入make时执行,编译生成可执行文件
all: $(TARGET)# 4. 链接目标文件:将.o文件链接成可执行文件
$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) -o $@ $(OBJS) # $@:目标文件(bookshelf_iterator);$<:依赖文件(.o)# 5. 编译源文件:将.cpp编译成.o文件
%.o: %.cpp$(CXX) $(CXXFLAGS) -c $< -o $@ # -c:只编译不链接# 6. 清理目标:输入make clean时执行,删除.o和可执行文件
clean:rm -f $(OBJS) $(TARGET) # 强制删除,避免提示# 7. 运行目标:输入make run时执行,编译后直接运行
run: all./$(TARGET)
4.1.3 编译运行步骤与结果解读
- 准备文件:将上面的代码保存为
main.cpp
,Makefile保存为Makefile
,放在同一文件夹; - 编译代码:打开终端,进入文件夹,输入
make
,终端会输出:
此时文件夹会生成g++ -std=c++11 -Wall -g -c main.cpp -o main.o g++ -std=c++11 -Wall -g -o bookshelf_iterator main.o
main.o
(目标文件)和bookshelf_iterator
(可执行文件); - 运行程序:输入
make run
,终端会输出:我的书架上有这些书: → 《C++ Primer 第6版》 → 《算法导论 第3版》 → 《设计模式:可复用面向对象软件的基础》 → 《深入理解计算机系统 第3版》
- 结果解读:
- 客户端(main函数)完全没用到
vector
、index
等书架内部细节,只通过Iterator
接口遍历; - 即使把书架的
vector
改成list
,只要BookShelf
的getBookAt
和getLength
方法不变,迭代器和客户端代码完全不用改——这就是解耦的威力!
- 客户端(main函数)完全没用到
4.2 案例2:C++ STL迭代器(实际开发中最常用)
C++ STL的“容器-算法-迭代器”模型,是迭代器模式的“终极应用”。比如vector
、list
、map
等容器,都通过begin()
/end()
提供迭代器,而sort
、for_each
等算法通过迭代器操作容器元素。
4.2.1 完整代码(演示STL迭代器的核心特性)
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <algorithm> // for_each算法
#include <iterator> // 迭代器工具(如back_inserter)/*** @brief 打印函数:用于for_each算法,打印单个元素* * 演示STL算法如何通过迭代器“间接操作”容器元素,无需知道容器类型。*/
template <typename T>
void printElement(const T& elem) {std::cout << elem << " ";
}int main() {// --------------------------// 1. vector:随机访问迭代器(最灵活的迭代器)// 特点:支持++、--、+n、-n(像指针一样直接跳)// --------------------------std::vector<int> vec = {10, 20, 30, 40, 50};std::cout << "1. vector遍历(随机访问迭代器):\n";// 方式1:普通for循环(利用随机访问特性,直接定位到i位置)std::vector<int>::iterator vec_it1;for (vec_it1 = vec.begin(); vec_it1 != vec.end(); ++vec_it1) {*vec_it1 += 5; // 迭代器可修改元素(非const)}std::cout << "vector元素加5后:";for (vec_it1 = vec.begin(); vec_it1 != vec.end(); ++vec_it1) {std::cout << *vec_it1 << " "; // 输出:15 25 35 45 55}std::cout << "\n";// 方式2:随机访问(直接跳到第3个元素,begin()+2 → 索引2)std::vector<int>::iterator vec_it2 = vec.begin() + 2;std::cout << "vector第3个元素:" << *vec_it2 << "\n"; // 输出:35// --------------------------// 2. list:双向迭代器(仅支持前后移动,不支持跳)// 特点:支持++、--,但不支持+n(链表结构决定,无法直接跳)// --------------------------std::list<std::string> lst = {"苹果", "香蕉", "橙子", "葡萄"};std::cout << "\n2. list遍历(双向迭代器):\n";// 方式1:普通for循环(只能++,不能+2)std::list<std::string>::iterator lst_it1;std::cout << "list元素:";for (lst_it1 = lst.begin(); lst_it1 != lst.end(); ++lst_it1) {std::cout << *lst_it1 << " "; // 输出:苹果 香蕉 橙子 葡萄}std::cout << "\n";// 方式2:双向移动(从end()向前跳1步,指向最后一个元素)std::list<std::string>::iterator lst_it2 = lst.end();--lst_it2; // end()是“尾后位置”,必须--才能指向最后一个元素std::cout << "list最后一个元素:" << *lst_it2 << "\n"; // 输出:葡萄// --------------------------// 3. map:双向迭代器(键值对容器,迭代器指向pair)// 特点:迭代器的value_type是std::pair<const Key, Value>,键不可修改// --------------------------std::map<int, std::string> student_map = {{101, "张三"},{102, "李四"},{103, "王五"}};std::cout << "\n3. map遍历(双向迭代器):\n";std::map<int, std::string>::iterator map_it;for (map_it = student_map.begin(); map_it != student_map.end(); ++map_it) {// map的迭代器指向pair,first是键(不可修改),second是值(可修改)std::cout << "学号:" << map_it->first << ",姓名:" << map_it->second << "\n";}// --------------------------// 4. STL算法:for_each(迭代器的“终极复用”)// 特点:同一算法可适配所有容器,只要容器提供迭代器// --------------------------std::cout << "\n4. STL算法for_each遍历:\n";std::cout << "vector用for_each:";std::for_each(vec.begin(), vec.end(), printElement<int>); // 输出:15 25 35 45 55std::cout << "\n";std::cout << "list用for_each:";std::for_each(lst.begin(), lst.end(), printElement<std::string>); // 输出:苹果 香蕉 橙子 葡萄std::cout << "\n";// --------------------------// 5. const_iterator:只读迭代器(避免误修改元素)// 特点:只能读取元素,不能修改(*it = xxx 会报错)// --------------------------std::cout << "\n5. const_iterator(只读):\n";std::vector<int>::const_iterator const_vec_it;std::cout << "vector只读遍历:";for (const_vec_it = vec.cbegin(); const_vec_it != vec.cend(); ++const_vec_it) {// *const_vec_it = 100; // 错误!const_iterator不允许修改元素std::cout << *const_vec_it << " "; // 只能读取}std::cout << "\n";return 0;
}
4.2.2 Makefile与运行结果
将代码保存为stl_iterator.cpp
,Makefile与案例1类似(只需修改TARGET
和SRCS
):
TARGET = stl_iterator
SRCS = stl_iterator.cpp
# 其余部分与案例1相同
运行make run
后,输出如下:
1. vector遍历(随机访问迭代器):
vector元素加5后:15 25 35 45 55
vector第3个元素:352. list遍历(双向迭代器):
list元素:苹果 香蕉 橙子 葡萄
list最后一个元素:葡萄3. map遍历(双向迭代器):
学号:101,姓名:张三
学号:102,姓名:李四
学号:103,姓名:王五4. STL算法for_each遍历:
vector用for_each:15 25 35 45 55
list用for_each:苹果 香蕉 橙子 葡萄 5. const_iterator(只读):
vector只读遍历:15 25 35 45 55
4.2.3 关键知识点:STL迭代器的5种类型
STL根据迭代器的功能强弱,将其分为5类,不同容器对应不同迭代器,这也是STL算法适配不同容器的关键:
迭代器类型 | 支持的操作(核心) | 典型容器 | 适用场景 |
---|---|---|---|
输入迭代器 | ++、*(只读)、==、!= | istream_iterator | 从容器读取元素(如文件读取) |
输出迭代器 | ++、*(只写) | ostream_iterator | 向容器写入元素(如文件写入) |
前向迭代器 | 输入迭代器 + 多遍遍历 | forward_list、unordered_map | 单链表遍历,可重复遍历 |
双向迭代器 | 前向迭代器 + – | list、map、set | 双向链表遍历,支持前后移动 |
随机访问迭代器 | 双向迭代器 + +n、-n、[]、<、> | vector、deque、array | 随机访问元素,效率最高 |
五、迭代器模式的现状与趋势:从“设计模式”到“语言特性”
迭代器模式早已不只是“设计模式手册里的概念”,而是成为很多编程语言的“内置特性”,让遍历变得更简单:
5.1 其他语言中的迭代器
语言 | 迭代器实现方式 | 示例代码(遍历列表) |
---|---|---|
Python | 内置__iter__() 和__next__() 方法,for循环自动调用迭代器 | python books = ["C++", "Python"] for book in books: print(book) |
Java | Collection接口提供iterator() 方法,foreach循环依赖迭代器 | java List<String> books = new ArrayList<>(); for (String book : books) { System.out.println(book); } |
C# | IEnumerable接口提供GetEnumerator() ,foreach自动遍历 | csharp List<string> books = new List<string>(); foreach (var book in books) { Console.WriteLine(book); } |
5.2 迭代器模式的新趋势
- 更简洁的遍历语法:如C++11的“范围for循环”(
for (auto& elem : container)
),底层还是迭代器,但代码更短; - 流式迭代器:如Java的Stream API、C++20的Ranges库,迭代器与函数式编程结合,支持过滤、映射等操作(如
vec | std::views::filter(x>10) | std::views::transform(x*2)
); - 安全迭代器:解决传统迭代器“失效”问题,如Java的
CopyOnWriteArrayList
,遍历期间修改容器不会导致迭代器失效(代价是拷贝内存)。
六、总结:迭代器模式的“灵魂”是什么?
迭代器模式的灵魂,不是“写一个Iterator接口”,而是**“分工”**——让聚合对象专注于“存储”,让迭代器专注于“遍历”,就像:
- 餐厅的后厨(聚合对象)只负责做菜(存储元素),不关心客人怎么吃;
- 服务员(迭代器)负责把菜端给客人(遍历元素),客人(客户端)只需要吃,不用管后厨怎么做菜。
这种分工带来的好处,就是灵活性和可复用性——换个后厨(比如从vector换成list),只要服务员(迭代器)不变,客人的用餐体验(客户端代码)就完全不受影响。
在C++ STL中,这种分工达到了极致:算法(客人的用餐方式)完全不依赖容器(后厨),只通过迭代器(服务员)操作元素,这也是STL能成为“C++基石”的重要原因。
如果你以后写代码时,发现“遍历一个容器需要写很多依赖内部结构的代码”,不妨想想迭代器模式——给你的“仓库”配一位“管理员”,让代码更简洁、更灵活!
附录:常见问题解答(FAQ)
问题 | 解答 |
---|---|
为什么STL的vector迭代器可能失效? | vector扩容时会重新分配内存,原迭代器指向的旧内存被释放,因此失效。解决办法:扩容后重新获取迭代器。 |
const_iterator和普通iterator的区别? | const_iterator只能读取元素,不能修改;普通iterator可以修改元素。建议只读遍历时用const_iterator,避免误操作。 |
自己实现迭代器时,要注意什么? | 1. 实现虚析构函数,避免内存泄漏;2. 做好边界检查(hasNext()判断),避免越界;3. 迭代器状态独立(每个迭代器有自己的index)。 |
迭代器模式和工厂模式有什么关系? | 聚合对象的createIterator()方法,本质是“工厂方法”——每个聚合对象是一个工厂,创建对应的迭代器。 |