【STL】迭代器


本文对 C++标准库中的迭代器。


目录

  • [1 概述](#1 概述)
  • [2 示例](#2 示例)
    • [2.1 显式使用](#2.1 显式使用)
    • [2.2 隐式使用](#2.2 隐式使用)
  • [3 类别](#3 类别)
    • [3.1 分类](#3.1 分类)
    • [3.2 通用迭代器规则](#3.2 通用迭代器规则)
    • [3.3 迭代器类别层级关系](#3.3 迭代器类别层级关系)
  • [4 其他](#4 其他)

1 概述

迭代器是一种对象,用于遍历 C++标准库容器内的元素,并访问其中单个元素。所有 C++标准库容器都提供迭代器,让通用算法可以用统一方式访问容器元素,无需关心底层容器的具体类型。

你有两种使用迭代器的方式:

  • 显式使用:调用成员函数 / 全局函数(如begin()和end()),通过运算符(++或--)前后移动迭代器;

  • 隐式使用:通过范围 for 循环或(对于某些迭代器类型)下标运算符 \[\];
    标准库统一规则:

  • 在 C++标准库中,begin() 指向序列第一个元素;end() 永远指向最后一个元素的下一位;

  • 全局函数 begin()、end() 可以获取任意容器的首尾迭代器;

2 示例

2.1 显式使用

典型显式迭代器循环访问容器中的所有元素,如下所示:

cpp 复制代码
#include <vector>
#include <iostream>

int main() {
	std::vector<int> vec{ 0,1,2,3,4 };
	for (auto it = begin(vec); it != end(vec); it++)
	{
		// Access element using dereference operator
		std::cout << *it << " ";
	}
}

运行结果:

2.2 隐式使用

使用for循环更加简单的完成相同操作:

cpp 复制代码
#include <vector>
#include <iostream>

int main() {
	std::vector<int> vec{ 0,1,2,3,4 };
	for (auto num : vec)
	{
		// no dereference operator
		std::cout << num << " ";
	}
}

运行结果:

3 类别

3.1 分类

迭代器分为五类,按功能从弱到强排序如下:

  1. 输出迭代器(Output)
    • 输出迭代器 X 只能通过 ++ 向后遍历序列;
    • 通过解引用 * 仅能对元素写入一次,不可重复读取 / 重复写入;
  2. 输入迭代器(Input)
    • 输入迭代器 X 仅能用 ++ 向后遍历;
    • 可通过 * 多次读取元素;
    • 支持 ==、!= 运算符来比较输入迭代器;
    • 一旦递增该迭代器的任意一份副本,其他所有副本都不能再安全地判等、解引用或自增;
  3. 前向迭代器(Forward)
    • 前向迭代器完全具备输入迭代器能力,只能 ++ 向后遍历;
    • 可无限次读取元素,对非 const 容器可无限次写入;
    • 支持 -> 访问成员、== / != 比较;
    • 可以拷贝多份迭代器副本,每份副本都能独立解引用、自增;
    • 未绑定任何容器初始化的前向迭代器称为空前向迭代器,所有空前向迭代器互相相等;
  4. 双向迭代器(Bidirectional)
    • 双向迭代器可完全替代前向迭代器;额外支持递减操作 --X、X--;
    • 其余成员访问、比较规则和前向迭代器一致;
  5. 随机访问迭代器(Random access)
    • 随机访问迭代器可完全替代双向迭代器;
    • 下标运算符 \[\] 直接访问元素;
    • +、-、+=、-= 一次性跳跃多个元素、计算迭代器间距;
    • 支持完整大小比较:==、!=、<、> 、<=、>=。
类别 移动方向 核心限制 典型容器
Output 仅 ++ 每个位置只能写1次,不可读 ostream_iterator
Input 仅 ++ 自增后副本全部失效 istream_iterator
Forward 仅 ++ 副本独立有效,可反复读写 unordered_map / set
Bidirectional ++ / -- 可前后移动 list / map / set
RandomAccess 任意跳跃 \[\]、± 数字、大小比较 vector / deque / string / 数组指针

说明:

  1. Input 迭代器的致命限制
    • 只要 it++,其他拷贝出来的迭代器全部报废,不能再用;而 Forward 迭代器无此限制,拷贝后互不干扰;
  2. 替换规则:强迭代器兼容弱迭代器
    • 比如 sort 需要随机访问迭代器,list 只有双向迭代器,因此 list 不能用 std::sort,只能用自身成员 sort。
  3. 裸指针 = 随机访问迭代器
    • 数组指针可以传给所有标准算法,因为满足随机访问全部要求;
  4. 空迭代器(null forward iterator)
    • 仅 Forward 及以上才有该概念,默认构造、未绑定容器的迭代器互等,但不能解引用;

3.2 通用迭代器规则

  • 所有迭代器都支持拷贝、赋值
  • 迭代器是轻量对象,通常以值传递 / 返回,而非引用;
  • 对有效迭代器执行上文所有操作都不会抛出异常

3.3 迭代器类别层级关系

注:箭头含义:右方可替代左方

  1. 只写场景(输出)
    • 输出迭代器 → 前向迭代器 → 双向迭代器 → 随机访问迭代器
    • 算法要求输出迭代器时,更强类型迭代器都能兼容使用,反之不行;
  2. 只读场景(输入)
    • 输入迭代器 → 前向迭代器 → 双向迭代器 → 随机访问迭代器
    • 输入迭代器是只读体系里最弱的一类;
  3. 读写均可场景
    • 前向迭代器 → 双向迭代器 → 随机访问迭代器

4 其他

补充规则:

  • 原生裸指针天然等价随机访问迭代器,根据读写权限,可充当任意一类迭代器;

  • 非指针类型的自定义迭代器,必须提供 iterator_traits<Iterator> 所需内嵌成员类型,最简单实现方式是公有继承标准基类 std::iterator;
    注意:理解每类迭代器的能力与限制,是看懂标准库容器、算法如何使用迭代器的关键。

  • 范围 for 循环可省去手动迭代器操作;

  • MSVC 提供带检查迭代器与调试迭代器,运行时拦截越界、非法迭代器操作;