当前位置: 首页 > news >正文

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

C++学习:六个月从基础到就业——C++学习之旅:STL迭代器系统

本文是我C++学习之旅系列的第二十四篇技术文章,也是第二阶段"C++进阶特性"的第二篇,主要介绍C++ STL迭代器系统。查看完整系列目录了解更多内容。

引言

在上一篇文章中,我们详细探讨了STL容器,了解了各种容器的特性和用法。今天,我们将深入研究STL的另一个核心组件——迭代器系统。迭代器是连接容器和算法的桥梁,它使得算法可以以统一的方式访问不同类型的容器,是STL设计中最为精妙的部分之一。

什么是迭代器?

迭代器是一种行为类似于指针的对象,提供了一种访问容器中元素的方式,而不需要暴露容器的内部实现。通过迭代器,我们可以遍历集合、访问集合中的元素,并且根据需要修改这些元素。

迭代器抽象了底层容器的实现细节,提供了一套统一的接口,使得算法可以工作在各种不同的容器上,而无需知道这些容器的具体实现。

迭代器的分类

STL中的迭代器按照其功能和支持的操作分为五类:

  1. 输入迭代器(Input Iterator):最基础的迭代器,只支持单次遍历,只读访问元素。
  2. 输出迭代器(Output Iterator):只能写入元素,不能读取。
  3. 前向迭代器(Forward Iterator):支持多次遍历,可读写元素,只能向前移动。
  4. 双向迭代器(Bidirectional Iterator):在前向迭代器的基础上增加了向后移动的能力。
  5. 随机访问迭代器(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提供三种插入迭代器:

  1. back_inserter - 在容器尾部插入元素
  2. front_inserter - 在容器头部插入元素
  3. 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算法的流迭代器:

  1. istream_iterator - 从输入流读取元素
  2. 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来访问迭代器的特性。主要的迭代器特性包括:

  1. value_type - 迭代器指向的值的类型
  2. difference_type - 两个迭代器之间距离的类型
  3. pointer - 指向值的指针类型
  4. reference - 引用类型
  5. 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;
}

迭代器失效问题

使用迭代器时,需要特别注意迭代器失效的问题。当容器被修改时,指向该容器的迭代器可能会失效。不同容器的迭代器失效规则不同:

  1. vector/string

    • 插入时,如果没有内存重新分配,则只有插入点之后的迭代器失效
    • 插入时,如果发生内存重新分配,则所有迭代器都失效
    • 删除时,被删除元素之后的所有迭代器都失效
  2. deque

    • 在两端插入/删除时,所有迭代器都失效,但引用不会失效
    • 在中间插入/删除时,所有迭代器和引用都失效
  3. list/forward_list

    • 只有指向被删除元素的迭代器会失效
    • 插入不会使任何迭代器失效
  4. map/set/multimap/multiset

    • 插入时迭代器不会失效
    • 删除时,只有被删元素的迭代器失效
  5. 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;
}

实际项目中的迭代器最佳实践

避免迭代器失效

  1. 了解每种容器的迭代器失效规则
  2. 在修改容器前保存元素或迭代器
  3. 使用容器方法返回的新迭代器(如erase返回下一个元素的迭代器)

使用更现代的方式

  1. 尽可能使用范围for循环(C++11及以上)
  2. 使用auto避免冗长的迭代器类型声明
  3. 熟悉新标准引入的迭代器工具(如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++学习之旅系列的第二十四篇技术文章。查看完整系列目录了解更多内容。

如有任何问题或建议,欢迎在评论区留言交流!

相关文章:

  • C++回顾 day3
  • 一些确保 iPaaS 集成平台与现有系统安全集成的方法
  • 深入剖析TCP协议(内容一):从OSI与TCP/IP网络模型到三次握手、四次挥手、状态管理、性能优化及Linux内核源码实现的全面技术指南
  • On the Biology of a Large Language Model——Claude团队的模型理解文章【论文阅读笔记】其一CLT与LLM知识推理
  • 初阶数据结构--排序算法(全解析!!!)
  • 开关电源LM5160-Q1 在 Fly-Buck 电路中的硬件设计与 PCB Layout 优化
  • OpenCV 图形API(53)颜色空间转换-----将 RGB 图像转换为灰度图像函数RGB2Gray()
  • HTML与Web 性能优化:构建高速响应的现代网站
  • 从物理到预测:数据驱动的深度学习的结构化探索及AI推理
  • vscode如何多行同时编辑,vscode快速选中多行快捷键
  • 从YOLOv5到YOLOv11,改进有多大?
  • 阻塞队列的介绍和简单实现——多线程编程简单案例[多线程编程篇(4)]
  • 升级xcode16之后react-native-zip-archive不兼容,unsupported option ‘-G‘
  • 从性能到安全:大型网站系统架构演化的 13 个核心维度
  • NoSQL 简单讲解
  • Hooks的使用限制及原因
  • 基于大模型的胃食管反流病全周期预测与诊疗方案研究
  • 机器视觉检测的量子效率QE
  • 基于 Spring Boot 瑞吉外卖系统开发(六)
  • 【每日八股】复习 Redis Day2:Redis 的持久化(下)
  • 被电诈100万元又要被骗71万元,女子经民警近8小时劝阻幡然醒悟
  • 不朽诗篇的现代重生,意大利音乐剧《神曲》将来华15城巡演
  • 外汇局:将持续强化外汇形势监测,保持汇率弹性,坚决对市场顺周期行为进行纠偏
  • 最高法:抢票软件为用户提供不正当优势,构成不正当竞争
  • 对话地铁读书人|翻译Esther:先读原著,再看电影
  • 上海黄金交易所:贵金属价格波动剧烈,提示投资者做好风险防范