迭代器(Iterator)概念详解
📌 什么是迭代器?
迭代器是一种抽象的设计模式,它提供了一种统一的方式来访问和遍历容器中的元素。一个迭代器可以是一个任意的对象,它指向某个范围内的元素(如数组、链表、树等),并通过实现一些基本操作来逐个访问这些元素。
🧩 迭代器的简单例子
示例 1:用下标变量模拟迭代器功能
int a[5] = {1, 2, 3, 4, 5};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += a[i];
}
- 变量
i
是一个整型索引,但它起到了类似迭代器的作用 —— 遍历数组中的每个元素。
示例 2:用指针模拟迭代器功能
Node *p = l.head;
while (p) {
// 使用 p->data 访问当前节点的数据
p = p->next;
}
- 指针
p
遍历了一个链表中的所有节点,它也具有类似于迭代器的功能。
🛠️ 如何为特定容器实现迭代器?
对于一个特定的容器类型(自定义类型),我们可以设计并实现一个专门的迭代器类:
✅ 只能用于容器Array的迭代器实现
#include <iostream>
#include <ostream>
using namespace std;
// 模板类 Array:支持非类型参数 n,表示数组的固定容量
template<typename T, int n>
class Array
{
private:
T* arr; // 动态分配的数组指针
int capcity; // 数组容量(最大可容纳元素个数)
int size; // 当前实际元素个数
public:
// 构造函数:根据模板参数 n 分配空间
Array() : arr(nullptr), capcity(n), size(0)
{
if (capcity > 0)
{
arr = new T[capcity];
}
}
// 拷贝构造函数:深拷贝
Array(const Array<T, n>& a) : arr(nullptr), capcity(a.capcity), size(a.size)
{
if (capcity > 0)
{
arr = new T[capcity];
for (int i = 0; i < size; ++i)
{
(*this)[i] = a[i]; // 使用下标操作符赋值
}
}
}
// 析构函数:释放动态分配的空间
~Array()
{
if (arr)
{
delete[] arr;
}
}
// 下标运算符重载(非 const 版本)
T& operator[](const int& i)
{
if (i >= 0 && i < size)
{
return arr[i];
}
}
// 下标运算符重载(const 版本)
const T& operator[](const int& i) const
{
if (i >= 0 && i < size)
{
return arr[i];
}
}
// 插入元素到数组末尾
bool insert(const T& d)
{
if (size >= capcity)
{
return false; // 容量已满,插入失败
}
arr[size++] = d;
return true;
}
// 获取当前数组中实际元素个数
int length(void) const
{
return size;
}
// << 输出运算符重载(友元函数)
template<typename T1, int n1>
friend ostream& operator<<(ostream& o, const Array<T1, n1>& a);
// 迭代器类:用于遍历 Array 数组中的元素
class ArrayIterator
{
private:
T* _arr; // 指向数组起始位置
int _size; // 遍历范围内的元素个数
int index; // 当前下标
public:
ArrayIterator(T* _start, int _s) : _arr(_start), _size(_s), index(0)
{}
// 将迭代器重置为指向第一个元素
void begin(void)
{
index = 0;
}
// 返回当前元素引用
T& operator*(void)
{
return _arr[index];
}
// 前缀自增运算符:移动到下一个元素
ArrayIterator& operator++()
{
index++;
return *this;
}
// 判断是否到达结尾
bool is_end(void)
{
return index == _size;
}
};
// 获取默认迭代器(从头开始,遍历全部元素)
ArrayIterator get_Iterator(void)
{
return ArrayIterator(arr, size);
}
// 获取指定起点和数量的迭代器
ArrayIterator get_Iterator(const int& first, const int& num)
{
return ArrayIterator(arr + first, num);
}
};
// << 输出运算符重载实现
template<typename T, int n>
ostream& operator<<(ostream& o, const Array<T, n>& a)
{
for (int i = 0; i < a.length(); ++i)
{
o << a[i] << " ";
}
o << endl;
return o;
}
int main()
{
// 创建一个容量为10的整型数组
Array<int, 10> a;
// 插入7个元素
a.insert(1);
a.insert(3);
a.insert(5);
a.insert(7);
a.insert(9);
a.insert(11);
a.insert(13);
// 输出整个数组
cout << a << endl;
// 输出:
// 1 3 5 7 9 11 13
cout << "--------------------" << endl;
// 获取默认迭代器
auto it = a.get_Iterator();
// 使用迭代器遍历数组
for (it.begin(); !it.is_end(); ++it)
{
cout << *it << " ";
}
cout << endl;
// 输出:
// 1 3 5 7 9 11 13
// 获取偏移1、长度6的迭代器
Array<int, 10>::ArrayIterator itr = a.get_Iterator(1, 6);
for (itr.begin(); !itr.is_end(); ++itr)
{
cout << *itr << " ";
}
cout << endl;
// 输出:
// 3 5 7 9 11 13
}
🔁 迭代器的工作原理
迭代器的本质是封装了“如何访问数据”的逻辑。它不关心底层容器的具体结构(数组、链表、哈希表等),而是通过统一接口来访问元素。
- 在软件构建中,集合对象的内部结构常常变化各异, 如:某种集合可能是数组实现,也可能是用链表实现的 …
- 对于这些集合对象, 我们希望在不暴露其内部结构的同时, 可以让外部用户透明的去访问其中包含的元素
- 同时 这种"透明遍历" 也为同一个算法在多个集合对象进行操作的提供了可能 ,使用面向对象技术将这种遍历机制抽象为 “迭代器对象” ,为遍历"变化中的集合对象"提供了一种很好的方式
- 迭代器模式:
提供一种方法 顺序访问一个聚合对象中的每一个元素,而又不暴露该对象的内部结构的表示
现在将之前实现的
ArrayIterator
类,改写为从一个统一的抽象迭代器接口类Iterator<T>
派生出来。
纯抽象类的迭代器接口
✅ 第一步:定义基类
template<typename T>
class Iterator {
public:
virtual void begin() = 0; // 将迭代器重置到起始位置
virtual bool end() const = 0; // 判断是否到达结束位置
virtual Iterator& operator++() = 0; // 前缀 ++ 运算符,移动到下一个元素
virtual T& operator*() const = 0; // 解引用操作符,返回当前元素的引用
virtual ~Iterator() = default; // 虚析构函数,确保派生类析构安全
};
✅ 第二步:修改 ArrayIterator
`
我们将原来的嵌套类 ArrayIterator
改写为继承自 Iterator<T>
,并实现所有纯虚函数。
📦 定义如下:
// 数组Array的"迭代器类",继承自 Iterator<T>
template<typename T>
class ArrayIterator : public Iterator<T> {
private:
T* _arr; // 指向遍历范围的首元素
int _size; // 遍历范围内元素个数
int index; // 当前下标
public:
ArrayIterator(T* start, int size)
: _arr(start), _size(size), index(0) {}
// 重置为第一个元素
void begin() override {
index = 0;
}
// 是否到达末尾
bool end() const override {
return index == _size;
}
// 前缀 ++it
Iterator<T>& operator++() override {
++index;
return *this;
}
// *it 返回当前元素的引用
T& operator*() const override {
return _arr[index];
}
};
✅ 第三步:更新 Array
类
ArrayIterator<T> get_Iterator() {
return ArrayIterator<T>(arr, size);
}
✅ 第四步:使用
使用统一的接口:
int main() {
Array<int, 10> a;
a.insert(1);
a.insert(3);
a.insert(5);
a.insert(7);
a.insert(9);
a.insert(11);
a.insert(13);
cout << a << endl;
cout << "--------------------" << endl;
// 使用统一的迭代器接口
ArrayIterator<int> it = a.get_Iterator();
for (it.begin(); !it.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 输出: 1 3 5 7 9 11 13
// 使用偏移迭代器
ArrayIterator<int> itr = a.get_Iterator(1, 6);
for (itr.begin(); !itr.end(); ++itr) {
cout << *itr << " ";
}
cout << endl;
// 输出: 3 5 7 9 11 13
}
🎯 补充说明
特性 | 说明 |
---|---|
统一接口 | 不同容器都可以使用相同的迭代器接口进行访问,便于泛型编程。 |
STL 支持 | C++ STL 中几乎所有容器都支持迭代器,例如 std::vector , std::list , std::map 等。 |
增强可维护性 | 容器与算法分离,提高了代码的复用性和可维护性。 |
避免越界访问 | 迭代器可以通过边界检查等方式提升安全性。 |