前一节中我们讨论了主要的几个迭代器,但是那几个迭代器都是定义于STL中的标准容器,用法也只能针对标准容器,比较局限。在不断的演变中,STL的爱好者扩充了迭代器的内容,他们在迭代器的基础上发展而来,叫迭代器适配器,他提供了更多的操作功能,也不仅仅局限于容器,还可以应用于更多方面。
首先看看迭代器适配器的分支图:
以上的迭代器都叫做迭代器适配器,正如容器有标准容器和容器适配器,仿函数有标准仿函数和仿函数适配器,这里的迭代器适配器也是一样的道理。显然他们都是在原有迭代器的基础之上发展而来的,所以基础迭代器拥有的他们都拥有,包括上一节所讲的几个重要迭代器函数。下面我们将要一一的来讲解上图中的迭代器适配器。
1:逆向迭代器
逆向迭代器是一种适配器,他通过重新定义递增递减运算符使其行为恰好相反。这样一来使用这种迭代器算法将会逆向次序进行。
定义方式:
Vector<int>::reserve_iterator rit;
而且首位置为rbegin(),也就是原来的末位,末位置为rend(),也就是原来的首位;
请看下面一段代码:
vector<int> bec;
for(int i=1;i<11;i++)
vec.push_back(i);
for_each(vec.rbegin(),vec.rend(),print);
//这里假定print为已经定义的输出函数
这样输出结果可想而知,输出的当然是10开始的逆序。
请注意看接下来的例子,他对你理解逆向迭代器至关重要。
结果:
这就奇怪了,同样是pos所指的位置,该位置为5,为什么利用逆向迭代器输出来以后就变成4了?现在我们需要讨论一个问题,从输出中我们知道结果已经变了,但是到底是迭代器所指的位置变了,还是迭代器的位置没变,而是逻辑位置发生了改变。这里读者可以自己猜一下。
接下来,废话不多说,直接上图!!!
有了这图我就不需要再说什么了,相信大家已经对逆向迭代器理解了.下面我们将要介绍一个STL算法库中针对逆向迭代器的函数。
Base()函数
该函数将逆向迭代器转回正常的迭代器。在上面一个例子中已经用到了。结果也已经列出,不在多说。
对于逆向迭代器,就这些主要点的内容,改迭代器主要用来逆向输出或者操作,只在一些特定的地方可以更好的发挥,但是也要掌握好他的用法,有时候处理起来还是比较方便。
2:输入流输出流迭代器
1:输出流迭代器
输出流迭代器也是一种适配器,他由输出迭代器演变而来,赋予了写入到输出流中的能力,对于输入输出流这两个迭代器,我认为最好的方式就是通过实例来说明问题,可以更好的学到用法。
输出流迭代器定义:
Ostream_iterator<type>(ostream,delim);
或Ostream_iterator<type>(ostream,);
上面ostream代表需要为他的构造函数提供一个输出流对象,我们一般是用cout,delim是一个字符串指针,表示以它的内容来隔开输出的内容。
Iter++ 和++iter :表示迭代器往前移动,对下一个流单位进行写入。
下面直接看这个例子。
可以看到利用输出流迭代器输出单个元素或者是输出整个容器都是十分方便的,其中的copy函数是把begin和end之间的部分复制到inWriter中,实际就是复制到输出流中,所以整个容器都输出来了。这种方式在容器中是最常用的方法。
2:输入流迭代器
对于输入流迭代器,就是利用算法读取输入流中的数据,输入输出天生就是一对拍档,但是输入流却要比输出流复杂一点,因为读取出来本来就要比写入进去复杂。
定义:
利用构造函数构造一个输入流迭代器的时候也需要传递一个输入流对象作为参数,我们使用cin进行读取,但是读取动作可能发生的错误:读取数据失败或者是读到了文件的尾部,因此我们利用了一个end_of_stream迭代器,他可以由istream_iterator的默认构造函数生成。
Istream_iterator: istream_iterator<type> inRead(cin);
End_of_stream: istream_iterator<type> inReadEOF;
只要输入流迭代器有任何一次读取失败,他就会变成end_of_stream迭代器,所以每次读取你都应该把他们两个拿来比较一番。
还有个值得说明的是,输入流迭代器的构造函数一旦构造他,就会马上打开输入流缓冲区,有时候会在构造之后马上读取一个元素,所以尽量不要过早的定义它,有可能导致第一个元素无法传回。当然有些版本中会延缓这个动作,不会马上读取,这个作个了解就好。真要出现了我们也无法解决。
下面直接看例子:
可以看到,输入的是那几个数字只出现了前面几个,那是因为我们构造函数传递的类型参数是int 所以她读取到字符的时候导致读取错误,迭代器变为end_of_stream迭代器,循环结束。
对于输入流输出流迭代器,希望大家多用,多熟悉,本节只是提及了一下最基本的,并不深入,但是你会发现处理很多问题还是很方便的,在以后有专门的章节介绍stream的那些知识。
3:安插型迭代器
Insert 迭代器用来将赋值和复制操作转化为安插操作,(至于这句话是什么意思,我来分析一下,因为容器的操作都是赋值和复制操作,都会产生临时对象,所以效率不高,利用安插型迭代器直接插入一个值,效率会更好,而且在某些方面,不仅仅是效率问题了。这就是安插型迭代器的理念。)
在介绍安插型迭代器之前,我们来看看copy算法的源代码:
(只是模拟的,也许细节有出入,大概就这样)
可以看到,copy从算法起点开始不断循环,不断赋值给to_pos,直到循环结束。于是有初学者直到了copy算法之后就开始这样使用,代码如下:
结果大家可以猜想一下,这里我直接贴出来:
有经验的一看就是内存错误,也许是越界咯!
那么到底是什么原因勒?我们知道copy算法是依次赋值,我相信只要学过编程的都知道,要想给一个变量赋值,首先那个变量得有地址吧!要分配空间吧,不能给一个不存在的变量赋值吧,那么在这里,a容器使用默认构造函数,构造的是一个空容器,不存在任何长度,也就没分配那么多空间,如何存储得下b的元素,失败是必然的。
这里顺便提一句, 在标准的C++算法库中,没有任何一个算法在不借助外来帮助的情况下可以改变容器的大小,注意我说的是算法库中,不是指push_back这一类容器自带的函数。
所以在这里,我们的安插型迭代器出现用途了,这就是上面所说的借助的外来帮助,安插性容器本来自带了分配内存并且插入的功能
改写后的程序,代码如下:
在代码中我只是修改了其中一个地方, 利用了后插迭代器的便捷函数,也许大家还对安插迭代器陌生,前面这一切只是个铺垫,下面我们真正的开始介绍安插迭代器。
安插性迭代器分类:
1:前插迭代器。(默认为只在最前面插入)
2:后插迭代器。(默认为只能在最后插入)
3:插入迭代器(也就是可以指出插入位置的迭代器)
实际上他们都是调用相应容器的插入函数。
每一种迭代器都可以由一个便捷函数加以生成和初始化(便捷函数不陌生吧,pair模版和make_pair函数)
请记住下面这个表:
名称 |
迭代器类名 class |
调用相应的函数 |
便捷函数 |
后插迭代器 |
back_insert_iterator |
Push_back(value) |
Back_inserter |
前插迭代器 |
front_insert_iterator |
Push_front(value) |
Front_inserter |
插入迭代器 |
Insert_iterator |
Insert(pos,value) |
Inserter |
对于迭代器的举例,这里我只列出后插的即可,代码如下:
从这个程序中我们可以看到,后插迭代器的初始化,首先需要指明容器类型,然后还要赋一个具体的容器,使他们绑定在一起,初始化方法需要掌握。接下来利用的是便捷函数back_inserter的用法,可以看出他比较方便,最后一段代码表示利用copy算法把本身前面的一段插入到后面一段,但是也许有人不明白,为什么最后一个数值出错了勒?大家可以考虑一下这个原因是什么,上面一个例子我已经说了,安插迭代器可以在不分配内存的情况下直接插入,所以这里只是错误,但不至于程序崩溃。在对容器自身进行插入时最好是分配好足够的空间,因为这样可以使原有的迭代器固定下来,执行插入也不容易出错。如果不固定容器大小则他的迭代器会经常改变,这也就是导致错误的原因。
解决办法,在上面的代码中加入这一行,我相信大家都知道加在哪:
coll.reserve(2*coll.size()); //把容器长度重置为原来的两倍。
安插型迭代器本身的内容就不多,把前面的理解了就可以了,至于更高级的用法,这需要与算法库中的函数结合才能发挥出效果。
迭代器适配器就到这里,我们主要分析了三种适配器的用法和注意点,合理的运用适配器会使程序更加方便简洁,本节我略去了几个知识,一个是为迭代器编写泛型函数,一个是使用自定义的迭代器,这些过于高级,我自己也一知半解,所以不能误人子弟,以后有机会我会补充出来的。