C++设计模式——Iterator迭代器模式
一,迭代器模式的定义
迭代器模式是一种行为型设计模式,它使得遍历一个容器对象中的元素变得更加简单。
迭代器模式将遍历操作从容器对象(如集合、列表)中分离出来,它通过迭代器对象来遍历容器对象中的元素,使得遍历操作与容器对象解耦。
迭代器模式可以解决的需求场景是当容器对象内部的结构经常被改变时,需要对外部屏蔽其变化过程。
迭代器模式对外提供了一个统一的遍历接口,这个接口可以顺序访问容器对象中的每个元素。
迭代器模式提供了遍历容器对象的方法,同时也隐藏了容器对象的实现细节,对容器对象的修改不影响迭代操作的使用。
迭代器模式在现实生活中的抽象实例:
1.图书馆中的书籍:在一个图书馆中,书籍可以看作是一个集合,每本书可以使用迭代器来逐个遍历。
2.超市货架上的商品:在超市中,货架上的商品可以使用迭代器来逐个检查和选择。
3.音乐播放器中的歌曲列表:在音乐播放器中,歌曲列表可以使用迭代器来逐个播放歌曲。
4.咖啡馆中的菜单:在咖啡馆中,菜单可以使用迭代器来逐个展示每种饮品和食物的信息。
在编程开发场景,迭代器的设计与实现有两种方式:
1.内部迭代器:通常是容器本身,由迭代器自身控制遍历过程,自发进行容器对象的访问和遍历,例如std::vector<int>,std::map<char, int>等。
2.外部迭代器:通常是独立于容器的接口,由调用者控制遍历过程,需要显示地访问容器对象中的元素,例如std::forward_iterator_tag等。
代码样例:
Demo1:内部迭代器
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = { 1, 2, 3, 4, 5 };
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
运行结果:
1 2 3 4 5
Demo2:外部迭代器
#include <iostream>
#include <list>
#include <algorithm>
#include <string>
class MyData {
public:
MyData(int tmpValue) {
value = tmpValue;
}
int value;
};
template<typename T, typename OutIt>
OutIt print_data(const std::list<T>& container, OutIt out) {
for (const auto& data : container) {
*out++ = " [";
*out++ = std::to_string(data.value);
*out++ = "]";
}
return out;
}
int main() {
std::list<MyData> dataList = { 10, 20, 30, 40, 50 };
std::ostream_iterator<std::string> outIter(std::cout, "");
print_data(dataList, outIter);
return 0;
}
运行结果:
[10] [20] [30] [40] [50]
二,迭代器模式的结构
迭代器模式主要包含以下组件:
1.迭代器(Iterator):
定义了迭代器的接口,包括获取下一个元素、判断是否还有下一个元素等方法。负责定义访问和遍历元素的接口。
2.具体迭代器(ConcreteIterator):
包含对迭代器接口的具体实现。
3.容器(Aggregate):
定义了容器对象的接口,包括获取迭代器、添加元素等方法。还提供了创建迭代器对象的接口。
4.具体容器(ConcreteAggregate):
包含对容器的具体实现。通常会在具体容器中返回一个具体迭代器对象。
组件之间的工作步骤如下:
1.客户端通过容器对象获取迭代器对象。
2.客户端使用迭代器对象进行遍历操作,获取容器中的元素。
3.迭代器对象根据容器的具体实现,在每次调用获取下一个元素的方法时返回容器中的下一个元素。
4.客户端使用迭代器对象的判断是否有下一个元素的方法,来判断是否继续遍历容器中的元素。
5.客户端根据具体的业务逻辑来处理遍历到的每个元素。
对应UML类图:
三,迭代器模式代码样例
#include <iostream>
#include <vector>
class Iterator {
public:
virtual bool hasNext() const = 0;
virtual int next() = 0;
};
class ConcreteIterator: public Iterator {
private:
std::vector<int> Aggregate;
int index = 0;
public:
ConcreteIterator(const std::vector<int>& vec){
Aggregate = vec;
}
bool hasNext() const override {
return index < Aggregate.size();
}
int next() override {
return Aggregate[index++];
}
};
class Aggregate {
public:
virtual Iterator* createIterator() const = 0;
};
class ConcreteAggregate : public Aggregate {
private:
std::vector<int> elements;
public:
ConcreteAggregate(const std::vector<int>& vec){
elements = vec;
}
Iterator* createIterator() const override {
return new ConcreteIterator(elements);
}
};
void traverseElements(ConcreteAggregate Aggregate) {
Iterator* iterator = Aggregate.createIterator();
while (iterator->hasNext()) {
int element = iterator->next();
std::cout << element << " ";
}
std::cout << std::endl;
delete iterator;
}
int main() {
std::vector<int> elements = { 1, 2, 3, 4, 5 };
ConcreteAggregate Aggregate(elements);
traverseElements(Aggregate);
return 0;
}
运行结果:
1 2 3 4 5
四,迭代器模式的应用场景
数据的分层访问:当数据源复杂且需要逐步解析时,迭代器使得上层代码可以按需获取数据,而不必关心底层实现的细节。
流式处理:在大数据处理场景,通常采用惰性加载或逐行读取的方式,只在需要时才请求下一个元素,类似于缓存机制。
算法封装:许多高级算法,如排序、搜索和图计算等,都可以用迭代器来驱动,这样既通用又灵活。
数据库查询:数据库API通常会提供游标,用户借助游标逐条获取查询结果,而无需了解具体的SQL执行细节。
文件系统开发:在读取大文件或目录树时,使用迭代器模式来依次访问每个文件或子目录,避免一次性加载所有内容导致内存溢出。
五,迭代器模式的优缺点
迭代器模式的优点:
简化了对容器对象的遍历操作,提供了一个统一的遍历接口。
将遍历操作与容器对象进行了解耦,使得容器对象可以独立变化。
处理大批量数据时,操作简单。
迭代器模式的缺点:
通常按顺序进行遍历,不支持直接索引访问,无法做到高效获取元素。
需要时刻维护自己的状态,如当前位置,不当使用可能导致错误。
每次遍历都需要创建一个新的迭代器对象,内存消耗大。
六,代码实战
基于迭代器模式封装的链表
#include <iostream>
template <typename T>
struct Node {
T data;
Node* next;
};
template <typename T>
class Iterator {
public:
virtual T next() = 0;
virtual bool hasNext() = 0;
};
template <typename T>
class LinkedListIterator : public Iterator<T> {
public:
LinkedListIterator(Node<T>* start){
current_ = start;
}
T next() override {
T data = current_->data;
current_ = current_->next;
return data;
}
bool hasNext() override {
return current_ != nullptr;
}
private:
Node<T>* current_;
};
int main() {
Node<int>* head = nullptr;
for (int i = 6; i >= 1; i--) {
Node<int>* newNode = new Node<int>{ i, head };
head = newNode;
}
Iterator<int>* iterator = new LinkedListIterator<int>(head);
while (iterator->hasNext()) {
std::cout << iterator->next() << " ";
}
while (head != nullptr) {
Node<int>* temp = head;
head = head->next;
delete temp;
}
delete iterator;
return 0;
}
运行结果:
1 2 3 4 5 6
七,参考阅读
https://softwarepatterns.com/cpp/iterator-software-pattern-cpp-example
https://www.geeksforgeeks.org/iterator-pattern/
http://qiusuoge.com/13723.html
https://www.geeksforgeeks.org/stdistream_iterator-stdostream_iterator-c-stl/
https://en.cppreference.com/w/cpp/iterator/next