C++学习:六个月从基础到就业------C++学习之旅:STL迭代器系统
本文是我C++学习之旅系列的第二十四篇技术文章,也是第二阶段"C++进阶特性"的第二篇,主要介绍C++ STL迭代器系统。查看完整系列目录了解更多内容。
引言
在上一篇文章中,我们详细探讨了STL容器,了解了各种容器的特性和用法。今天,我们将深入研究STL的另一个核心组件------迭代器系统。迭代器是连接容器和算法的桥梁,它使得算法可以以统一的方式访问不同类型的容器,是STL设计中最为精妙的部分之一。
什么是迭代器?
迭代器是一种行为类似于指针的对象,提供了一种访问容器中元素的方式,而不需要暴露容器的内部实现。通过迭代器,我们可以遍历集合、访问集合中的元素,并且根据需要修改这些元素。
迭代器抽象了底层容器的实现细节,提供了一套统一的接口,使得算法可以工作在各种不同的容器上,而无需知道这些容器的具体实现。
迭代器的分类
STL中的迭代器按照其功能和支持的操作分为五类:
- 输入迭代器(Input Iterator):最基础的迭代器,只支持单次遍历,只读访问元素。
- 输出迭代器(Output Iterator):只能写入元素,不能读取。
- 前向迭代器(Forward Iterator):支持多次遍历,可读写元素,只能向前移动。
- 双向迭代器(Bidirectional Iterator):在前向迭代器的基础上增加了向后移动的能力。
- 随机访问迭代器(Random Access Iterator):在双向迭代器的基础上增加了随机访问和迭代器算术的能力。

每种更高级别的迭代器都包含了前一级别迭代器的所有功能。不同的容器提供不同类型的迭代器,例如:
std::vector
提供随机访问迭代器std::list
提供双向迭代器std::forward_list
提供前向迭代器std::istream_iterator
是输入迭代器std::ostream_iterator
是输出迭代器
C++20新增了**连续迭代器(Contiguous Iterator)**作为随机访问迭代器的子类,对应于元素在内存中连续存储的容器(如std::vector
)。
迭代器的基本操作
迭代器提供了一系列基本操作,具体取决于其类别:
所有迭代器共有的操作
cpp
*it // 解引用迭代器
it->member // 访问元素的成员(相当于(*it).member)
++it // 前置自增(移动到下一个元素)
it++ // 后置自增
it1 == it2 // 相等比较
it1 != it2 // 不等比较
双向迭代器增加的操作
cpp
--it // 前置自减(移动到前一个元素)
it-- // 后置自减
随机访问迭代器增加的操作
cpp
it + n // 迭代器向前移动n个位置
it - n // 迭代器向后移动n个位置
it += n // 迭代器向前移动n个位置并赋值
it -= n // 迭代器向后移动n个位置并赋值
it1 - it2 // 计算两个迭代器之间的距离
it[n] // 随机访问(相当于*(it + n))
it1 < it2 // 小于比较
it1 > it2 // 大于比较
it1 <= it2 // 小于等于比较
it1 >= it2 // 大于等于比较
常见STL容器的迭代器类型和用法
让我们看看各种STL容器所提供的迭代器类型以及如何使用它们:
std::vector 的迭代器
std::vector
提供了随机访问迭代器,支持所有迭代器操作。
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 获取迭代器
auto begin = vec.begin(); // 指向第一个元素
auto end = vec.end(); // 指向最后一个元素之后的位置
// 基本遍历
std::cout << "基本遍历: ";
for (auto it = begin; it != end; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 随机访问
std::cout << "第三个元素: " << *(begin + 2) << std::endl;
// 逆向遍历
std::cout << "逆向遍历: ";
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 修改元素
for (auto it = begin; it != end; ++it) {
*it *= 2;
}
// 迭代器算术运算
auto middle = begin + (end - begin) / 2;
std::cout << "中间元素: " << *middle << std::endl;
return 0;
}
std::list 的迭代器
std::list
提供了双向迭代器,可以向前和向后移动,但不能随机访问。
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> lst = {1, 2, 3, 4, 5};
// 正向遍历
std::cout << "正向遍历: ";
for (auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 反向遍历
std::cout << "反向遍历: ";
for (auto it = lst.rbegin(); it != lst.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 移动迭代器
auto it = lst.begin();
++it; // 移动到第二个元素
++it; // 移动到第三个元素
std::cout << "第三个元素: " << *it << std::endl;
// 不支持随机访问,以下代码无法编译:
// auto third = lst.begin() + 2; // 错误!list迭代器不支持+n操作
return 0;
}
std::map 的迭代器
std::map
提供的是双向迭代器,迭代时会按照键的顺序遍历。
cpp
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> scores = {
{"Alice", 95},
{"Bob", 89},
{"Charlie", 92},
{"David", 88}
};
// 正向遍历
std::cout << "学生分数:" << std::endl;
for (auto it = scores.begin(); it != scores.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
// 反向遍历
std::cout << "\n按反向字母顺序:" << std::endl;
for (auto it = scores.rbegin(); it != scores.rend(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
// 查找并修改
auto it = scores.find("Bob");
if (it != scores.end()) {
it->second = 91; // 更新Bob的分数
std::cout << "\nBob的新分数: " << it->second << std::endl;
}
return 0;
}
迭代器适配器
STL提供了几种特殊的迭代器适配器,它们构建在其他迭代器之上,提供特殊的功能:
反向迭代器
反向迭代器将遍历方向反转,通过rbegin()
和rend()
获取。
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << "正向遍历: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
std::cout << "反向遍历: ";
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用std::reverse_iterator适配器
std::reverse_iterator<std::vector<int>::iterator> rev_it(vec.end());
std::cout << "使用反向迭代器适配器: ";
for (; rev_it != std::reverse_iterator<std::vector<int>::iterator>(vec.begin()); ++rev_it) {
std::cout << *rev_it << " ";
}
std::cout << std::endl;
return 0;
}
插入迭代器
插入迭代器允许算法将元素添加到容器中,而不是覆盖现有元素。STL提供三种插入迭代器:
- back_inserter - 在容器尾部插入元素
- front_inserter - 在容器头部插入元素
- inserter - 在指定位置之前插入元素
cpp
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> dest1;
std::list<int> dest2;
std::vector<int> dest3 = {10, 20, 30};
// back_inserter - 在尾部插入
std::copy(source.begin(), source.end(), std::back_inserter(dest1));
std::cout << "back_inserter结果: ";
for (int n : dest1) std::cout << n << " ";
std::cout << std::endl;
// front_inserter - 在头部插入 (只能用于支持push_front的容器)
std::copy(source.begin(), source.end(), std::front_inserter(dest2));
std::cout << "front_inserter结果: ";
for (int n : dest2) std::cout << n << " "; // 会是倒序的
std::cout << std::endl;
// inserter - 在指定位置插入
std::copy(source.begin(), source.end(), std::inserter(dest3, dest3.begin() + 1));
std::cout << "inserter结果: ";
for (int n : dest3) std::cout << n << " ";
std::cout << std::endl;
return 0;
}
流迭代器
STL还提供了连接I/O流与STL算法的流迭代器:
- istream_iterator - 从输入流读取元素
- ostream_iterator - 向输出流写入元素
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <sstream>
int main() {
// 读取输入流的例子
std::istringstream iss("1 2 3 4 5");
std::vector<int> vec;
// 使用istream_iterator从字符串流读取整数
std::istream_iterator<int> iss_iter(iss);
std::istream_iterator<int> iss_end; // 默认构造的迭代器表示结束
std::copy(iss_iter, iss_end, std::back_inserter(vec));
// 使用ostream_iterator输出到控制台
std::cout << "从流读取的数字: ";
std::copy(vec.begin(), vec.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
// 直接使用流迭代器读取和写入
std::istringstream input("10 20 30 40 50");
std::cout << "数字的平方: ";
std::transform(
std::istream_iterator<int>(input),
std::istream_iterator<int>(),
std::ostream_iterator<int>(std::cout, " "),
[](int x) { return x * x; }
);
std::cout << std::endl;
return 0;
}
迭代器特性(Traits)
迭代器特性是一种用于描述迭代器属性的类模板,它使算法能够决定使用最高效的实现。C++通过std::iterator_traits
来访问迭代器的特性。主要的迭代器特性包括:
- value_type - 迭代器指向的值的类型
- difference_type - 两个迭代器之间距离的类型
- pointer - 指向值的指针类型
- reference - 引用类型
- iterator_category - 迭代器类别
cpp
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <type_traits>
template <typename Iterator>
void printIteratorCategory() {
using Category = typename std::iterator_traits<Iterator>::iterator_category;
if (std::is_same<Category, std::input_iterator_tag>::value)
std::cout << "输入迭代器" << std::endl;
else if (std::is_same<Category, std::output_iterator_tag>::value)
std::cout << "输出迭代器" << std::endl;
else if (std::is_same<Category, std::forward_iterator_tag>::value)
std::cout << "前向迭代器" << std::endl;
else if (std::is_same<Category, std::bidirectional_iterator_tag>::value)
std::cout << "双向迭代器" << std::endl;
else if (std::is_same<Category, std::random_access_iterator_tag>::value)
std::cout << "随机访问迭代器" << std::endl;
#if defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 201907L
else if (std::is_same<Category, std::contiguous_iterator_tag>::value)
std::cout << "连续迭代器" << std::endl;
#endif
else
std::cout << "未知迭代器类别" << std::endl;
}
int main() {
std::cout << "vector迭代器: ";
printIteratorCategory<std::vector<int>::iterator>();
std::cout << "list迭代器: ";
printIteratorCategory<std::list<int>::iterator>();
std::cout << "istream_iterator: ";
printIteratorCategory<std::istream_iterator<int>>();
std::cout << "ostream_iterator: ";
printIteratorCategory<std::ostream_iterator<int>>();
// 简单的迭代器traits演示
using VecIt = std::vector<int>::iterator;
std::iterator_traits<VecIt>::value_type val = 10;
std::cout << "Vector迭代器value_type: " << val << std::endl;
return 0;
}
自定义迭代器
有时候,我们需要为自定义容器实现迭代器。以下是一个简单的自定义迭代器例子,用于遍历一个简单的环形缓冲区:
cpp
#include <iostream>
#include <vector>
#include <iterator>
// 简单的环形缓冲区
template <typename T, size_t Size>
class CircularBuffer {
private:
std::vector<T> buffer;
size_t head; // 开始位置
size_t count; // 元素数量
public:
CircularBuffer() : buffer(Size), head(0), count(0) {}
void push(const T& value) {
size_t position = (head + count) % Size;
buffer[position] = value;
if (count < Size)
++count;
else
head = (head + 1) % Size; // 环形缓冲区满了,覆盖最旧的元素
}
bool empty() const { return count == 0; }
bool full() const { return count == Size; }
size_t size() const { return count; }
size_t capacity() const { return Size; }
// 自定义迭代器
class Iterator {
private:
CircularBuffer* buffer;
size_t position;
size_t visited; // 已访问计数
public:
// 迭代器特性类型定义
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
Iterator(CircularBuffer* buf, size_t pos, size_t vis)
: buffer(buf), position(pos), visited(vis) {}
reference operator*() const {
return buffer->buffer[position];
}
pointer operator->() const {
return &(buffer->buffer[position]);
}
Iterator& operator++() {
if (visited < buffer->count) {
position = (position + 1) % Size;
++visited;
}
return *this;
}
Iterator operator++(int) {
Iterator temp = *this;
++(*this);
return temp;
}
bool operator==(const Iterator& other) const {
return buffer == other.buffer &&
(position == other.position ||
visited == buffer->count && other.visited == buffer->count);
}
bool operator!=(const Iterator& other) const {
return !(*this == other);
}
};
// 返回迭代器
Iterator begin() {
return Iterator(this, head, 0);
}
Iterator end() {
return Iterator(this, (head + count) % Size, count);
}
};
int main() {
CircularBuffer<int, 5> cb;
// 添加一些元素
for (int i = 1; i <= 7; ++i) {
cb.push(i);
}
std::cout << "环形缓冲区内容: ";
for (auto it = cb.begin(); it != cb.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用范围循环
std::cout << "使用范围循环: ";
for (int val : cb) {
std::cout << val << " ";
}
std::cout << std::endl;
// 适用于STL算法
std::cout << "使用std::for_each: ";
std::for_each(cb.begin(), cb.end(), [](int x) {
std::cout << x << " ";
});
std::cout << std::endl;
return 0;
}
迭代器失效问题
使用迭代器时,需要特别注意迭代器失效的问题。当容器被修改时,指向该容器的迭代器可能会失效。不同容器的迭代器失效规则不同:
-
vector/string:
- 插入时,如果没有内存重新分配,则只有插入点之后的迭代器失效
- 插入时,如果发生内存重新分配,则所有迭代器都失效
- 删除时,被删除元素之后的所有迭代器都失效
-
deque:
- 在两端插入/删除时,所有迭代器都失效,但引用不会失效
- 在中间插入/删除时,所有迭代器和引用都失效
-
list/forward_list:
- 只有指向被删除元素的迭代器会失效
- 插入不会使任何迭代器失效
-
map/set/multimap/multiset:
- 插入时迭代器不会失效
- 删除时,只有被删元素的迭代器失效
-
unordered_容器:
- 插入可能导致全部迭代器失效(如果发生重新哈希)
- 删除只会使被删元素的迭代器失效
迭代器失效示例
cpp
#include <iostream>
#include <vector>
#include <list>
void vectorIteratorInvalidation() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << "Vector迭代器失效示例:" << std::endl;
// 错误示例:在遍历时修改容器
std::cout << "错误示例(注释掉以防崩溃):" << std::endl;
/*
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it == 3) {
vec.push_back(6); // 可能导致迭代器失效和未定义行为
}
std::cout << *it << " ";
}
*/
// 正确示例1:先存储元素,结束遍历后再修改
std::cout << "正确示例1:" << std::endl;
std::vector<int> to_add;
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it == 3) {
to_add.push_back(6);
}
std::cout << *it << " ";
}
for (int val : to_add) {
vec.push_back(val);
}
std::cout << "\n修改后: ";
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
// 正确示例2:使用索引而非迭代器
std::cout << "\n正确示例2:" << std::endl;
vec = {1, 2, 3, 4, 5};
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
if (vec[i] == 3) {
vec.erase(vec.begin() + i); // 删除当前元素
--i; // 调整索引,避免跳过元素
}
}
std::cout << "\n删除后: ";
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
}
void listIteratorInvalidation() {
std::list<int> lst = {1, 2, 3, 4, 5};
std::cout << "\nList迭代器失效示例:" << std::endl;
// List在删除元素时只有被删元素的迭代器失效
for (auto it = lst.begin(); it != lst.end(); ) {
if (*it == 3) {
it = lst.erase(it); // erase返回下一个元素的迭代器
} else {
++it;
}
}
std::cout << "删除后: ";
for (int val : lst) {
std::cout << val << " ";
}
std::cout << std::endl;
}
int main() {
vectorIteratorInvalidation();
listIteratorInvalidation();
return 0;
}
迭代器与算法的结合
STL的迭代器和算法是紧密结合的,通过迭代器,算法可以作用于任何容器。以下是一些常用算法与迭代器的结合使用:
cpp
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <algorithm>
#include <numeric>
#include <iterator>
int main() {
// 初始化几个容器
std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7, 6, 4};
std::list<int> lst = {5, 2, 8, 1, 9, 3, 7, 6, 4};
std::map<std::string, int> mp = {
{"apple", 5},
{"banana", 3},
{"cherry", 7},
{"date", 2}
};
// 排序
std::sort(vec.begin(), vec.end());
// list不支持随机访问,不能用std::sort,但有自己的sort方法
lst.sort();
// 查找
auto vecIt = std::find(vec.begin(), vec.end(), 7);
auto lstIt = std::find(lst.begin(), lst.end(), 7);
auto mpIt = mp.find("cherry");
std::cout << "在vector中找到7: " << (vecIt != vec.end() ? "是" : "否") << std::endl;
std::cout << "在list中找到7: " << (lstIt != lst.end() ? "是" : "否") << std::endl;
std::cout << "在map中找到cherry: " << (mpIt != mp.end() ? "是" : "否") << std::endl;
// 计数
int count3 = std::count(vec.begin(), vec.end(), 3);
std::cout << "vector中3的数量: " << count3 << std::endl;
// 求和
int sum = std::accumulate(vec.begin(), vec.end(), 0);
std::cout << "vector元素总和: " << sum << std::endl;
// 转换
std::vector<int> doubled;
std::transform(vec.begin(), vec.end(), std::back_inserter(doubled),
[](int x) { return x * 2; });
std::cout << "翻倍后的元素: ";
for (int n : doubled) std::cout << n << " ";
std::cout << std::endl;
// 使用流迭代器输出
std::cout << "使用ostream_iterator输出vector: ";
std::copy(vec.begin(), vec.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
return 0;
}
实际项目中的迭代器最佳实践
避免迭代器失效
- 了解每种容器的迭代器失效规则
- 在修改容器前保存元素或迭代器
- 使用容器方法返回的新迭代器(如erase返回下一个元素的迭代器)
使用更现代的方式
- 尽可能使用范围for循环(C++11及以上)
- 使用auto避免冗长的迭代器类型声明
- 熟悉新标准引入的迭代器工具(如C++17的
std::size
,C++20的std::ranges
)
避免无效迭代器解引用
始终检查迭代器是否有效,特别是在可能返回end()的情况下:
cpp
auto it = container.find(key);
if (it != container.end()) {
// 只有确认迭代器有效时才解引用
useValue(*it);
}
使用迭代器适配器简化代码
cpp
// 不重新定义临时容器来存储结果
std::transform(input.begin(), input.end(),
std::back_inserter(output),
someTransformation);
// 直接写入流
std::copy(container.begin(), container.end(),
std::ostream_iterator<ValueType>(std::cout, ", "));
C++20中的范围(Ranges)
C++20引入了范围库,它建立在迭代器概念之上,但提供了更便捷的接口。范围允许更简洁、更可组合的算法表达:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
#include <ranges>
void rangesExample() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 使用管道语法过滤、转换和输出
auto result = vec
| std::views::filter([](int n) { return n % 2 == 0; }) // 只保留偶数
| std::views::transform([](int n) { return n * 2; }); // 将每个数乘以2
std::cout << "C++20 Ranges 过滤和转换后: ";
for (int n : result) {
std::cout << n << " ";
}
std::cout << std::endl;
}
#else
void rangesExample() {
std::cout << "您的编译器不支持C++20 Ranges" << std::endl;
}
#endif
int main() {
rangesExample();
return 0;
}
总结
迭代器是STL设计中最为精妙的部分之一,它实现了容器和算法的解耦,允许算法在不同的容器上以统一的方式运行。掌握迭代器对于高效使用STL至关重要。
本文讨论了迭代器的分类、操作、特性和常见问题,并展示了如何在实际项目中使用迭代器。随着C++20带来的范围库,迭代器的概念进一步演进,变得更易用和更强大。
在下一篇文章中,我们将探讨STL的第三个主要组件:算法库。我们将学习如何利用STL提供的众多算法来处理容器中的数据,从而避免重新发明轮子,编写更高效、更可靠的代码。
参考资源
- C++ 参考手册
- 《C++ 标准库》by Nicolai M. Josuttis
- 《Effective STL》by Scott Meyers
- 《The C++ Programming Language》by Bjarne Stroustrup
这是我C++学习之旅系列的第二十四篇技术文章。查看完整系列目录了解更多内容。
如有任何问题或建议,欢迎在评论区留言交流!