除了为每一个容器定义的迭代器以外,标准库在头文件iterator中还定义了额外几种迭代器。这些迭代器包括如下几种。ios
插入器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。当咱们经过一个迭代器进行赋值时,该迭代器调用容器操做来向给定容器的指定位置插入一个元素。下表列出了这种迭代器支持的操做。算法
插入迭代器操做 |
it=t 在it指定的当前位置插入值t。假定c是it绑定的容器,依赖于插入迭代器的不一样种类,此赋值分别调用c.push_back(t)、c.push_front(t)或c.insert(t,p),其中p为传递给inserter的迭 代器位置数组 *it,++it,it++ 这些操做虽然存在,但不会对it作任何事情。每一个操做都返回it函数 |
插入迭代器有三种类型,差别在于元素插入的位置:spa
注意:只有在容器支持push_front的状况下,咱们才可使用front_inserter。相似的,只有在容器支持push_back的状况下,咱们才能使用back_inserter指针
理解插入迭代器的工做过程是很重要的:当调用inserter(c,iter)时,咱们获得一个迭代器,接下来使用它时,会将元素插入到iter原来所指的位置以前的位置。即,若是it是由inserter生成的迭代器,则下面这样的赋值语句code
*it=val;对象
其效果与下面代码同样blog
it=c.insert(it,val);//it指向新加入的元素ci
++it; //递增it使它指向原来的元素
front_inserter生成的迭代器的行为与inserter生成的迭代器彻底不同。当咱们使用front_inserter时,元素老是插入到容器第一个元素以前,即便咱们传递给inserter的位置原来指向第一个元素,只要咱们在此元素以前插入一个新元素,此元素就再也不是容器的首元素了:
list<int> lst={1,2,3,4};
list<int> lst2,lst3; //空list
//拷贝完成以后,lst2包含4 3 2 1
copy(lst.begin(),lst.end(),front_inserter(lst2));
//拷贝完成以后lst3包含1 2 3 4
copy(lst.begin(),lst.end(),inserter(lst3,lst.begin()));
当调用front_inserter(c)时,咱们获得一个插入迭代器,接下来会调用push_front.当每一个元素被插入到容器c中时,它变为c的新的首元素。所以,front_inserter生成的迭代器会将插入的元素序列的顺序颠倒过来,而inserter和back_inserter则不会。
虽然iostream类型不是容器,但标准库定义了用于这些IO类型对象的迭代器。istream_iterator读取输入流,ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流看成一个特定类型的元素序列来处理。经过使用流迭代器,咱们能够用泛型算法从流对象读取数据以及向其写入数据。
istream_iterator操做
当建立一个流迭代器时,必须指定迭代器将要读写的对象类型。一个istream_iterator使用>>来读取流。所以,istream_iterator要读取的类型必须定义了输入运算符。当建立一个istream_iterator时,咱们能够将它绑定到一个流。固然,咱们还能够默认初始化迭代器,这样就建立了一个能够看成尾后值使用的迭代器。
istream_iterator<int> int_it(cin); //从cin读取int
istream_iterator<int> int_eof; //尾后迭代器
ifstream in("afile");
istream_iterator<string> str_in(in); //从“afile读取字符串
下面是一个用istream_iterator从标准输入流读取数据,存入一个vector的例子:
istream_iterator<int> in_iter(cin); //从cin读取int
istream_iterator<int> eof; //istream尾后迭代器
while(in_iter!=eof)
//后置递增运算读取流,返回迭代器的旧值
//解引用迭代器,得到从流读取的前一个值
vec.push_back(*in_iter++);
此循环从cin读取int值,保存在vec中。在每一个循环步中,循环体代码检查in_iter是否等于eof。eof被定义为空istream_iterator,从而能够看成尾后迭代器来使用。对于一个绑定到流的迭代器,一旦其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相等。
咱们能够将程序重写为以下形式,这体现了istream_iterator更有用的地方:
istream_iterator<int> in_iter(cin),eof; //从cin读取int
vector<int> vec(in_iter,eof); //从迭代器范围构造vec
本例中咱们使用了一对表示范围的迭代器来构造vec,这两个迭代器是istream_iterator,这意味着元素范围是经过从关联的流中读取数据得到的。这个构造函数从cin读取数据,直至遇到文件尾或者遇到一个不是int的数据为止。从流中读取的数据被用来构造vec。
istream_iterator操做 |
istream_iterator<T> in(is); in从输入流is读取类型为T的值 istream_iterator<T> end; 读取类型为T的值的istream_iterator迭代器,表所尾后位置 in1==in2 in1和in2必须读取相同类型。若是它们都是尾后迭代器,或绑定到相同的输入,则两个相等 in1!=in2 *in 返回从流中读取数据 in->mem 与(*in).mem的含义相同 ++in,in++ 使用元素类型所定义的>>运算符从输入流中读取下一个值。与以往同样,前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值 |
使用算法操做流迭代器
因为算法使用迭代器操做来处理数据,而流迭代器又至少支持某种迭代器操做,所以咱们至少能够用某些算法来操做流迭代器。下面是一个例子,咱们能够用一对istream_iterator来调用accumulate:
istream_iterator<int> in(cin),eof;
cout<<accumulatre(in,eof,0)<<endl;
此调用会计算出从标准输入读取的值的和。若是输入为:
1 3 7 9 9
输出为29
istream_iterator容许使用懒惰求值
当咱们将一个istream_iterator绑定到一个流时,标准库并不保证迭代器当即从流读取数据。具体实现能够推迟从中读取数据,直到咱们使用迭代器时才真正读取。标准库中的实现所保证的是,在咱们第一次解引用迭代器以前,从流中读取数据的操做已经完成了。对于大多数程序来讲,当即读取仍是推迟读取并无什么差异。可是,若是咱们建立了一个istream_iterator,没有使用就销毁了,或者咱们正在从两个不一样的对象同步读同一个流,那么什么时候读取可能就很重要了。
ostream_iterator操做
咱们能够对任何输出运算符(<<运算符)的类型定义ostream_iterator。当建立一个ostream_iterator时,咱们能够提供(可选的)第二参数,它是一个字符串,在输出每一个元素后都会打印此字符串。此字符串必须是一个C风格字符串(即,一个字符串字面值或者一个指向以空字符结尾的字符数组的指针)。必须将ostream_iterator绑定到一个指定的流。不容许空的或表示尾后位置的ostream_iterator。
ostream_iterator操做 |
ostream_iterator<T> out(os); out将类型为T的值写到输出流os中 ostream_iterator<T> out(os,d); out将类型为T的值写到输出流os中,每一个值后面都输出一个d。d指向一个空字符串结尾的字符数组 out=val 用<<运算符将val写入到out所绑定的ostream中。val的类型必须与out可写的类型兼容 *out,++out,out++ 这些运算符是存在的,但不对out作任何事情。每一个运算符都返回out |
咱们可使用ostream_iterator来输出值的序列:
ostream_iterator<int> out_iter(cout," ");
for(auto e:vec)
*out_iter++=e; //赋值语句实际上将元素写到cout
cout<<endl;
此程序将vec中的每一个元素写到cout,每一个元素加一个空格,每次向out_iter赋值时,写操做就会被提交。
值得注意的是,当咱们向out_iter赋值时,能够忽略解引用和递增运算。即,循环能够重写成下面的样子:
for(auto e:vec)
out_iter=e;//赋值语句将元素写道cout
cout<<end;
运算符*和++实际上对ostream_iterator对象不作任何事情,所以忽略它们对咱们的程序没有任何影响。可是,推荐第一种形式。在这种写法中,流迭代器的使用与其余迭代器的使用保存一致。若是想将此循环改成操做其余迭代器类型,修改起来很是容易。并且,对于读者来讲,此循环的行为也更为清晰。
能够经过调用copy来打印vec中的元素,这比编写循环更为简单:
copy(vec.begin(),vec.end(),out_iter);
cout<<endl;
使用流迭代器处理类类型
咱们能够为任何定义了输入运算符(>>)的类型建立istream_iterator对象。相似的,只要类型有输出运算符(<<),咱们就能够为其定义ostream_iterator。因为Sales_item既有输入运算符也有输出运算符,所以可使用IO迭代器。例如:
istream_iterator<Sales_item> item_iter(cin),eof; ostream_iterator<Sales_item> out_iter(cout,"\n"); Sales_item sum=*item_iter++; while(item_iter!=eof) { if(item_iter->isbn()==sum.isbn()) sum+=*item_iter++; else { out_iter=sum; sum=*item_iter++; } } out_iter=sum;
此程序使用item_iter从cin读取Sales_item交易记录,并将和写入cout,每一个结果后面都跟一个换行符。定义了本身的迭代器后,咱们就能够用item_iter读取第一条交易记录,用它的值来初始化sum.
反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。对于反向迭代器,递增(以及递减)操做的含义会颠倒过来。递增一个反向迭代器(++it)会移动到前一个元素;递减一迭代器(--it)会移动到下一个元素。
除了forward_list以外,其余容器都支持反向迭代器。咱们能够经过调用rbegin、rcend、crbegin和crend成员函数来得到反向迭代器。这些成员函数返回指向容器尾元素和首元素以前一个位置的迭代器。与普通迭代器同样,反向迭代器也有const和非const版本。
下面的循环是一个使用反向迭代器的例子,它按逆序打印vec中的元素:
vector<int> vec={0,1,2,3,4,5,6,7,8,9};
//从尾元素到首元素的反向迭代器
for(auto r_iter=vec.crbegin;r_iter!=vec.crend();++r_iter)
cout<<*r_iter<<endl; //打印9,8,7,6,5,4,3,2,1,0
虽然颠倒递增和递减运算符的含义可能使人混淆,但这样作是咱们能够用算法透明地向前或向后处理容器。例如,能够经过向sort传递一对反向迭代器来将vector整理为递减序:
sort(vec.begin(),vec.end());
sort(vec.rbegin(),vec.rend());
反向迭代器须要递减运算符
咱们只能从既支持++也支持--的迭代器来定义反向迭代器。毕竟反向迭代器的目的是在序列中反向移动。出了forward_list以外,标准容器上的其余迭代器都既支持递增运算又支持递减运算。可是,流迭代器不支持递减运算,由于不可能在一个流中反向移动。所以,不可能从一个forward_list或一个流迭代器建立反向迭代器。
反向迭代器与其余迭代器间的关系
假定有一个名为line的string,保存着一个逗号分隔的单词列表,咱们但愿打印line中的第一个单词,使用find能够很容易地完成这一任务:
//在一个逗号分隔的列表中查找一个元素
auto comma=find(line.cbegin(),line.cend(),',');
cout<<string(line.cbegin(),comma)<<endl;
若是line中有逗号,那么comma将指向这个逗号;不然,它将等于line.cend().当咱们打印从line.cbegin()到comma之间的内容时,将打印到逗号为止的序列,或者打印整个string(若是其中不含逗号的话)。
若是但愿打印最后一个单词,能够改用反向迭代器:
//在一个逗号分隔的列表中查找最后一个元素
auto rcomma=find(line.crbegin(),line.crend(),',');
因为咱们将crbegin和crend传递给find,find将从line的最后一个字符开始向前搜索。当find完成后,若是line中有逗号,则rcomma指向最后一个逗号——即,它指向反向搜索中找到的第一个逗号。若是line中没有逗号,则rcomma指向line.crend()
但咱们试图打印找到的单词时,看起来下面的代码是显然的方法
//错误:将逆序输出单词的字符
cout<<string(line.crbegin(),rcomma)<<endl;
但它会生成错误的输出结果。例如,若是咱们的输入是
FIRST,MIDOLE,LAST
则这条语句会打印TSAL!
问题所在:咱们使用的是反向迭代器,会反向出来string。所以,上述输出语句从crbegin开始反向打印line中内容。而咱们但愿按正常顺序打印从rcomma开始到line末尾间的字符。可是,咱们不能直接使用rcomma。由于它是一个反向迭代器,意味着它会反向朝着string的开始位置移动。须要作的是,将rcomma转换回一个普通迭代器,能在line中正向移动。咱们经过调用reverse_iterator的base成员函数来完成这一转换,此成员函数会返回其对应的普通迭代器:
//正确:获得一个正向迭代器,从逗号开始读取字符直到line末尾
cout<<string(rcomma.base(),line.cend())<<endl;
rcomma和rcomma.base()指向了不一样的元素,line.crbegin()和line.cend()也是如此。这些不一样保证了元素范围不管是正向处理仍是反向出来都是相同的。
从技术上讲,普通迭代器与反向迭代器的关系反映了左闭合区间的特征。关键点在于[line.crbegin(),rcomma)和[rcomma.base(),line.cend())指向line中相同的元素范围。