泛型3(迭代器)
除了为每个容器定义的迭代器之外,标准库在头文件 iterator 中还定义了额外几种迭代器。这些迭代器包括以下几种:
插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素
流迭代器:这些迭代器被绑定到输入或输出流上,可用来变量所有管理的 IO 流
反向迭代器:这些迭代器向后而不是向前移动。除了 forward_list 之外的标准库容器都有反向迭代器
移动迭代器:这些专用的迭代器不是拷贝其中的元素,而是移动它们。
插入迭代器(back_inserter/front_inserter/inserter):
1 #include <iostream> 2 #include <algorithm> 3 #include <iterator> 4 #include <list> 5 using namespace std; 6 7 int main(void){ 8 list<int> lst = {1, 2, 3, 4, 5}; 9 list<int> lst1, lst2, lst3;//空list 10 11 copy(lst.cbegin(), lst.cend(), front_inserter(lst1)); 12 for(const auto &indx : lst1){ 13 cout << indx << " "; 14 } 15 cout << endl;//5 4 3 2 1 16 17 copy(lst.cbegin(), lst.cend(), back_inserter(lst2)); 18 for(const auto &indx : lst2){ 19 cout << indx << " "; 20 } 21 cout << endl;//1 2 3 4 5 22 23 copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin())); 24 for(const auto &indx : lst3){ 25 cout << indx << " "; 26 } 27 cout << endl;//1 2 3 4 5 28 29 return 0; 30 }
注意:它们是迭代器适配器。back_inserter、front_inserter 和 inserter 分别会调用 push_back、push_front 和 insert,因此只有在容器支持 push_back 的情况下我们才可以使用 back_inserter,容器支持 push_front 的情况下我们才可以使用 front_inserter,容器支持 insert 的情况下才可以调用 inserter
在插入过程中,back_inserter 总是将新元素插入当前容器尾后迭代器之前,front_inserter 总是将新元素插入当前迭代器首元素之前的位置。而当调用 inserter(c, iter) 时,我们得到一个迭代器,接下来使用它时,会将元素插入到 iter 原来所指向的元素之前的位置。即,如果 it 是由 inserter 生成的迭代器,则:
*it = val;
其效果于下下面代码一样:
it = c.insert(it, val);// it 指向新加入的元素
++it;// 递增 it 使他指向原来的元素
iostream 迭代器:
虽然 iostream 不是迭代器,但标准库定义了可以用于这些 IO 类型对象的迭代器。istream_iterator 读取输入流,ostream_iterator 向一个输出流写数据。这些迭代器将它们对于的流当作一个特定类型的元素序列来处理。通过使用流迭代器,我们可以用泛型算法从流对象读取数据以及向其写入数据。
istream_iterator 操作:
当创建一个流迭代器时,必须指定迭代器将要读写的对象类型。一个 istream_iterator 使用 >> 来读取流。因此,istream_iterator 要读取的类型必须定义了输入运算符。反之亦然(我们可以为任何定义了输入运算符的类型创建 istream_iterator 对象)。当创建一个 istream_iterator 时,我们可以将它绑定到一个流。也可以默认初始化迭代器,这样就创建了一个可以当作尾后值使用的迭代器:
1 istream_iterator<int> int_it(cin);//从cin读取int 2 istream_iterator<int> int_eof;//尾后迭代器 3 4 ifstream in("afile"); 5 istream_iterator<string> str_it(in);//从afile读取字符串
用 istream_iterator 从标准输入读取数据并存入一个 vector:
1 vector<int> v; 2 istream_iterator<int> in_iter(cin);//从cin读取int 3 istream_iterator<int> eof;//istream尾后迭代器 4 while(in_iter != eof){//当有数据可读时 5 //后置递增运算读取流,返回迭代器的旧值 6 v.push_back(*in_iter++);//解引用迭代器,获得从流读取的前一个值 7 } 8 for(const auto &indx : v){ 9 cout << indx << " "; 10 } 11 cout << endl;
注意:对于一个绑定到流的迭代器,一旦其关联的流遇到文件尾或遇到 IO 错误,迭代器就与尾后迭代器相等
用流迭代器直接构造容器:
1 istream_iterator<int> in(cin); 2 istream_iterator<int> ed; 3 vector<int> vec(in, ed); 4 for(const auto &indx : vec){ 5 cout << indx << " "; 6 } 7 cout << endl;
注意:这个构造函数从 cin 中读取数据,直至遇到文件尾或者遇到一个不是 int 的数据为止
使用算法操作流迭代器:
由于算法使用迭代器操作来处理数据,而流迭代器又至少支持某些迭代器的操作,因此我们至少可以使用某些算法类操作流迭代器。如,对一对 istream_iterator 来调用 accumulate:
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 #include <iterator> 5 using namespace std; 6 7 int main(void){ 8 istream_iterator<int> in(cin), eof; 9 cout << accumulate(in, eof, 0) << endl; 10 11 return 0; 12 }
istream_iterator 允许使用懒惰求值:
当我们将一个 istream_iterator 绑定到一个流时,标准库并不能保证迭代器立即从流读取数据。具体实现可以推迟从流中读取数据,直到我们使用迭代器时才真正读取。标准库中的实现保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成。对于大多数程序来说,立即读取还是推迟读取没什么差别。但是,如果我们创建了一个 istream_iterator,没有使用就销毁了,或者我们正在从两个不同的对象同步读取一个流,那么何时读取可能就很重要了~
ostream_iterator 操作:
我们可以对任何具有输出运算符的类定义 ostream_iterator。当创建一个 ostream_iterator 时,我们可以提供(可选的)第二参数,它是一个字符串,在输出每个元素后都会打印此字符串。此字符串必须是一个 c 风格字符串。必须将 ostream_iterator 绑定到一个指定的流,不允许空的或表示尾后位置的 ostream_iterator。
用 ostream_iterator 来输出值的序列:
1 #include <iostream> 2 #include <iterator> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 vector<int> vec = {1, 2, 3, 4, 45, 5, 6}; 8 9 ostream_iterator<int> out_iter(cout, " "); 10 for(const auto &e : vec){ 11 *out_iter++ = e;//赋值语句实际上将元素写到cout 12 } 13 cout << endl;//1 2 3 4 45 5 6 14 15 return 0; 16 }
值得注意的是,当我们向 out_iter 赋值时,可以忽略解引用和递增运算。即,上面代码可以写成下面的形式:
1 #include <iostream> 2 #include <iterator> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 vector<int> vec = {1, 2, 3, 4, 45, 5, 6}; 8 9 ostream_iterator<int> out_iter(cout, " "); 10 for(const auto &e : vec){ 11 out_iter = e;//赋值语句将元素写到cout 12 } 13 cout << endl;//1 2 3 4 45 5 6 14 15 return 0; 16 }
但是对于 istream_iterator 是不能这样操作的~
运算符 * 和 ++ 实际上对 ostream_iterator 对象不做任何事情,因此忽略它们对我们的程序没有任何影响。但是最好使用第一种写法,这此种写法中,ostream_iterator 与其它迭代器的使用保持一致。更容易修改,可读性也更好~
通过调用 copy 来打印容器中的元素:
1 #include <iostream> 2 #include <iterator> 3 #include <array> 4 using namespace std; 5 6 int main(void){ 7 array<int, 5> a = {1, 2, 3, 4, 5}; 8 ostream_iterator<int> out(cout, " "); 9 copy(a.begin(), a.end(), out);//将a中元素拷贝到out,实际上就是将a中元素写到cout 10 cout << endl;//1 2 3 4 5 11 12 return 0; 13 }
使用流迭代器处理类类型:
我们可以为任何定义了输入运算符的类型创建 istream_iterator 对象。类似的,只要类型有输出运算符我们就可以为其定义 ostream_iterator:
1 #include <iostream> 2 #include <list> 3 #include <iterator> 4 using namespace std; 5 6 class gel{ 7 friend istream& operator>>(istream&, gel&);//将>>重载为gel的友元函数 8 friend ostream& operator<<(ostream&, const gel&);//将<<重载为gel的友元函数 9 10 private: 11 int x, y, z; 12 13 public: 14 gel(int a, int b, int c) : x(a), y(b), z(c) {} 15 gel(int a, int b) : gel(a, b, 0) {} 16 gel(int a) : gel(a, 0, 0) {} 17 gel() : gel(0, 0, 0) {} 18 ~gel(){} 19 }; 20 21 istream& operator>>(istream &is, gel &it) { 22 is >> it.x >> it.y >> it.z; 23 return is; 24 } 25 26 ostream& operator<<(ostream &os, const gel &it){ 27 os << it.x << " " << it.y << " " << it.z; 28 return os; 29 } 30 31 int main(void){ 32 istream_iterator<gel> is(cin), eof; 33 ostream_iterator<gel> os(cout, "\n"); 34 list<gel> l; 35 36 while(is != eof){ 37 l.push_back(*is++);//解引用迭代器,获得从流读取的前一个值 38 } 39 40 for(const auto &indx : l) { 41 *os++ = indx;//将indx拷贝赋值给os,即将indx写到cout 42 } 43 cout << endl; 44 45 return 0; 46 }
注意:只有重载了 >> / << 的类类型才能创建 istream_iterator 或 ostream_iterator 对象
用流迭代器读写文件:
1 #include <iostream> 2 #include <fstream> 3 #include <vector> 4 #include <iterator> 5 #include <algorithm> 6 using namespace std; 7 8 const char *filename = "D:\\code\\c++\\ac29.txt"; 9 10 int main(void){ 11 12 // 从屏幕读到vec中 13 istream_iterator<string> in(cin), in_eof; 14 vector<string> vec(in, in_eof); 15 16 //从vec写到文件ac29.txt中 17 ofstream out_file(filename);//用只写的方式打开文件 18 ostream_iterator<string> out_file_it(out_file, " "); 19 for(const auto &indx : vec){ 20 *out_file_it++ = indx; 21 } 22 23 out_file.close();//后面还要对filename文件进行操作,所以一定要关闭out_file文件流 24 25 //从文件ac29.txt中读到ved中 26 ifstream in_file(filename);//用只读的方式打开文件 27 istream_iterator<string> in_file_it(in_file), in_file_eof; 28 vector<string> ved(in_file_it, in_file_eof); 29 30 in_file.close();//养成用完就关闭文件流的好习惯~ 31 32 //比较vec和ved中的内容是否相同 33 cout << equal(vec.cbegin(), vec.cend(), ved.cbegin()) << endl; 34 return 0; 35 }