引言

在 C++ 中,迭代器 (iterator) 和指针 (pointer) 是两个密切相关但又有所区别的概念。它们都可以用来访问内存中的数据,都支持类似的操作符 (如*->),但应用场景和功能范围却有显著差异。本文将深入解析迭代器与指针的关系、区别及各自的应用场景。

核心概念

指针的本质

指针是 C++ 从 C 语言继承而来的概念,是一个变量,其值为另一个变量的内存地址。指针直接指向内存中的某个位置,可以是:

  • 普通变量的地址
  • 数组元素的地址
  • 动态分配内存的地址
  • 函数的地址

迭代器的本质

迭代器是 C++ 标准库提供的一种抽象,它模拟了指针的行为,为各种容器提供了统一的访问接口。迭代器可以看作是 "广义指针",它使得算法可以独立于容器类型工作。

两者的核心关系

  • 迭代器在很多方面模仿了指针的行为
  • 指针可以看作是一种特殊的迭代器(用于原生数组)
  • 所有指针都满足随机访问迭代器的要求
  • 迭代器通常通过重载运算符来模拟指针的操作

代码示例

指针与迭代器的基本使用对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <vector>
#include <array>

int main() {
// 1. 指针操作原生数组
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr; // 指向数组首元素的指针

std::cout << "使用指针访问数组:" << std::endl;
for (size_t i = 0; i < 5; ++i) {
std::cout << *ptr << " "; // 解引用
ptr++; // 指针自增
}
std::cout << std::endl;

// 2. 迭代器操作vector容器
std::vector<int> vec = {10, 20, 30, 40, 50};
std::vector<int>::iterator it = vec.begin(); // 获取起始迭代器

std::cout << "使用迭代器访问vector:" << std::endl;
while (it != vec.end()) { // 与结束迭代器比较
std::cout << *it << " "; // 解引用
it++; // 迭代器自增
}
std::cout << std::endl;

// 3. 指针可以作为数组的迭代器
std::cout << "使用指针作为迭代器:" << std::endl;
int arr2[] = {100, 200, 300};
// std::begin和std::end对数组返回指针
for (auto p = std::begin(arr2); p != std::end(arr2); ++p) {
std::cout << *p << " ";
}
std::cout << std::endl;

return 0;
}

不同类型的迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
#include <list>

int main() {
// 随机访问迭代器 (vector)
std::vector<int> vec = {1, 2, 3, 4, 5};
auto vec_it = vec.begin();
std::cout << "vector迭代器支持随机访问:" << std::endl;
std::cout << "第三个元素: " << *(vec_it + 2) << std::endl; // 支持+操作
std::cout << "距离末尾: " << (vec.end() - vec_it) << std::endl; // 支持-操作

// 双向迭代器 (list)
std::list<int> lst = {10, 20, 30, 40, 50};
auto lst_it = lst.begin();
std::cout << "\nlist迭代器仅支持双向访问:" << std::endl;
// lst_it + 2; // 编译错误,list迭代器不支持随机访问
std::advance(lst_it, 2); // 需要使用advance函数
std::cout << "第三个元素: " << *lst_it << std::endl;

return 0;
}

迭代器失效与指针失效对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <vector>

int main() {
// 迭代器失效示例
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin() + 2; // 指向3

std::cout << "迭代器失效演示:" << std::endl;
std::cout << "迭代器当前值: " << *it << std::endl;

vec.resize(10); // 可能导致内存重分配,使迭代器失效
// std::cout << *it << std::endl; // 未定义行为,可能崩溃

// 指针失效示例
int* arr = new int[5]{1, 2, 3, 4, 5};
int* ptr = &arr[2]; // 指向3

std::cout << "\n指针失效演示:" << std::endl;
std::cout << "指针当前值: " << *ptr << std::endl;

delete[] arr; // 释放内存,指针变为悬垂指针
// std::cout << *ptr << std::endl; // 未定义行为,可能崩溃

return 0;
}
1
g++ iterator_vs_pointer.cpp 

迭代器与指针的主要区别

特性 指针 迭代器
定义 直接指向内存地址的变量 抽象的数据访问接口,可能包含复杂逻辑
适用范围 原生数组、对象、函数等 主要用于 STL 容器
类型 只有一种指针类型(根据指向对象类型区分) 多种类型:输入、输出、前向、双向、随机访问
操作 支持所有指针算术运算 支持的操作取决于迭代器类型
安全性 安全性较低,容易产生悬垂指针 设计上更安全,提供了边界检查的可能性
实现 语言内置特性 通常是类模板的实例,通过运算符重载实现

应用场景

适合使用指针的场景

  1. 操作原生数组或基本数据结构
  2. 需要直接访问内存地址的底层操作
  3. 与 C 语言代码交互时
  4. 实现一些低级功能如内存管理

适合使用迭代器的场景

  1. 操作 STL 容器(vector、list、map 等)
  2. 使用标准库算法(如 std::sort、std::find)
  3. 需要编写与容器类型无关的通用代码
  4. 遍历复杂数据结构时

注意事项

  1. 迭代器失效:当容器发生修改(如插入、删除元素)时,迭代器可能失效,需特别注意
  2. 悬垂指针:指针指向的内存被释放后,指针成为悬垂指针,访问它会导致未定义行为
  3. const 正确性:正确使用const_iteratorconst指针,确保不意外修改数据
  4. 迭代器类型:不同容器提供不同类型的迭代器,了解其特性才能正确使用
  5. 范围检查:无论是指针还是迭代器,都应避免越界访问

总结

迭代器是 C++ 对指针概念的抽象和泛化,它继承了指针的访问方式,同时提供了更高层次的抽象和容器独立性。指针是底层工具,直接操作内存地址;迭代器则是高层接口,提供了统一的容器访问方式。

注意:C++11 及后续标准对迭代器进行了扩展,引入了cbegin()cend()等返回const_iterator的方法,进一步增强了迭代器的功能和安全性。