迭代器(iterator)
我们知道,迭代器常用来访问容器中的元素。在使用迭代器的方式上来看,和指针非常的类似,甚至于就已经当作指针来使用了。事实上,迭代器的作用就是为了提供一种访问数据的方式。
容器(container)作为标准模板库中的一个核心内容,给我们提供了非常强大的数据结构的支持,以便于我们能够更加方便、高效的实现我们的算法。为了方便我们对容器中的元素进行操作,标准模板库还给我们提供了另一个强大的功能,那就是迭代器。因此,大家把迭代器作为连接容器和算法的纽带。
迭代器实际上是对数据访问的一种抽象,他不是一个固定的对象。由于不同的数据结构具有不同的访问方式,因此不同的容器需要给我们提供不同的迭代器。
迭代器操作
迭代器的行为类似于指针,因此它也有类似指针的操作,主要分为下面五类操作:
| 操作 | 作用 |
|---|---|
| 读操作 | 返回迭代器指向元素的引用(=*iter) |
| 写操作 | 对迭代器所指元素进行更改(*iter=) |
| 成员访问 | 访问迭代器所指元素的成员(iter->member、[]) |
| 迭代 | 令迭代器指向前(后)一个元素(iter++、iter–、++iter、–iter) |
| 比较 | 对迭代器对象进行比较(==、!=、>、<、>=、<=) |
注意,并不是所有的迭代器都具有这5个功能!
迭代器分类
根据迭代器能执行的操作可将迭代器分为下面五类:
| 类别 | 操作 |
|---|---|
| 输入迭代器 | *p=、++ |
| 输出迭代器 | =*p、->、++、==、!= |
| 前向迭代器 | *p=、=*p、->、++、==、!= |
| 双向迭代器 | *p=、=*p、->、++、- -、==、!= |
| 随机访问迭代器 | *p=、=*p、->、[]、++、- -、+、-、+=、- =、==、!= 、>、<、>=、<= |
容器的迭代器支持
STL中不同容器支持不同的迭代器,下面是它们支持的迭代器类型:
| 容器 | 迭代器类型 |
|---|---|
| vector | 随机访问迭代器 |
| list | 双向迭代器 |
| deque | 随机访问迭代器 |
| set | 双向迭代器 |
| map | 双向迭代器 |
另外,容器适配器不支持迭代器。
迭代器实例(以vector为例)
1.定义及初始化
vector提供了下面几种迭代器,这里以int为例:
vector<int>::iterator
vector<int>::const_iterator
vector<int>::reverse_iterator
vector<int>::const_reverse_iterator_iterator
前者可通过迭代器进行读写,而后者不能通过迭代器进行写操作。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec1 = { 1,3,5,7,9 };
const vector<int> &vec2 = vec1;
vector<int>::iterator it1 =vec1.begin();
vector<int>::const_iterator it2 = vec2.end();
return 0;
}
注1:vec.begin()函数返回指向容器中第一个元素的迭代器;vec.end()函数返回指向容器中最后一个元素后一个位置的迭代器。不要利用vec.end()返回的迭代器进行读写等操作,这样可能导致程序崩溃。
注2:vec.begin()和vec.end()的返回类型为iterator或者const_iterator,这取决于vector的类型。如程序中的vec2是vec1的常引用,因此vec2.end()返回类型为const_iterator,所以需要it2的类型需要声明为const_iterator。
注3:如果要获取普通vector的const_iterator或者reverse_iterator,还可以使用vector提供的vec.cbegin()、vec.rbegin()或者是vec.crbegin()等函数。当然,我们还可以使用C++提供的自动类型auto。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec1 = { 1,3,5,7,9 };
const vector<int> &vec2 = vec1;
auto it1 = vec1.begin();
auto it2 = vec2.end();
return 0;
}
2.遍历
迭代器最重要的功能就是对容器中的元素进行访问,下面是利用迭代器对容器进行遍历的程序:
顺序遍历
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec1 = { 1,3,5,7,9 };
for (auto it = vec1.begin(); it != vec1.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
1 3 5 7 9
逆序遍历
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec1 = { 1,3,5,7,9 };
for (auto it = vec1.rbegin(); it != vec1.rend(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
9 7 5 3 1
for循环中,循环的结束条件为”it!=vec1.end()”,正说明了vec1.end()函数返回的是容器最后一个元素后一个位置的迭代器。此外,这里没有用”it<vec1.end()”作为结束条件是因为除了vector和queue外其他容器都不提供随机访问迭代器,不能使用“<”、“>”、“<=”、“>=”对迭代器进行比较。因此为了统一,循环条件都写为”it!=vec1.end()”的形式。当然,对于vector和deque,使用”it<vec1.end()”是可以的。
注意,利用迭代器对容器进行遍历的方式并不是C++提供的最简洁的方式。C++还提供了range_based for和lambda函数等方式来对容器进行遍历,这里不展开比较。
迭代器失效
在讨论容器的时候我们提到,当容器中元素增加时,如果当前分配的空间已满,会重新分配一块更大的空间,然后执行复制、销毁等操作。那么这个时候迭代器所指向的元素可能发生了变化或者根本已经不存在了,这就是所谓的迭代器失效。
当然,除了增加元素还有许多使迭代器失效的情况,例如删除、插入等。
下面是一个迭代器失效的简单例子:
#include <iostream>
#include <vector>
#include <exception>
using namespace std;
int main() {
vector<int> vec1 = { 1,3,5,7,9 };
auto it_begin = vec1.begin();
auto it_end = vec1.end();
vec1.pop_back();//vec1.push_back(11);
for (auto it = it_begin; it != it_end; it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
上面的程序中,在第7、8行声明了两个迭代器并初始化为vec1.begin()和vec1.end(),随后执行了pop_back方法将容器中最后一个元素移出了,然后企图继续利用上述两个迭代器对容器元素进行遍历。运行的结果告诉我们这里根本得不到运行结果,使用已经失效的迭代器会发生非常严重的问题,使得程序崩溃。
因此,我们在使用迭代器的时候应该知道什么操作会使得结构体失效,从而采取措施进行避免。
来源:CSDN
作者:WavenZ
链接:https://blog.csdn.net/weixin_43374723/article/details/83958879