迭代器
无论是序列容器还是关联容器,最常做的操作无疑是遍历容器中存储的元素,而实现此操作,多数情况会选用“迭代器(iterator)”来实现。
我们知道,尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等需要对数据进行遍历的操作方法应该是类似的。
既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,从而将容器和算法分离开。但实现此目的需要有一个类似中介的装置,它除了要具有对容器进行遍历读写数据的能力之外,还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据。
这是泛型思维发展的必然结果,于是迭代器就产生了。简单来讲,迭代器和 C++ 的指针非常类似,它可以是需要的任意类型,通过迭代器可以指向容器中的某个元素,如果需要,还可以对该元素进行读/写操作。
STL 标准库为每一种标准容器定义了一种迭代器类型,这意味着,不同容器的迭代器也不同,其功能强弱也有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。
常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 5 种。这里主要介绍前向迭代器、双向迭代器、随机访问迭代器这 3 种迭代器。
输入迭代器和输出迭代器比较特殊,它们不是把数组或容器当做操作对象,而是把输入流/输出流作为操作对象。
1) 前向(正向)迭代器(forward iterator)
假设p是一个前向迭代器,则p支持++p,p++,*p操作,还可以被复制或赋值,可以用==和!=运算符进行比较。此外,两个前向迭代器可以互相赋值。
2) 双向迭代器(bidirectional iterator)
双向迭代器具有前向迭代器的全部功能,除此之外,假设p是一个双向迭代器,则还可以进行–p或者p–操作(即一次向后移动一个位置)。
3) 随机访问迭代器(random access iterator)
随机访问迭代器具有双向迭代器的全部功能。除此之外,假设p是一个随机访问迭代器,i是一个整型变量或常量,则p还支持以下操作:
p+=i:使得p往后移动i个元素。
p-=i:使得p往前移动i个元素。
p+i:返回p后面第i个元素的迭代器。
p-i:返回p前面第i个元素的迭代器。
p[i]:返回p后面第i个元素的引用。
此外,两个随机访问迭代器p1、p2还可以用 <、>、<=、>= 运算符进行比较。另外,表达式p2-p1也是有定义的,其返回值表示p2所指向元素和p1所指向元素的序号之差(也可以说是p2和p1之间的元素个数减一)。
表 1 不同容器的迭代器
容器 | 对应的迭代器类型 |
---|
array | 随机访问迭代器 |
vector | 随机访问迭代器 |
deque | 随机访问迭代器 |
list | 双向迭代器 |
set / multiset | 双向迭代器 |
map / multimap | 双向迭代器 |
forward_list | 前向迭代器 |
unordered_map / unordered_multimap | 前向迭代器 |
unordered_set / unordered_multiset | 前向迭代器 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
注意,容器适配器stack和queue没有迭代器,它们包含有一些成员函数,可以用来对元素进行访问。
表2 迭代器的4种定义方式
迭代器定义方式 | 具体格式 |
---|
正向迭代器 | 容器类名::iterator 迭代器名; |
常量正向迭代器 | 容器类名::const_iterator 迭代器名; |
反向迭代器 | 容器类名::reverse_iterator 迭代器名; |
常量反向迭代器 | 容器类名::const_reverse_iterator 迭代器名; |
值得一提的是,表 2 中的反向迭代器全称为 “反向迭代器适配器”。
通过定义以上几种迭代器,就可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。其中,常量迭代器和非常量迭代器的分别在于,通过非常量迭代器还能修改其指向的元素。
反向迭代器和正向迭代器的区别在于:对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。
注意,以上4种定义迭代器的方式,并不是每个容器都适用。有一部分容器同时支持以上 4 种方式,比如 array、deque、vector;而有些容器只支持其中部分的定义方式,例如 forward_list 容器只支持定义正向迭代器,不支持定义反向迭代器。可以通过 C++ STL标准手册,查询具体容器迭代器支持的定义方式。
例1:vector 支持随机访问迭代器,因此遍历 vector 容器有以下几种方法:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
cout << "第一种遍历方法:" << endl;
for (int i = 0; i < v.size(); ++i)
cout << v[i] << " ";
vector<int>::iterator i;
cout << endl << "第二种遍历方法:" << endl;
for (i = v.begin(); i != v.end(); ++i)
cout << *i << " ";
cout << endl << "第三种遍历方法:" << endl;
for (i = v.begin(); i < v.end(); ++i)
cout << *i << " ";
cout << endl << "第四种遍历方法:" << endl;
i = v.begin();
while (i < v.end()) {
cout << *i << " ";
i += 2;
}
}
图1 遍历 vector 容器运行结果
例2:list 容器的迭代器是双向迭代器,遍历 list 容器可使用以下方法:
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> li;
list<int>::const_iterator i;
for (i = li.begin(); i != li.end(); ++i)
cout << *i;
for (i = li.begin(); i < li.end(); ++i)
cout << *i;
for (int i = 0; i < li.size(); ++i)
cout << li[i];
}
其实在 C++ 中,数组也是容器。数组的迭代器就是指针,而且是随机访问迭代器。例如,对于数组 int a[10],int * 类型的指针就是其迭代器。则 a、a+1、a+2 都是 a 的迭代器。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)