引言:迭代器的核心价值
在 C++ 标准模板库 (STL) 中,迭代器扮演着 "胶水" 的角色,它连接了容器与算法,使算法能够独立于具体容器类型工作。这种抽象机制带来了极大的灵活性 —— 同一个排序算法可以作用于向量 (vector)、链表 (list) 或数组 (array),只需它们提供兼容的迭代器。
迭代适配器则是在基础迭代器之上的增强,通过包装现有迭代器,提供反向遍历、插入操作等特殊行为,进一步扩展了迭代器的能力。本文将系统解析迭代器的分类、实现原理及迭代适配器的应用场景。
一、迭代器基础:概念与分类
1.1 迭代器的本质
迭代器本质上是一种泛化的指针,它重载了*
、->
、++
等运算符,使开发者能够以统一的方式访问容器中的元素,而不必关心容器的内部实现细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <vector> #include <list> #include <iostream>
template<typename Iterator> void print_range(Iterator begin, Iterator end) { while (begin != end) { std::cout << *begin << " "; ++begin; } std::cout << std::endl; }
int main() { std::vector<int> vec = {1, 2, 3, 4}; std::list<std::string> lst = {"Hello", "World"}; print_range(vec.begin(), vec.end()); print_range(lst.begin(), lst.end()); return 0; }
|
1.2 迭代器的五种类型
C++ 标准根据迭代器支持的操作,将其分为五类,形成一个层次结构:
- 输入迭代器 (Input Iterator)
- 支持:
++
、*
、->
、==
、!=
- 特性:只能读取元素,单向移动,同一元素只能读一次
- 典型用途:从流中读取数据 (
istream_iterator
)
- 输出迭代器 (Output Iterator)
- 支持:
++
、*
- 特性:只能写入元素,单向移动,同一位置只能写一次
- 典型用途:向流中写入数据 (
ostream_iterator
)
- 前向迭代器 (Forward Iterator)
- 支持输入迭代器的所有操作
- 特性:可多次读写同一元素,单向移动
- 典型用途:单向链表 (
forward_list
)
- 双向迭代器 (Bidirectional Iterator)
- 支持前向迭代器的所有操作
- 增加:
--
操作(反向移动)
- 典型用途:双向链表 (
list
)、集合 (set
)
- 随机访问迭代器 (Random Access Iterator)
- 支持双向迭代器的所有操作
- 增加:
+=
、-=
、[]
、随机访问
- 典型用途:向量 (
vector
)、数组 (array
)、字符串 (string
)
迭代器类型决定了可在其上使用的算法 —— 例如,sort
算法要求随机访问迭代器,而find
算法只需输入迭代器。
二、迭代适配器:扩展迭代器功能
迭代适配器是包装现有迭代器并改变其行为的对象,它们不直接访问容器,而是通过底层迭代器工作。STL 提供了三种主要的迭代适配器:反向迭代器、插入迭代器和流迭代器。
2.1 反向迭代器 (Reverse Iterator)
反向迭代器将迭代方向反转,使++
操作实际移动到前一个元素,--
操作移动到后一个元素。通过rbegin()
和rend()
可以获取容器的反向迭代器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <vector> #include <iostream>
int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; std::cout << "正向遍历: "; for (auto it = numbers.begin(); it != numbers.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; std::cout << "反向遍历: "; for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) { std::cout << *it << " "; } std::cout << std::endl; return 0; }
|
输出结果:
1 2
| 正向遍历: 1 2 3 4 5 反向遍历: 5 4 3 2 1
|
反向迭代器与正向迭代器可以通过base()
方法相互转换,但需要注意它们指向的位置关系:反向迭代器rit
对应的正向迭代器rit.base()
指向的是rit
当前指向元素的下一个元素。
2.2 插入迭代器 (Insert Iterator)
插入迭代器将赋值操作 (*it = value
) 转换为插入操作,适用于在容器中插入元素而非覆盖现有元素。STL 提供三种插入迭代器:
- back_insert_iterator:在容器末尾插入(要求容器支持
push_back()
)
- front_insert_iterator:在容器开头插入(要求容器支持
push_front()
)
- insert_iterator:在指定位置插入(要求容器支持
insert()
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <vector> #include <list> #include <iterator> #include <algorithm> #include <iostream>
int main() { std::vector<int> source = {1, 2, 3}; std::vector<int> dest1; std::list<int> dest2; std::copy(source.begin(), source.end(), std::back_inserter(dest1)); std::copy(source.begin(), source.end(), std::front_inserter(dest2)); std::vector<int> dest3 = {10, 20, 30}; std::copy(source.begin(), source.end(), std::inserter(dest3, dest3.begin() + 1)); std::cout << "dest1: "; for (int x : dest1) std::cout << x << " "; std::cout << "\ndest2: "; for (int x : dest2) std::cout << x << " "; std::cout << "\ndest3: "; for (int x : dest3) std::cout << x << " "; return 0; }
|
插入迭代器解决了算法与容器大小的矛盾 —— 算法无需关心目标容器是否有足够空间,只需专注于元素的复制或转换。
2.3 流迭代器 (Stream Iterator)
流迭代器将输入输出流视为序列,允许我们像操作容器一样操作流。主要包括:
- istream_iterator:从输入流读取数据
- ostream_iterator:向输出流写入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <vector> #include <iterator> #include <algorithm>
int main() { std::cout << "请输入一些整数(按Ctrl+D结束): "; std::istream_iterator<int> in_iter(std::cin); std::istream_iterator<int> eof; std::vector<int> numbers(in_iter, eof); std::sort(numbers.begin(), numbers.end()); std::cout << "排序结果: "; std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<int>(std::cout, ", ")); std::cout << std::endl; return 0; }
|
流迭代器的强大之处在于能将算法直接应用于流操作,例如可以用std::transform
对流数据进行处理后直接输出。
三、迭代器的失效问题
当容器的内部结构发生变化时(如插入、删除元素),迭代器可能会失效,使用失效的迭代器会导致未定义行为。不同容器的迭代器失效规则不同:
- vector:
- 插入元素可能导致所有迭代器失效(当需要重新分配内存时)
- 删除元素导致被删除元素后的所有迭代器失效
- list:
- 插入元素不会导致任何迭代器失效
- 删除元素只导致指向被删除元素的迭代器失效
- map/set:
- 插入元素不会导致任何迭代器失效
- 删除元素只导致指向被删除元素的迭代器失效
安全删除示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| std::vector<int> vec = {1, 2, 3, 4, 5}; for (auto it = vec.begin(); it != vec.end(); ) { if (*it % 2 == 0) { it = vec.erase(it); } else { ++it; } }
std::list<int> lst = {1, 2, 3, 4, 5}; for (auto it = lst.begin(); it != lst.end(); ) { if (*it % 2 == 0) { it = lst.erase(it); } else { ++it; } }
|