10.5.2 迭代器辅助操作
1.前进、后退
前面提到“++”与“--”可以用在多数迭代器上,但“+=”与“-=”就只能支持随机迭代器。
#include <iterator>
void advance(InputItrator& pos, Dist n);
advance可以让一个迭代器(pos),前进n步,如果n是负数,并且pos又支持双向,那就是后退n步。
InputItrator 和 Dist在这里都是函数模板参数,Dist通常就是一个整数。advance会识别出pos是否支持随机访问,如果支持,那就是一次 pos += n的操作,否则,会调用以下代码(假设n为正数):
for(int i = 0; i < n; ++i) ++pos;
【危险】:不负责的导游
无论是包装好的advance()操作,还是直接的 ++, +=, --, -=操作,被前进或后退的迭代器,都不会主动检查自己是否已经越界了。
2.计算迭代器之间的距离
两个迭代器都指向同一个容器(并且这个容器没有在变动),那么可以计算这两个迭代器之间的距离(隔几个元素):
#include <iterator>
void distance(InputIterator pos1, InputIterator pos2);
pos1 和 pos2 必须指向同一个容器。如果是随机迭代器,则简单返回 pos2 - pos1;如果不是,该函数一直执行++pos,直到它和pos2相遇,可见,pos2 必须确保在 pos1 相等或其后的位置。
distance应用的典型场景,是在使用find算法查找到一个确切的位置之后,用来计算它距离前一个位置有多远。
3.交换两个迭代器所指向的值
#include <algorithm> //在"算法"中声明
void iter_swap(ForwardIterator1 pos1, ForwardIterator2 pos2);
这个函数不是在交换另个迭代器的指向,而是在交换二者所指向的值。
所以允许pos1和pos2不一定指向同一容器,甚至两个迭代器的类型都可以不一致,只要它们所指向的内容可以交换(互相赋值)。
10.5.3 喜欢兼职的迭代器
1.逆向迭代器
倒着遍历一个容器,这个需求非常普遍。如果使用“双向迭代器”,可以从容器的尾部(如果有提供的话)开始,然后使用“--”操作,一步步退回来:
list <int> li {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
list <int >::const_iterator it = li.end(); //尾部
//不能是for(--if;...),也不能是for(;it != begin();--it)
for(; it != li.begin();)
{
--it;
cout << *it << ",";
}
it一开始指向容器的尾部节点(一个虚的节点,位于最后一个节点之后)。如果这个容器是空的,那么begin()和end()是相等的,所以for循环的判断条件在空容器的情况下,也是适用的。
这个过程显然没有正向遍历来得直观,所以
“(Reverse)逆向迭代器”包装了正常的迭代器,重新定义了递增运算和递减运算,让二者的行为正好相反(镜像)。
逆向迭代器,必须附身在一个“双向迭代器”身上(因为内部通过调用递减操作实现)。
支持逆向遍历的容器,通常都提供了rbegin()和rend(),作用就像begin()和end(),只不过rbegin()其实是容器的尾端,而rend()是容器的开端。
...
list <int>::const_reverse_iterator it2 = li.rbegin();
//回到正常的for循环写法了,舒服!
for(; it2 != li.rend(); ++ it2)
{
cout << *it2 << ",";
}
rbegin()的位置相当于end(),但透过rbegin()访问一个数据,访问到的是其前面的那个元素(因为我们不能在end()位置上访问数据),如图10-13所示。
正向迭代器表里如一,表现和实际指向的都是同一个节点。逆向迭代器逻辑上指向n位置上的节点,但实际访问的是n-1位置上的节点;
【危险】:从正向迭代器,构造一个逆向迭代器
可以通过一个正向迭代器,构造出一个逆向迭代器,不过由于上述原因,经过构造转换之后,访问该逆向迭代器,得到的是其前一节点的位置。
举个例子:如果将begin()转换成逆向迭代器,就得到rend(),可不能访问它的值,因为它实际指向的内容非法。
2.插入型的迭代器
插入型迭代器,可以向容器插入数据。
根据可插入的位置,“插入型迭代器”又区分为“Front-Inserter(前端插入器)”、“Back-Inserter(后端插入器)”和“General-Inserter(通用插入器)”。
提供有push_back()操作的容器都可适用“Back-Inserter”。像标准库中的vectors,deques,lists,strings,等等。以vector为例:
//同样需要包含该头文件
#include <vector>
void test_back_inserter()
{
vector <int> v; //本来空无一物
back_insert_iterator <vector <int>> it_inserter(v);
for(int i = 0; i < 10; ++i)
{
//会调用v.push_back(i+1)
* it_inserter = i + 1;
}
//现在,v里面有10个元素
...
}
重点在“ * it_inserter = i + 1;”这一句。它会调用 v.push_back(i + 1)。、
back_insert_iterator <typename T>的模板参数是一个具体的容器类型,而构造参数则是一个容器。
front_insert_iterator <typename T>与之类似,只是改为要求容器提供push_front()操作,STL中此类容器有 deques 和 lists 等。
从插入效果看,由于每次都在最前面插入,如果是无需容器,则存储元素的次序刚好和插入次序相反(有序容器会根据插入内容自动排序)。
所有提供insert()操作的容器都支持“General-Inserter”。通用性插入器可在容器的各个位置插入。其构造函数也有所不同,必须指明插入位置:
insert_iterator <typename T, typename P> iter(T& c, P pos);
T和c 仍然是容器的类型的对象,pos 是一个迭代器,用于表示要插入的位置。
#include <list>
...
list <int> l;
//构造一个通用型插入迭代器,首先在begin()处
insert_iterator <list<int>> it_inserter(l, l.begin());
*it_inserter = 1;
*it_inserter = 2;
list <int>::iterator it = l.begin();
++ it;
//再构造一个通用型插入迭代器,在it处
insert_iterator <list<int>> it_inserter2(l, it);
*it_inserter2 = 3;
*it_inserter2 = 4;