c++迭代器的介绍

12 篇文章 0 订阅
9 篇文章 0 订阅

迭代器主要的作用就是为了可以像数组那样实现指针向后移动到下一个数据。同时迭代器统一了所有容器,让所有容器可以通过迭代器互通数据。

那么下面我们来看看迭代器

数组的优势

我们数组的优势就是内存连续,那么我们将首地址的地址进行加减就可以访问上一个数据和下一个数据:

大概就是得到开头的指针,然后得到最后一个数据的下一个指针,然后我们就可以用这种方法逐一操作数据了。

迭代器

此时我们稍微将这个指针包装一下:

这大概就是迭代器的大概意思。

iterator就是迭代器的名字,所用容器的迭代器都叫这个名字

迭代器将所有的容器都视为数组的“连续内存形式”,这样我们就可以使用指针的加减操作。

各种容器迭代器的使用方法

容器名::iterator

定义迭代器要指定是哪个容器的迭代器

因为不同容器的内存形式是不同的,像链表就是碎片化的内存,那么我们的迭代器为了实现“数组的形式”,就要实现一个类来重载++、--这些操作,就是模拟指向下一个节点,上一个节点。所以每个容器的迭代器底层是不同的,所以为了保证迭代器名字相同就要typedef 一下。所以迭代器是名字一样,实现目的一样,但是实现的过程不同。

这些不同底层的迭代器都是在每个容器的类里面定义的,所以要加上空间限制符来指定你现在是用的哪个类的迭代器

头尾迭代器的调用

迭代器的开始就是类.begin(),尾就是类.end()

举例:

string:

vector:

list:

这里面的迭代器都是指向内容的地址指针,解引用后才是所要的数据。

既然是获取到它的地址,那么我们可以用迭代器做很多事情。改变数据,获取数据都是很方便的。

迭代器的种类

每个容器内部迭代器分类

每个容器实现的迭代器作用是基本相同的。我们有时候不想要可以改变值的迭代器,所以就有了const_iterator类的迭代器,同时我们想要反向遍历的迭代器,所以有了reserve_iterator

iterator

上面做了详细的讲解,就是正向的迭代器。

const_iterator

为什么我们不直接用const iterator呢?

这里我们要这样看:用string的迭代器来举例,string的迭代器底层应该就是数组指针进行包装:

这样看就会明白,直接在iterator前面加const只是让它的地址不能改变。编译器会将iterator看成一个整体,所以只能修饰我们的p指针。

我们可以看到依旧可以改变值。

所以就要这样tyedef:(用string的举例)

typedef const char* const_iterator

这样就会修饰我们的*p(值)了。

这里还是用begin()的原因就是进行了重载,而编译器会根据你传入值的类型来匹配最合适的重载,因为这里要返回const_iterator类型的,所以会调用返回对应类型的begin函数。

reverse_iterator

我们可以用iterator来反着遍历吗:

我们end是指向最后一个数据的下一个指针,我们可以-1来调整到最后一个开始,但是我们的begin要怎么调整呢?-1就会报错。所以是不能反着来的。

所以就有了reverse_iterator

这里会接触到一个单词reserve(反转),要和reverse(预留)做区分。

具体的实现底层我还不知道,没学到那去。

这里的reverse_iterator就是将方向变了,原先的正方向掉了个头。现在想左是正方向。

那么也就有对应的函数来返回对应的开头和结尾迭代器rbegin rend,r就是reverse的第一个字母,联系记忆。

const_reverse_iterator

既然有了反向迭代器,那么也要考虑权限的问题,所以就有const_reverse_iterator:

只能读不能写。

容器间迭代器的分类

因为不同容器的内存形式不同,产生内存结构的局限性,所以我们的迭代器的功能不是完全一样的。

例如我们的单链表是不能逆序遍历的,只能正向便利,所以只有正向迭代器。又因为链表的内存的不连续性,所以不支持迭代器的加减,只支持自增自减。

所以就分了:

随机迭代器(RandomAccessIterator)

这种迭代器支持迭代器的加减:

因为它自身是连续内存,所以这样跳跃访问时间复杂度是O(1)。

双向迭代器(BidirectionalIterator)

这种迭代器不支持跳跃访问,因为内存的不连续,如果支持时间复杂度是O(N),将是很大的时间损耗。

但是可以双向移动,例如我们的双向链表。

单向迭代器(ForwardIterator)

这种迭代器不支持双向访问,只能一个方向访问,即正向访问。

例如单链表,就只能正向访问。

那么这样就可以用维恩图来看:

各种容器里面与迭代器相关的成员函数

上面我也介绍了几个成员函数。

那么他们都是围绕着返回开头迭代器指针和结尾迭代器指针的。区分就是前面多了几个字母:

我们就介绍返回开头迭代器的begin相关函数,结尾函数自然就水到渠成了。

首先是begin函数,这个函数写了两个重载,如果是普通的类就返回可以改变值的迭代器,如果是const的类就返回不可修改的const迭代器。

然后是cbegin,这个就是绝对返回const_iterator的迭代器,无论是否是const的类。

其次是rbegin,这个就是reverse_iterator返回。

然后是两者组合,其实就是const_reverse_iterator的缩写。

auto变量

上面的什么string::const_reverse_iterator这么长的变量名,我们写着很麻烦,那么我们有一个auto自动变量可以自动匹配我们的变量类型:

我们借助typeid的name函数来看看变量名:

编译器通过看等号右边的变量类型来自动匹配左边的类型。

另外要提的点:auto不可以作为函数的传参类型,但是可以作为返回类型

但是这样虽然节省时间,但是降低了可读性,所以在一些不太重要的点上才用。防止后面看代码不知道变量的类型是什么。

范围for

只要支持迭代器,那么编译器就有一个简单实用的范围for就可以用:

这里支持迭代器要满足:1、你写的迭代器名字叫iterator。2、要有对应的begin(),end() 函数。

它和下面的的函数是一个原理:

这里的引用可以加可以不加,有时候不想在原容器值上操作的时候就可以用拷贝,但是也有缺点,如果容器储存的东西是类这种内存很大的类型的话,拷贝会很消耗性能,所以慎重,尽量用引用。

这里我们就可以写成auto:

迭代器的失效问题

有些情况我们的迭代器在传入到某些函数里面后,迭代器就失效了,要么不可访问成为野指针,要么就已经不是指向所要指的值了。

这里我们用数组来举例:

例如我们有一个这样的数组,我们的一个模拟的数组迭代器指向1处。我们要在1前面插入一个数据'x',那么就会变成"0x123",但是我们的迭代器依旧指向第二个空间,所以如果我们再次访问这个地址,就会访问到'x'而不是原来的'2'。

还有很多失效的情况我就不一一举例,我们要根据各个容器的底层结构来分析,看看迭代器是否失效。

当然也有迭代器不失效的情况,例如list的插入就不失效,因为内存是不连续的,插入和删除操作不会影响每个节点。

总之我们尽量不要使用做了参量的迭代器,防止某个函数过后迭代器失效,或者我们接收一下函数返回值更新我们的迭代器值

迭代器互通的底层支持

首先像string这种连续内存的迭代器很好实现。但是像list这种的就需要额外创建一个迭代器类来进行实现:

重载和类

单独创建一个list_iterator类,然后用运算符重载来实现自增自减,还有比较重载,然后在类里面typedef一下:typedef list_iterator iterator,那么这个种类的迭代器就只能在这个类里面使用,除非你知道它原本迭代器的名字。

模板

我们会遇到一些全局函数可以传任意的迭代器:

例如上面这个实现容器元素逆置的函数,只要是双向类型及以上的迭代器都可以传给它进行数据的交换。这就用到了模板来进行支持。

看到最后点个赞吧🙂!

  • 30
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值