iterator头文件中,除了为每个容器所定义的迭代器外,标准库中还额外定义了其它的迭代器类型,如下。
插入迭代器(inset iterator):该类迭代器被绑定到一个容器上,可用来向容器中插入元素。
流迭代器(stream iterator):该类迭代器被绑定到输入或者输出流上,可以用来遍历有关联的IO流。
反向迭代器(reverse iterator):这些迭代器向后移动而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器。(从头至尾的方向为前,从尾至头的方向为后。)
插入迭代器
插入迭代器是一种迭代器适配器,它接受一个容器类型,能够向容器中添加元素。当我们对插入迭代器进行赋值时,并不是将值赋给迭代器所指向的对象,而是将该值插入到容器中。
插入迭代器有以下三类型:
back_inserter:创建一个使用push_back的迭代器类型;
front_inserter:创建一个使用push_front的迭代器类型;
inserter:创建一个使用insert的迭代器类型;该迭代器在绑定容器的时候要接受第二个参数,这个参数必须是指向给定容器的迭代器,元素将插入到给定迭代器所表示的元素之前。
理解插入迭代器的执行过程是非常重要的,下面将用代码来解释和演示插入迭代器的三种类型。
inserter迭代器的执行过程
vector<int> ivec = {1, 3, 5, 7, 9};
auto it = insert(ivec, ++ivec.begin()); // 指向3;
*it = 2;
*it = 4;
for(const auto &elem : ivec)
cout << elem << " ";
// 输出为1 2 4 3 5 7 9
这个过程可能有些难以理解,不是说插在当前值前面吗?我们知道,当对容器进行插入,删除等操作时,原来的指针、迭代器会失效,因为它们指向的是原来的位置,而不是原来的值。我们可以在第三行在3前面插入2,迭代器原本指向的位置的值右3变成了2,第四行应该在2前面插入4,为什么仍然在3前面插入4呢?
所以说,我们要了解inserter迭代器执行的过程。
*it = 2; // 执行该语句时,可以将过程等效为下面的过程
it = ivec.insert(it, 2); // 在it的位置前插入2,it指向2
++it; // it指向原来的元素,即3,这样再插入4的时候就是在3前面插入了
back_inserter迭代器的执行过程
vector<int> ivec = {1, 3, 5, 7, 9};
auto bt = back_inserter(ivec);
*bt = 11;
*bt = 13
for(const auto &elem : ivec)
cout << elem << " ";
// 输出为1, 3, 5, 7, 9, 11, 13
我们该如何理解第三行和第四行的句子呢?上面说了,back_insertor迭代器使用push_back,结果相当于执行了ivec.push_back()函数。
但是它的执行过程是什么样子的呢?其实无论你将back_inserter迭代器绑定到容器那个迭代器上,它都会指向ivec.end(),然后对其进行’inserter’操作即可。即该迭代器永远指向最后一个元素的下一个位置。
front_inserter迭代器的执行过程
vector<int> ivec = {1, 3, 5, 7, 9};
auto ft = front_inserter(ivec);
*ft = 11;
*ft = 13
for(const auto &elem : ivec)
cout << elem << " ";
// 输出为13 11 1 3 5 7 9
同back_inserter,结果相当于执行了ivec.push_front()函数。
但是它的执行过程是什么样子的呢?其实无论你将front_inserter迭代器绑定到容器那个迭代器上,它都会指向ivec.begin(),然后对其进行’inserter’操作即可。即该迭代器永远指向第一个元素。
注意:
只有在容器支持push_back的情况下才能使用back_inserter;同理,只有在容器支持push_front的情况下才能使用front_inserter。
对于插入迭代器it, *it, ++it, --it这些操作虽然存在,但是不会对it造成任何的变化。
再来一段代码,来看插入迭代器有什么作用
list<int> ilist = {1, 2, 3, 4, 5, 6, 7};
list<int> ilist1, ilist2, ilist3;
copy(ilist.begin(), ilist.end(), front_inserter(ilist1));
copy(ilist.begin(), ilist.end(), back_inserter(ilist2));
copy(ilist.begin(), ilist.end(), inserter(ilist3, ilist.begin()));
for(const auto &elem : ilist1)
cout << elem << " ";
cout << endl;
// 7 6 5 4 3 2 1
fot(const auto &elem : ilist2)
cout << elem << " ";
cout << endl;
// 1 2 3 4 5 6 7
for(const auto &elem : ilist3)
cout << elem << " ";
cout << endl;
// 1 2 3 4 5 6 7
iostream迭代器
虽然标准输入输出并不是标准库容器,但是标准库定义了istream_iterator迭代器和ostream_iterator迭代器,有了这些迭代器,我们就可以用泛型算法来对输入输出流进行操作。
istream_iterator
当我们创建一个输入流迭代器的时候,我们要么对其进行一个对象的绑定,要么就对其进行默认初始化,使其能够当做一个尾后值迭代器来使用。
初始化
istream_iterator<int> int_it(cin); // 从cin中读取int
istream_iterator<int> int_eof; // 默认初始化,充当尾后值
istream in("EDG NB"); // 输入流对象
istream_iterator<string> str_it(in); // 绑定对象in
用法:
istream_iterator<int> int_it(cin); // 从cin中读取int
istream_iterator<int> int_eof; // 默认初始化,充当尾后值
while(int_it != int_eof) // 当有正确的数据可供读取时
ivec.push_back(*int_it++);
我们介绍过,定义输入输出流迭代器的目的便是让泛型算法能够对输入输出流进行操作,下面的代码将体现istream_iterator更有用的用途
vector<int> ivec(int_it, int_eof); //直接用输入流迭代器来初始化标准库容器
cout << accumulate(int_it, int_eof, 0) << 0; // 用算法操作输入流迭代器
ostream_iterator迭代器
ostream_iterator的操作
ostream_iterator<T> out(os); // 将类型T的值写入到输出流os中
ostream_iterator<T> out(os, d); // 将类型T的值写入到输出流os中,每个值的后面都跟着一个d。
// d是一个字符串,且该字符串是C风格类型的字符串,要么是一个字符串常量,要么是一个以空字符结尾的字符型数组
out = val; // 用<<流运算符将val写入out所绑定的ostream对象中,val必须与out可写的类型所兼容
*out, ++out, --out // 这些操作存在,但是没什么意义,out没有改变
ostream的用法
vector<int> ivec{1, 2, 3, 4, 5, 6 ,7};
ostream_itreator<int> int_ot(cout, " "); // 绑定到标准输出
for(const auto& elem : ivec)
// 虽然下面的*, ++运算符无意义,但是可读性增强了
// 等价语句:int_ot = elem;
*int_ot++ = elem; // 将elem输出了
cout << endl;
当然,我们定义输入输出流迭代器,那么肯定是要用泛型算法对其进行操作的,那么如何进行呢?
copy(ivec.begin(), ivec.end(), int_ot);
cout << endl;
这要比循环更简单一些。
反向迭代器
反向迭代器,顾名思义,从队尾元素向队首元素进行迭代。对于反向迭代器,递增(++)和递减(–)运算符的含义将会颠倒,递增一个反向迭代器会将其移动到前一个元素,递减一个反向迭代器会将其移动到后一个元素。
除了forward_list容器外,其它容器都具有反向迭代器。
rbegin, crbegin指向队尾元素。
rend, crend指向队首元素的前一个位置。
// 此段代码为从后向前打印容器元素
vector<int> ivec = {1, 2, 3, 4 ,5 ,6, 7};
for(auto irt = ivec.crbegin(); irt != ivec.crend(); ++irt)
cout << *irt << " "
// 7 6 5 4 3 2 1
当然,我们知道,STL便是泛型编程,迭代器便是为了泛型编程中的泛型算法服务的,那么这个反向迭代器肯定可以运用在泛型算法中。
sort(ivec,begin(), ivec.end()); // 将ivec按照升序排列。
sort(ivec,rbegin(), ivec.rend()); // 将ivec按照降序排列
// 假如我们有一堆英语单词,他们用逗号分隔,我们需要第一个英语单词,那么我们可以进行如下的操作
string str("EDG,NB,EDG,WINNER");
auto it = find(str.cbegin(), str.cend(), ',');
copy(str.begin(), it, int_ot); // 输出EGD
假如我们需要最后一个英语单词呢?这里我们就可以用到反向迭代器了。
string str("EDG,NB,EDG,WINNER");
auto it = find(str.crbegin(), str.crend(), ',');
copy(str.rbegin(), it, int_ot); // 输出RENNIW
那么为什么输出的是RENNIW而不是WINNER呢?因为我们用的是反向迭代器,因此copy会反向处理我们的字符串。此时,我们可以调用reverse_interator的成员函数base(),此成员函数会返回其对应的普通迭代器类型。
string str("EDG,NB,EDG,WINNER");
auto it = find(str.crbegin(), str.crend(), ',');
copy(it.base(), str.end(), int_ot); // 输出WINNER
注意:
反向迭代器的目的是表示元素范围,而这些范围是不对称的,这导致一个重要的结果:当我们从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的值。