C++ 中的 迭代器(Iterator) 是用于遍历容器元素的一种抽象工具,类似于指针,提供了一种统一的方式来访问和操作 STL 容器中的元素。迭代器是 STL 的核心组件之一,与算法、容器紧密结合,使得容器与算法之间的分离成为可能。
以下将详细介绍 C++ 中的迭代器,包括其分类、特性、常见操作、自定义迭代器等。
1. 迭代器的概念
迭代器 是一个对象,允许用户逐一访问容器中的元素,而不需要了解底层数据结构。C++ STL 中的大多数容器都提供了迭代器,例如 std::vector
、std::list
、std::map
等。
迭代器的作用类似于指针,可以使用 *
操作符解引用获得当前元素,使用 ++
、--
操作符进行迭代(移动到下一个或上一个元素)。
2. 迭代器的分类
C++ STL 中的迭代器分为五类,按照它们的功能和性能特性进行划分:
2.1 输入迭代器(Input Iterator)
-
特点:只读访问容器中的元素,支持单向遍历。
-
操作:解引用(
*
)、前置/后置递增(++
)。 -
使用场景:适用于只需要从容器中读取元素的算法,如
std::find
。示例:
std::istream_iterator<int> input_it(std::cin); std::istream_iterator<int> end; while (input_it != end) { std::cout << *input_it << " "; ++input_it; }
2.2 输出迭代器(Output Iterator)
-
特点:只能写入元素,不能读取,支持单向遍历。
-
操作:解引用赋值(
*
)、前置/后置递增(++
)。 -
使用场景:适用于将结果输出到容器或流的算法,如
std::copy
。示例:
std::ostream_iterator<int> output_it(std::cout, " "); *output_it = 1; // 输出到标准输出 ++output_it;
2.3 前向迭代器(Forward Iterator)
-
特点:支持只读或读写访问,支持单向遍历,允许多次遍历相同的元素。
-
操作:解引用(
*
)、前置/后置递增(++
)。 -
使用场景:适用于需要读写访问的算法,如
std::replace
。示例:
std::forward_list<int> flist = { 1, 2, 3}; auto it = flist.begin(); while (it != flist.end()) { std::cout << *it << " "; ++it; }
2.4 双向迭代器(Bidirectional Iterator)
-
特点:支持双向遍历,既可以向前遍历,也可以向后遍历。
-
操作:解引用(
*
)、前置/后置递增(++
)、前置/后置递减(--
)。 -
使用场景:适用于需要双向遍历的算法,如
std::reverse
。示例:
std::list<int> lst = { 1, 2, 3}; auto it = lst.rbegin(); // 反向迭代器 while (it != lst.rend()) { std::cout << *it << " "; ++it; }
2.5 随机访问迭代器(Random Access Iterator)
-
特点:支持随机访问,可以直接跳转到容器中的任意元素。是功能最强的迭代器类型。
-
操作:解引用(
*
)、前置/后置递增(++
)、前置/后置递减(--
)、随机访问(it + n
、it[n]
、it - n
)。 -
使用场景:适用于需要高效随机访问的算法,如
std::sort
。示例:
std::vector<int> vec = { 1, 2, 3}; auto it = vec.begin(); it += 2; // 移动到第三个元素 std::cout << *it << std::endl; // 输出 3
3. 迭代器的常见操作
-
begin()
/end()
:返回指向容器第一个元素和最后一个元素之后的迭代器。std::vector<int> vec = { 1, 2, 3}; auto it = vec.begin();
-
解引用:通过
*it
访问当前迭代器指向的元素。std::cout << *it << std::endl;
-
递增:
++it
前置递增,it++
后置递增,移动到下一个元素。++it;
-
递减(仅适用于双向和随机访问迭代器):
--it;
-
随机访问(仅适用于随机访问迭代器):
it += 2; // 移动到第三个元素
-
比较:可以使用
==
、!=
来比较两个迭代器是否指向同一位置。随机访问迭代器还支持<
、>
、<=
、>=
等比较操作。
4. 反向迭代器
C++ 提供了反向迭代器,用于反向遍历容器。反向迭代器通过调用 rbegin()
和 rend()
获得。
示例:
std::vector<int> vec = {
1, 2, 3};
auto rit = vec.rbegin(); // 反向迭代器指向最后一个元素
while (rit != vec.rend()) {
std::cout << *rit << " ";
++rit;
}
在上述代码中,vec.rbegin()
返回指向最后一个元素的反向迭代器,vec.rend()
则返回指向第一个元素之前位置的反向迭代器。
5. 自定义迭代器
C++ 允许用户定义自己的迭代器,通过实现迭代器接口的相关函数,可以将自定义数据结构与 STL 算法结合使用。
自定义迭代器的步骤:
- 继承
std::iterator
或定义必要的类型别名,如value_type
、pointer
、reference
等。 - 实现
operator*
用于解引用。 - 实现
operator++
、operator--
(根据迭代器类型,选择实现前置或后置版本)。 - 实现比较操作符
==
、!=
用于迭代器比较。
示例:简单的自定义迭代器
template<typename T>
class MyIterator {
public:
using value_type = T;
using pointer = T*;
using reference = T&;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
MyIterator(pointer ptr) : ptr_(ptr) {
}
reference operator*() const {
return *ptr_; }
pointer operator->() {
return ptr_; }
// 前置递增
MyIterator& operator++() {
++ptr_;
return *this;
}
// 后置递增
MyIterator operator++(int) {
MyIterator tmp = *this;
++(*this);
return tmp;
}
friend bool operator==(const MyIterator& a, const MyIterator& b) {
return a.ptr_ == b.ptr_;
}
friend bool operator!=(const MyIterator& a, const MyIterator& b) {
return a.ptr_ != b.ptr_;
}
private:
pointer ptr_;
};
// 使用自定义迭代器
int main() {
int arr[] = {
1, 2, 3};
MyIterator<int> begin(arr);
MyIterator<int> end(arr + 3);
for (auto it = begin; it != end; ++it) {
std::cout << *it << " ";
}
return 0;
}
6. 迭代器适配器
C++ STL 提供了多种迭代器适配器,用于在现有的迭代器基础上扩展功能:
std::reverse_iterator
:反向迭代器
,用于反向遍历容器。
-
std::insert_iterator
:用于在容器中插入元素的迭代器适配器。std::back_inserter
、std::front_inserter
也是常见的插入迭代器,分别用于从容器的末尾和开头插入元素。 -
std::istream_iterator
和std::ostream_iterator
:分别用于从输入流读取数据和向输出流写入数据的迭代器适配器。
示例:反向迭代器适配器
std::vector<int> vec = {
1, 2, 3, 4, 5};
std::reverse_iterator<std::vector<int>::iterator> rit = vec.rbegin();
while (rit != vec.rend()) {
std::cout << *rit << " "; // 输出 5 4 3 2 1
++rit;
}
示例:插入迭代器适配器
std::vector<int> vec = {
1, 2, 3};
std::vector<int> vec2;
std::copy(vec.begin(), vec.end(), std::back_inserter(vec2)); // 将 vec 的元素复制到 vec2 中
示例:流迭代器适配器
std::vector<int> vec = {
1, 2, 3};
std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " ")); // 输出 1 2 3
7. 迭代器的安全性问题
C++ 中的迭代器操作有时会引发安全性问题,主要包括:
-
迭代器失效:当容器的结构发生变化时,如插入、删除、重分配等操作,迭代器可能会变得无效。
- 例如,在
std::vector
中插入或删除元素后,指向原始元素的迭代器可能不再有效。 - 使用
std::list
或std::forward_list
等链表容器时,插入和删除操作通常不会使迭代器失效。
- 例如,在
-
访问非法位置:如果迭代器指向了容器的
end()
或rend()
,对其解引用可能会导致未定义行为。
避免迭代器失效的建议
- 了解容器特性:掌握不同容器在进行插入、删除操作时对迭代器的影响。例如,在
std::vector
中,避免在迭代时进行插入或删除操作。 - 迭代时慎用增删操作:在遍历容器时,尽量避免增删操作。若需要修改容器,考虑使用
remove_if
、erase
结合使用,或通过手动管理迭代器来避免失效。 - 检查迭代器有效性:在使用迭代器时,务必检查其是否指向有效位置。
8. 总结
迭代器是 C++ 标准模板库中非常强大的工具,它为容器提供了统一的访问接口,使得算法与容器分离成为可能。理解并正确使用迭代器是掌握 C++ STL 的关键:
- 迭代器类型:从最简单的输入、输出迭代器,到前向、双向和随机访问迭代器,不同类型的迭代器提供了不同的功能和性能特性。
- 常见操作:迭代器支持解引用、递增、递减、随机访问和比较操作。
- 适配器:迭代器适配器如
reverse_iterator
、insert_iterator
、istream_iterator
和ostream_iterator
扩展了迭代器的功能。 - 安全性问题:迭代器失效和非法访问是常见的安全性问题,必须谨慎处理。
通过合理地选择和使用迭代器,开发者可以编写出更为高效、简洁且易维护的 C++ 程序。