除了常规的迭代器,C++还提供额外3种迭代器,这3种迭代器都定义在<iterator>头文件中。
- 插入迭代器(insert iterator): 用于和容器绑定,实现向容器写入数据
- iostream迭代器(iostream iterator): 和IO流绑定
- 反向迭代器(reverse iterator): 实现反序遍历
1.插入迭代器
插入迭代器采用的适配器模式,接收一个容器作为构造函数参数,接口定义又符合迭代器的标准,从而实现利用迭代器动态插入数据。根据插入的位置不同,插入迭代器分为3种
- back_inserter,使用push_back写入容器
- front_inserter,使用push_front写入容器
- inserter,使用insert插入指定位置,构造器需要一个额外的入参用于指定位置
1.back_inserter
back_inserter接收一个容器对象,实现用容器的push_back向容器里写入数据。
void test_back_inserter() {
vector<string> v1;
v1.push_back("1");
v1.push_back("2");
v1.push_back("3");
vector<string> v2;
std::copy(v1.begin(), v1.end(), std::back_inserter(v2));
cout << "v2: " << to_str(v2,",") << std::endl; // print: v2: 1,2,3
std::copy(v1.rbegin(), v1.rend(), std::back_inserter(v2));
cout << "v2: " << to_str(v2, ",") << std::endl; // print: v2: 1,2,3,3,2,1
}
2.front_inserter
和back_inserter不同,front_inserter通过使用push_front实现插入。使用前面示例的代码,vector<string>不支持push_front,我们将vector<string>改为list<string>。具体代码如下
void test_front_inserter() {
vector<string> v1;
v1.push_back("1");
v1.push_back("2");
v1.push_back("3");
list<string> v2;
std::copy(v1.begin(), v1.end(), std::front_inserter(v2));
cout << "v2: " << to_str(v2, ",") << std::endl; // print v2: 3,2,1,
std::copy(v1.rbegin(), v1.rend(), std::front_inserter(v2));
cout << "v2: " << to_str(v2, ",") << std::endl; // v2: 1,2,3,3,2,1,
}
3.inserter
接收一个容器,以及一个位置参数用于指定位置插入。比较特别的是,插入一个元素之后,下一次插入的位置是上一次插入位置的下一个。
void test_inserter() {
vector<string> v1;
v1.push_back("a");
v1.push_back("b");
v1.push_back("c");
v1.push_back("d");
vector<string>::iterator it = std::find(v1.begin(), v1.end(), string("c")); // 获取指向"c"的迭代器
vector<string> v2;
v2.push_back("1");
v2.push_back("2");
v2.push_back("3");
std::copy(v2.begin(), v2.end(), inserter(v1, it)); // 在"c"的位置插入v2的内容,"c"后移,v2的内容按原先的迭代器输出
cout << "v2: " << to_str(v2,",") << std::endl; // print v2: 1,2,3
cout << "v1: " << to_str(v1,",") << std::endl; // print v1: a,b,1,2,3,c,d
}
front_inserter会导致要写入的内容倒序插入到容器前面,使用inserter + 迭代器的起始位置,我们可以做到将元素保留原有顺序写入到容器其实位置。如果要用inserter模拟front_inserter的效果,需要将插入位置选为begin,并倒序迭代要插入的元素。
void test_inserter_as_front_inserter() {
list<string> v1;
v1.push_back("a");
v1.push_back("b");
v1.push_back("c");
v1.push_back("d");
vector<string> v2;
v2.push_back("1");
v2.push_back("2");
v2.push_back("3");
std::copy(v2.rbegin(), v2.rend(), inserter(v1, v1.begin())); // print: v1: 3,2,1,a,b,c,d,
//std::copy(v2.begin(), v2.end(), std::front_inserter(v1)); // print: v1: 3,2,1,a,b,c,d,
cout << "v2: " << to_str(v2, ",") << std::endl;
cout << "v1: " << to_str(v1, ",") << std::endl;
}
2. iostream迭代器
标准库提供了istream_iterator用于读取输入流,ostream_iterator用于写输出流。适配iostream成迭代器,从而支持泛型算法的一些功能。
流迭代只提供了最基本的功能,赋值、自增、解引用,对于istream_iterator额外提供了一个相等(==)比较的能力。
1.istream_iterator
istream_iterator通过默认构造函数的返回值提示流已经迭代完成。我们来可以一个简单的例子,用istream_iterator从istringstream读取字符串保存到vector中
#include <iostream>
#include <iterator>
#include <sstream>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;
string to_str(const vector<string>& ivec, const char* deli) {
std::ostringstream os;
std::copy(ivec.begin(), ivec.end() - 1, std::ostream_iterator<string>(os, deli));
os << ivec.back();
return os.str();
}
void test_istream_iterator() {
std::istringstream iss("the quick red fox jumps over the slow red turtle");
std::istream_iterator<string> it(iss);
std::istream_iterator<string> eof;
vector<string> v1;
std::copy(it, eof, std::back_inserter(v1)); // 通过eof限定迭代器结束位置
cout << "v1: " << to_str(v1, ",") << endl;
}
这个例子中用it、eof指定的范围插入到vector中,其实更高效的写法是直接传递给vector的构造函数
void test_vector() {
std::istringstream iss("the quick red fox jumps over the slow red turtle");
string s1;
std::istream_iterator<string> it(iss);
std::istream_iterator<string> eof;
vector<string> v1(it, eof);
cout << "v1: " << to_str(v1, ",") << endl;
}
2. ostream_iterator
ostream_iterator将赋值给迭代器的内容,输出到绑定的输出流中。我们用cout初始化ostream_iterator,每次输出后接一个分隔符"\n"。
void test_ostream_iterator() {
std::istringstream iss("the quick red fox jumps over the slow red turtle");
std::istream_iterator<string> it(iss);
std::istream_iterator<string> eof;
std::ostream_iterator<string> it_out(cout, "\n");
while (it != eof) {
*it_out++ = *it++;
}
}
3.反向迭代器
类似于容器提供的begin、end函数,很多容器还提供rbegin、rend函数,rbegin之前容器的最后一个元素,rend指向容器第一个元素的前一个位置。
我们来看一个简单的示例,通过反向迭代器倒序输出vector中的数据
void test_reverse_iterator() {
std::vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
cout << "v1: " << to_str(v1, ",") << endl; // print v1: 0,1,2,3,4,5,6,7,8,9
vector<int>::reverse_iterator rit;
for (rit = v1.rbegin(); rit != v1.rend(); rit++) {
cout << *rit << endl; // print 9 8 7 ...
}
}
之前我们学过sort函数,用于按字典顺序排列元素,如果我们想倒序排列,一种办法是提供自定义的谓词函数,另外一种就是传入反向迭代器。
bool compare(string i1, string i2) {
return i2 < i1;
}
void test_reverse_sort() {
vector<string> v1;
v1.push_back("1");
v1.push_back("3");
v1.push_back("2");
//std::sort(v1.begin(), v1.end()); // print: v1: 1,2,3
std::sort(v1.rbegin(), v1.rend()); // print: v1: 3,2,1
//std::sort(v1.begin(), v1.end(), compare); // print: v1: 3,2,1
cout << "v1: " << to_str(v1,",") << endl;
}
标准库提供的容器都支持正序、反序迭代,iostream迭代器不支持倒序迭代。
1. 正反序迭代器转换
当我们使用反向迭代器的时候,迭代器将元素逆向输出。我们举个例子来说明这个问题
void test_reverse() {
string s1("123,456,789");
string::iterator it = find(s1.begin(), s1.end(), ',');
cout << "正序,第1个,之前的内容: " << string(s1.begin(), it) << endl; // print 123
string::reverse_iterator rit = find(s1.rbegin(), s1.rend(), ',');
cout << "倒序,第1个,之后的内容: " << string(s1.rbegin(), rit) << endl; // print 987
cout << "倒序,第1个,之后的内容正序输出: " << string(rit.base(), s1.end()) << endl; // print 789
}
第一个输出,用于显示正序第一个","之前的内容,输出123并没有什么问题。第二个输出,显示最后一个","之后的内容,并没有按s1里原先的顺序显示789,而是显示为987。反序迭代器提供了一个base函数,用于将反序迭代器转换为正序迭代器。第3个输出,我们将rit通过base函数转换,输出到end位置的内容,显示内容是我们期望的789。
4.迭代器分类
不同的迭代器支持的操作不同,比如ostream_iterator只支持赋值、自增、解引用,而vector的迭代器还支持自减、关系和算术运算。根据泛型算法对迭代器的要求,可以将迭代器分为5类
1.输入迭代器
支持这个层次的泛型算法有find、accumulate等,标准库的istream_iterator就是输入迭代器的一种。
2.输出迭代器
输出迭代器一般作为泛型算法的第3个参数,作为输出的目标位置。标准库ostream_iterator就是输出迭代器的一种。
3.前向迭代器
前向迭代器只能向一个方向遍历,支持输入迭代器和输出迭代器的所有操作。同时支持对一个元素的多次读写。标准库replace函数就至少需要这个层次的迭代器。
4.双向迭代器
支持前向迭代器的所有操作,额外支持自减(--)的能力。标准库reverse函数至少需要这个层次的迭代器。标准库容器提供的迭代器至少都达到双向迭代器的要求。
5.随机访问迭代器
提供常量时间内访问任意位置的功能,支持双向迭代器的功能为,额外提供以下操作
- 关系操作符(<、<=、>、>=),比较两个迭代器的相对位置
- 支持算术运算,支持迭代器和整型数值n的加减法操作
- 支持两个迭代器的减法操作,得到之间的距离
- 下标操作符iter[n],是*(iter+n)的同义词
泛型算法里的sort就需要这个层次的迭代器。vector、deque、string、数组的指针的迭代器都是随机访问迭代器。map、set、list都提供双向迭代器。istream_iterator是输入迭代器,而ostream_iterator是输出迭代器。5种迭代器,出来输出迭代器,剩下的4种形成一组继承结构,在要求较低层级的迭代器的算法中,可以传入更高层级的迭代器,比如find要求输入迭代器,我们可以传入一个输入迭代器,也可以传入一个前向迭代器。