迭代器的本质是类似指针的对象 ,但不等于指针。具体来说:
-
指针是最简单的迭代器 (随机访问迭代器)
-
迭代器是更智能的指针抽象 (可能是类对象)
-
迭代器是:智能下标
从用法上看:迭代器完全可以看作访问容器元素的下标,作用一模一样;
从本质上看:它不是整数下标,而是指向元素的指针抽象;
从能力上看:它是所有容器通用的下标,数组下标做不到这一点
指针与迭代器的关系
cpp
// 是包含关系:
迭代器
├── 指针迭代器(vector, array, deque 等)
├── 类对象迭代器(list, map, set 等)
├── 特殊迭代器(reverse_iterator, move_iterator 等)
└── 自定义迭代器
// 都能运输(访问数据),但内部不同
"迭代器的本质是智能指针抽象。指针是最简单的迭代器,但迭代器可以是更复杂的类对象。关键在于所有迭代器都提供一致的接口(*、++、->等),让算法不关心具体实现。vector迭代器通常是指针,list/map迭代器是类对象。"
关键理解:
-
指针 ∈ 迭代器:指针是迭代器的子集
-
接口一致性:用法相同,内部实现不同
-
多态行为:通过操作符重载实现
-
零开销:简单迭代器优化后与指针性能相同
-
设计精髓:隐藏实现细节,暴露统一接口
1. 指针 vs 迭代器
指针是迭代器的一种
cpp
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr; // 指针
// 指针具有迭代器的所有基本操作:
*ptr; // 解引用
/*
解引用是"获取指针指向的值"的操作。符号 *有两种含义:
定义指针时:int* ptr→ 声明 ptr 是指针
使用指针时:*ptr→ 获取 ptr 指向的值
*/
++ptr; // 前进
ptr[2]; // 随机访问
ptr1 - ptr2;// 计算距离
ptr1 < ptr2;// 比较
但迭代器不一定是指针
cpp
#include <list>
#include <iostream>
std::list<int> lst = {1, 2, 3, 4, 5};
auto it = lst.begin(); // 迭代器(是类对象)
// 用法像指针
std::cout << *it; // 1
++it; // 前进
std::cout << *it; // 2
// ❌ 但不是指针
// it[2]; // 错误!list迭代器不支持随机访问
// it + 3; // 错误!
// it - lst.begin(); // 错误!
list是标准库的内置类模板。
lst.begin()返回的是 list<int>::iterator类型的对象,这是一个嵌套在 list 类内的迭代器类。
2. 迭代器的三种实现形式
① 指针(最简单)
// vector 的迭代器通常是原始指针
template<typename T>
class vector {
T* data; // 动态数组
public:
typedef T* iterator; // 迭代器就是指针
iterator begin() { return data; }
iterator end() { return data + size; }
};
② 类对象(复杂容器)
// list 的迭代器是类对象
template<typename T>
class list {
struct Node {
T data;
Node* next;
Node* prev;
};
class iterator {
Node* current; // 内部维护指针
public:
// 重载操作符
T& operator*() { return current->data; }
iterator& operator++() {
current = current->next;
return *this;
}
// ...
};
};
③ 智能指针包装
// 检查迭代器(调试模式)
class CheckedIterator {
int* ptr;
int* begin;
int* end;
public:
// 安全检查
int& operator*() {
if (ptr < begin || ptr >= end)
throw std::out_of_range("迭代器越界");
return *ptr;
}
// ...
};
3. 编译器视角
vector 迭代器通常优化为指针
cpp
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
// 编译器优化后:
int* it = vec.data(); // 直接是原始指针
// 零开销抽象
调试模式下的迭代器
// 调试版本:添加检查
class DebugVectorIterator {
int* ptr;
int* start;
int* finish;
int* end_of_storage;
void check_validity() {
if (ptr < start || ptr >= finish)
throw "迭代器无效";
}
public:
int& operator*() {
check_validity();
return *ptr;
}
// ...
};
5. 迭代器的核心:接口一致性
不管实现如何,接口相同
接口相同指的是迭代器都支持相同的操作符 (*、++、!=等),尽管底层实现不同。就像不同品牌汽车都有方向盘、油门、刹车,操作方法相同,但内部机械结构完全不同。
// 所有迭代器都支持这些操作
template<typename Iterator>
void process(Iterator it) {
auto value = *it; // 都能解引用
++it; // 都能前进
// 不关心里面是什么
}
就像不同汽车都有方向盘:
-
机械方向盘
-
电子方向盘
-
线控方向盘
-
接口相同:转动控制方向
-
实现不同:内部机制完全不同
例如不同手机,相同充电接口,就是接口相同
6. 迭代器的内存布局
vector 迭代器(通常是连续指针)
// 内存布局:[ptr]
+--------+
| 0x1000 | → 指向数组元素
+--------+
list 迭代器(包含节点指针)
// 内存布局:[node_ptr]
+--------+
| 0x2000 | → 指向链表节点
+--------+
↓
+------------+ +------------+
| data: 1 | | data: 2 |
| next: 0x2008| → | next: ... |
| prev: ... | | prev: 0x2000|
+------------+ +------------+
map 迭代器(树节点指针)
// 内存布局:[node_ptr, container_ptr, ...]
+--------+
| 0x3000 | → 指向红黑树节点
+--------+
7. 展示迭代器如何通过重载操作符来提供统一接口
(了解即可)
template<typename T>
class ListIterator {
Node<T>* current;
public:
// 重载 * 操作符
T& operator*() {
return current->data;
}
// 重载 ++ 操作符
ListIterator& operator++() {
current = current->next;
return *this;
}
// 重载 -> 操作符
T* operator->() {
return ¤t->data;
}
// 重载比较操作符
bool operator!=(const ListIterator& other) {
return current != other.current;
}
};
8. 迭代器和指针在实际使用中感知不到差异
// 使用起来完全一样
std::vector<int> vec = {1, 2, 3};
std::list<int> lst = {1, 2, 3};
std::map<int, std::string> m = {{1, "a"}};
// 相同操作
auto v_it = vec.begin();
auto l_it = lst.begin();
auto m_it = m.begin();
int v_val = *v_it; // 解引用
std::string m_val = m_it->second; // 箭头操作符
++v_it; // 前进
++l_it; // 前进
++m_it; // 前进
// 用户不关心里面是指针还是对象