在C++ STL(标准模板库)中,迭代器(iterator)是连接容器与算法的"桥梁"。它本质是一种"泛化的指针",封装了底层容器的访问逻辑,让我们无需关注容器的内部实现,就能统一、便捷地遍历容器中的元素------无论是vector、list、map,还是set,都能通过迭代器实现一致的遍历操作,这也是STL"算法与容器解耦"的核心设计思想。
本文从迭代器的基础认知、分类、核心用法、底层原理、常见误区五个维度,整理迭代器的核心知识点,适配新手学习与复习,兼顾实用性和应试性(面试常考)。
一、迭代器基础认知
1. 什么是迭代器?
迭代器是STL中定义的模板类/结构体,本质是"行为类似指针"的对象,提供了对容器元素的间接访问能力。它屏蔽了不同容器(如vector的连续内存、list的链表内存)的底层差异,让我们可以用统一的语法(如*it取值、++it移动)访问任何容器的元素。
简单来说:迭代器 = 通用版指针,指针是迭代器的一种特殊形式(针对数组),而迭代器可以适配所有STL容器。
2. 迭代器的核心作用
遍历容器:依次访问容器中的每一个元素,是STL遍历容器的标准方式。
元素操作:通过迭代器取值(*it)、修改元素(非const迭代器)。
连接算法与容器:STL算法(如sort、find)无需关心容器类型,只需接收迭代器范围,就能对容器进行操作(如sort只需传入容器的begin和end迭代器)。
3. 迭代器的头文件
迭代器无需单独包含头文件,只要包含对应容器的头文件,就会自动引入该容器的迭代器定义:
cpp
#include <vector> // 包含vector容器,同时引入vector::iterator
#include <list> // 包含list容器,同时引入list::iterator
#include <map> // 包含map容器,同时引入map::iterator
若需使用通用迭代器相关工具(如迭代器适配器),需包含头文件 #include <iterator>。
二、迭代器的分类(按功能划分,面试重点)
STL迭代器按功能和访问能力,分为5类,核心常用的是前4类,从弱到强依次为:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。不同容器支持的迭代器类型不同,这也决定了容器能支持的算法(如sort需要随机访问迭代器)。
1. 输入迭代器(Input Iterator)
最基础的迭代器,仅支持"只读"和"单向移动(++)",不能修改元素,也不能反向移动(--)。
支持操作:++it(前缀/后缀)、*it(取值,只读)、==、!=。
适用容器/场景:istream_iterator(输入流迭代器),仅用于读取数据,不用于容器遍历。
2. 输出迭代器(Output Iterator)
仅支持"只写"和"单向移动(++)",不能读取元素,也不能反向移动。
支持操作:++it、*it = val(赋值,只写)。
适用容器/场景:ostream_iterator(输出流迭代器),仅用于写入数据,不用于容器遍历。
3. 前向迭代器(Forward Iterator)
继承输入/输出迭代器的功能,支持"读写"和"单向移动(++)",可以重复遍历同一范围(多次++)。
支持操作:输入/输出迭代器的所有操作,可重复访问同一元素。
适用容器:forward_list(单向链表)。
4. 双向迭代器(Bidirectional Iterator)
继承前向迭代器的功能,新增"反向移动(--)",支持双向遍历。
支持操作:前向迭代器的所有操作 + --it(前缀/后缀)。
适用容器:list(双向链表)、set、multiset、map、multimap。
5. 随机访问迭代器(Random Access Iterator)
功能最强的迭代器,继承双向迭代器的功能,支持"随机访问"(直接访问任意位置元素),速度最快。
支持操作:双向迭代器的所有操作 +it + n、it - n、it += n、it -= n、it1 - it2(计算两个迭代器的距离)、[](如it[0]等价于*(it+0))。
适用容器:vector、deque、array(数组),也是最常用的迭代器类型。
核心总结(表格对比,面试直接背)
| 迭代器类型 | 核心能力 | 支持的容器 |
|---|---|---|
| 输入迭代器 | 只读、单向移动 | istream_iterator |
| 输出迭代器 | 只写、单向移动 | ostream_iterator |
| 前向迭代器 | 读写、单向移动、可重复遍历 | forward_list |
| 双向迭代器 | 读写、双向移动 | list、set、map 等 |
| 随机访问迭代器 | 读写、双向移动、随机访问 | vector、deque、array |
三、迭代器的核心用法(最常用,必掌握)
迭代器的使用流程:获取迭代器 → 移动迭代器 → 操作元素,不同容器的迭代器用法基本一致,以下以最常用的vector(随机访问迭代器)和list(双向迭代器)为例,讲解通用用法。
1. 迭代器的基本定义与初始化
每个容器都有对应的迭代器类型,格式为:容器类型::iterator(非const迭代器,可读写)、容器类型::const_iterator(const迭代器,只读)。
cpp
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// 1. 非const迭代器(可读写)
vector<int>::iterator it;
it = v.begin(); // 指向容器第一个元素
// 2. const迭代器(只读,不能修改元素)
vector<int>::const_iterator cit = v.cbegin(); // cbegin() 返回const迭代器
// 3. 反向迭代器(从容器末尾开始遍历)
vector<int>::reverse_iterator rit = v.rbegin(); // rbegin() 指向最后一个元素
return 0;
}
2. 迭代器的核心操作(以vector为例)
cpp
vector<int> v = {1, 2, 3, 4, 5};
vector<int>::iterator it = v.begin(); // 指向第一个元素(1)
// 1. 取值:*it
cout << *it << endl; // 输出:1
// 2. 移动迭代器:++it(前缀,效率更高)、it++(后缀)
++it; // 指向第二个元素(2)
cout << *it << endl; // 输出:2
// 3. 随机访问(只有随机访问迭代器支持)
it += 2; // 从2移动到4(跳过3)
cout << *it << endl; // 输出:4
cout << it[1] << endl; // 等价于*(it+1),输出:5
// 4. 遍历容器(最常用写法)
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " "; // 输出:1 2 3 4 5
}
// 5. 修改元素(非const迭代器)
*it = 10; // 将当前指向的元素(5)改为10
3. 双向迭代器用法(以list为例)
list的迭代器是双向迭代器,不支持随机访问(不能用it + n、it[]),只能用++it、--it移动:
cpp
#include <list>
using namespace std;
int main() {
list<int> l = {10, 20, 30};
list<int>::iterator it = l.begin();
++it; // 指向20(支持)
--it; // 指向10(支持)
// it += 2; // 报错!双向迭代器不支持随机访问
// 遍历(只能用++/--)
for (auto it = l.begin(); it != l.end(); ++it) {
cout << *it << " "; // 输出:10 20 30
}
return 0;
}
4. C++11 简化写法(auto自动推导迭代器类型)
C++11及以上支持auto关键字,无需手动写迭代器类型,简化代码(最常用,推荐):
cpp
vector<int> v = {1, 2, 3};
// auto 自动推导为 vector<int>::iterator
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
// const迭代器简化(只读)
for (auto it = v.cbegin(); it != v.cend(); ++it) {
// *it = 10; // 报错,只读
}
// 反向迭代器简化
for (auto it = v.rbegin(); it != v.rend(); ++it) {
cout << *it << " "; // 输出:3 2 1
}
四、迭代器的底层原理(面试考点)
迭代器本质是"封装了指针的模板类",不同容器的迭代器底层实现不同,核心是适配容器的内存结构,实现统一的访问接口。
1. 随机访问迭代器(vector)底层实现
vector的底层是连续内存(动态数组),所以其迭代器本质就是T*(普通指针),所有随机访问操作(it + n、it[])都是直接对指针进行操作,效率极高。
简化底层逻辑(伪代码):
cpp
template <typename T>
class vector_iterator {
private:
T* ptr; // 底层就是普通指针
public:
// 构造函数
vector_iterator(T* p) : ptr(p) {}
// 取值
T& operator*() { return *ptr; }
// 移动迭代器
vector_iterator& operator++() { ptr++; return *this; }
// 随机访问
vector_iterator& operator+(int n) { ptr += n; return *this; }
// 其他操作(==、!=、[]等)...
};
2. 双向迭代器(list)底层实现
list的底层是双向链表,内存不连续,无法用普通指针直接访问下一个元素,因此其迭代器需要封装"链表节点指针",并实现++、--操作(本质是移动到链表的下一个/上一个节点)。
简化底层逻辑(伪代码):
cpp
// list的节点结构
template <typename T>
struct list_node {
T data;
list_node* prev; // 前驱节点
list_node* next; // 后继节点
};
// list迭代器
template <typename T>
class list_iterator {
private:
list_node<T>* node; // 底层是链表节点指针
public:
list_iterator(list_node<T>* n) : node(n) {}
// 取值
T& operator*() { return node->data; }
// 移动到下一个节点
list_iterator& operator++() {
node = node->next;
return *this;
}
// 移动到上一个节点
list_iterator& operator--() {
node = node->prev;
return *this;
}
// 其他操作...
};
3. 核心总结
迭代器的底层实现依赖容器的内存结构:
连续内存容器(vector、deque):迭代器 = 封装的普通指针,支持随机访问,效率高。
非连续内存容器(list、set、map):迭代器 = 封装的节点指针,仅支持双向/前向移动,效率略低,但适配链表/树形结构。
所有迭代器都统一了"访问接口",因此算法可以无缝适配所有容器。
五、迭代器的常见误区与注意事项(避坑重点)
1. 迭代器失效(最常见、最容易踩坑)
迭代器失效:迭代器指向的内存位置失效(如内存被释放、元素被删除),此时使用迭代器(如*it、++it)会导致未定义行为(程序崩溃、乱码)。
常见导致迭代器失效的场景:
场景1:vector扩容(push_back时触发扩容)------ 扩容会申请新内存、拷贝旧元素、释放旧内存,原迭代器指向旧内存,全部失效。
场景2:删除容器元素(erase)------ 删除元素后,该元素及其后面的迭代器失效(vector),或仅删除的迭代器失效(list)。
避坑建议:
vector扩容后,重新获取迭代器(如it = v.begin())。
删除元素时,用erase的返回值更新迭代器(仅vector、deque需要,list无需)。
避免在遍历过程中随意修改容器(如push_back、erase)。
2. const迭代器与非const迭代器的区别
iterator:非const迭代器,可读写元素,可修改容器内容。
const_iterator:const迭代器,只读元素,不能修改容器内容(常用于遍历const容器)。
注意:cbegin()、cend() 返回const迭代器,begin()、end() 返回非const迭代器。
3. 迭代器的赋值与比较
只有同一容器的迭代器才能赋值和比较(如vector的迭代器不能和list的迭代器比较)。
迭代器的==、!= 表示"是否指向同一个元素",<、> 仅随机访问迭代器支持(表示位置先后)。
4. 空容器的迭代器
空容器的begin() 和 end() 相等(都指向"末尾之后"的位置),此时不能取值(*it),否则报错。
六、迭代器的应用场景(实战)
1. 遍历容器(最基础)
cpp
// 遍历vector
vector<string> vs = {"a", "b", "c"};
for (auto it = vs.begin(); it != vs.end(); ++it) {
cout << *it << endl;
}
// 遍历list
list<int> l = {1, 2, 3};
for (auto it = l.cbegin(); it != l.cend(); ++it) {
cout << *it << " ";
}
2. 配合STL算法使用(核心场景)
STL算法(如sort、find、reverse)的参数都是迭代器范围,无需关心容器类型:
cpp
#include <algorithm> // 包含STL算法头文件
vector<int> v = {3, 1, 4, 1, 5};
// 1. 排序(sort需要随机访问迭代器,vector支持,list不支持)
sort(v.begin(), v.end()); // 升序排序,结果:1 1 3 4 5
// 2. 查找(find支持所有迭代器)
auto it = find(v.begin(), v.end(), 4);
if (it != v.end()) {
cout << "找到元素:" << *it << endl;
}
// 3. 反转(reverse支持双向及以上迭代器)
reverse(v.begin(), v.end()); // 结果:5 4 3 1 1
七、总结(核心考点浓缩)
-
迭代器是STL容器与算法的桥梁,本质是泛化的指针,统一容器访问接口。
-
5类迭代器:输入、输出、前向、双向、随机访问,核心常用后4类,不同容器支持的迭代器类型不同。
-
核心用法:定义迭代器 → 用begin()/end()获取范围 → 移动迭代器 → 取值/修改(非const)。
-
底层:连续内存容器(vector)迭代器是封装指针,非连续内存(list)是封装节点指针。
-
重点避坑:迭代器失效(扩容、删除元素),删除元素用erase返回值更新迭代器。
-
面试高频:迭代器分类、迭代器失效场景、不同容器的迭代器类型。