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
)。
迭代器的基本操作
迭代器提供了一系列基本操作,具体取决于其类别:
所有迭代器共有的操作
*it // 解引用迭代器
it->member // 访问元素的成员(相当于(*it).member)
++it // 前置自增(移动到下一个元素)
it++ // 后置自增
it1 == it2 // 相等比较
it1 != it2 // 不等比较
双向迭代器增加的操作
--it // 前置自减(移动到前一个元素)
it-- // 后置自减
随机访问迭代器增加的操作
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
提供了随机访问迭代器,支持所有迭代器操作。
#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
提供了双向迭代器,可以向前和向后移动,但不能随机访问。
#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
提供的是双向迭代器,迭代时会按照键的顺序遍历。
#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()
获取。
#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 - 在指定位置之前插入元素
#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 - 向输出流写入元素
#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 - 迭代器类别
#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 >= 201907Lelse if (std::is_same<Category, std::contiguous_iterator_tag>::value)std::cout << "连续迭代器" << std::endl;
#endifelsestd::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;
}
自定义迭代器
有时候,我们需要为自定义容器实现迭代器。以下是一个简单的自定义迭代器例子,用于遍历一个简单的环形缓冲区:
#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;elsehead = (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_容器:
- 插入可能导致全部迭代器失效(如果发生重新哈希)
- 删除只会使被删元素的迭代器失效
迭代器失效示例
#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的迭代器和算法是紧密结合的,通过迭代器,算法可以作用于任何容器。以下是一些常用算法与迭代器的结合使用:
#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()的情况下:
auto it = container.find(key);
if (it != container.end()) {// 只有确认迭代器有效时才解引用useValue(*it);
}
使用迭代器适配器简化代码
// 不重新定义临时容器来存储结果
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引入了范围库,它建立在迭代器概念之上,但提供了更便捷的接口。范围允许更简洁、更可组合的算法表达:
#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; }); // 将每个数乘以2std::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;
}
#endifint 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++学习之旅系列的第二十四篇技术文章。查看完整系列目录了解更多内容。
如有任何问题或建议,欢迎在评论区留言交流!