插入迭代器
插入迭代器是一种迭代器适配器,带有一个容器参数,并生成一个迭代器,用于在指定的容器中插入元素。通过插入迭代器赋值时,迭代器将会插入一个新的元素。C++语言提供了三种插入器,其差别在于插入元素的位置不同:
1)back_inserter,创建使用push_back实现插入的迭代器;
2)front_inserter,使用push_front实现插入;
3)inserter,使用insert实现插入操作。除了所关联的容器外,inserter还带有第二个实参:指向插入起始位置的迭代器。
front_inserter的操作类似于back_inserter:该函数将创建一个迭代器,调用它所关联的基础容器的push_front成员函数代替赋值操作。注意:只有当容器提供push_front操作时,才能使用front_inserter。在vector或其他没有push_front 运算的容器上使用front_inserter,将产生错误。
inserter将产生在指定位置实现插入的迭代器,inserter总是在它的迭代器参数所标明的位置前面插入新元素。看看下面的例子:
list<int> ilst, ilst2, ilst3; //empty lists
// after this loop ilst contains: 1 2 3 4
for (list<int>::value_type i = 0; i != 4; ++i)
ilst.push_front(i + 1);
// after copy ilst2 contains: 4 3 2 1
copy (ilst.begin(), ilst.end(), front_inserter(ilst2));
// after copy ilst3 contains: 1 2 3 4
copy (ilst.begin(), ilst.end(), inserter(ilst3, ilst3.begin()));
iostream 迭代器
虽然iostream类型不是容器,但标准库同样提供了在iostream对象上使用的迭代器:istream_iterator用于读取读入流,而 ostream_iterator用于写输出流。这些迭代器将它们所对应的流视为特定类型的元素序列。使用流迭代器时,可以用泛型算法从流对象中读数据(或将数据写到流对象中)。
istream_iterator<T> in(strm); |
创建从输入流strm中读取T类型对象的istream_iterator对象 |
istream_iterator<T> in; |
istream_iterator对象的超出末端迭代器 |
ostream_iterator<T> out(strm); |
创建将T类型的对象写到输出流strm的ostream_iterator对象 |
ostream_iterator<T> out(strm, delim); |
创建将T类型的对象写到输出流strm的ostream_iterator对象,在写入过程中使用delim作为元素的分隔符。delim是以空字符结束的字符数组 |
流迭代器只定义了最基本的迭代器操作:自增、解引用和赋值。此外,可比较两个istream迭代器是否相等(或不等)。而ostream迭代器则不提供比较运算。
it1 == it2 |
比较两个istream_iterator是否相等(不等)。迭代器读取的必须是 相同的类型。如果两个迭代器都是end值,则它们相等。对于两个都不 |
it1 != it2 |
指向流结束位置的迭代器,如果它们使用同一个输入流构造,则它们 相等。 |
*it |
返回从流中读取的值 |
it->mem |
是(*it).mem的同义词。返回从流中读取的对象的mem成员 |
++it |
通过使用元素类型提供的>>操作符从个输入流中读取下一个元素值, 使迭代器向前移动。通常,前缀版本使迭代器在流中向前移动,并返 回对加1后的迭代器的引用。 |
it++ |
而后缀版本使迭代器在流中向前移动后,返回原值。 |
流迭代器是类模板:任何已定义输入操作符(>>操作符)的类型都可以定义istream_iterator。类似地,任何已定义输出操作符(<<操作符)的类型也可以ostream_iterator。
istream_iterator使用举例:
#include <iostream> #include <vector> #include <iterator> using namespace std;
int main() { istream_iterator<int> in_iter(cin); istream_iterator<int> eof;
//vector<int> vec(in_iter, eof); //do the same work as following loop
vector<int> vec; while (in_iter != eof) vec.push_back(*in_iter++); vector<int>::const_iterator it = vec.begin(); for(; it != vec.end(); ++it) cout<<*it<<endl; return 0; } |
ostream_iterator使用举例:
#include <iostream> #include <iterator> using namespace std;
int main() { ostream_iterator<string> out_iter(cout, "\n"); istream_iterator<string> in_iter(cin), eof; while (in_iter != eof) *out_iter++ = *in_iter++; return 0; }
|
流迭代器的限制:
1)不可能从ostream_iterator对象读入,也不可能写到istream_iterator对象中;
2)一旦给ostream_iterator对象赋了一个值,写入就提交了。赋值后,没有办法再改变这个值。此外,ostream_iterator对象中每个不同的值都只能正好输出一次。
3)ostream_iterator没有->操作符。
与算法一起使用流迭代器,如下面的示例实现从标准输入读取一些数,然后将不重复的数写到标准输出:
#include <iostream> #include <vector> #include <iterator> #include <algorithm> using namespace std;
int main() { istream_iterator<int> in_it(cin), eof; vector<int> vec(in_it, eof); sort(vec.begin(), vec.end()); ostream_iterator<int> out_it(cout, " "); unique_copy(vec.begin(), vec.end(), out_it); return 0; }
|
反向迭代器
反向迭代器是一种反向遍历容器的迭代器。也就是,从最后一个元素到第一个元素遍历容器。反向迭代器将自增(和自减)的含义反过来了:对于反向迭代器,++运算将访问前一个元素,而--运算则访问下一个元素。
begin(), end(), rbegin(), rend()与容器序列关系示意图如下:
1)反向迭代器需要使用自减操作符:标准容器上的迭代器(reverse_iterator)既支持自增运算,也支持自减运算。但是,流迭代器由于不能反向遍历流,因此流迭代器不能创建反向迭代器。
2)可以通过reverse_iterator::base()将反向迭代器转换为普通迭代器使用,从逆序得到普通次序。如下面的例子所示:
#include <iostream> #include <string> #include <iterator> #include <algorithm> using namespace std;
int main() { string str = "this 'sentence' is a test"; cout<<"String: "<<str<<endl; string::iterator it1 = find(str.begin(), str.end(), '\''); string::iterator it2 = find(++it1, str.end(), '\''); // output: sentence cout<<"B-E: "<<string(it1, it2)<<endl; string::reverse_iterator rit1 = find(str.rbegin(), str.rend(), '\''); string::reverse_iterator rit2 = find(++rit1, str.rend(), '\''); // output: ecnetnes cout<<"R-B-E 1: "<<string(rit1, rit2)<<endl; // output: sentence cout<<"R-B-E 2: "<<string(rit2.base(), rit1.base())<<endl; return 0; }
|
const 迭代器
在标准库中,有输入范围的泛型算法要求其两个迭代器类型完全一样,包括const属性。要么都是const,要么都是非const,否则无法通过编译。同样,它们的返回值迭代器也与参数类型保持一致。
迭代器分类
不同的迭代器支持不同的操作集,而各种算法也要求相应的迭代器具有最小的操作集。因此,可以将算法的迭代器分为下面五类:
输入迭代器 (input iterator) |
读,不能写。支持的操作集:==, !=, 前缀++, 后缀++, *, ->。例如,find, accumulate算法要求这类迭代器。 |
输出迭代器 (output iterator) |
写,不能读。支持的操作集:前缀++, 后缀++, *(只能出现在赋值运算的左操作数上)。推出迭代器要求每个迭代器必须正好写入一次。例如,ostream_iterator是输出迭代器,copy算法使用这类迭代器。 |
前向迭代器(forward iterator) |
读和写,支持输入迭代器和输出迭代器提供的所有操作,还支持对同一个元素的多次读写。例如,replace算法需要这种迭代器。 |
双向迭代器(bidirectional iterator) |
读和写,除了支持前向迭代器的所有操作,还支持前缀--和后缀--,即支持双向遍历容器。例如,reverse算法要求这类迭代器。标准库容器中提供的迭代器都至少达到双向迭代器的要求。 |
随机访问迭代器(random-access iterator) |
读和写。提供在常量时间内访问容器任意位置的功能。支持完整的迭代器操作集:1)关系运算:==, !=, <, <=, >, >=;2)算术运算:it + n, it - n, it += n, it -= n以及it1 - it2;3)下标运算:it[n],等价于*(it + n)。需要随机访问迭代器的泛型算法包括sort算法。例如,vector, deque, string迭代器是随机访问迭代器,用作访问内置数组元素的指针也是随机访问迭代器。 |
除了输出迭代器,其他类别的迭代器形成了一个层次结构:需要低级类别迭代器的地方,可使用任意一种更高级的迭代器。例如,对于需要输入迭代器的算法,可传递前向、双向或随机访问迭代器调用该算法。而反之则不行。注意:向算法传递无效的迭代器类别所引起的错误,无法保证会在编译时被捕获到。
map, set, list类型提供双向迭代器,而string, vector和deque容器上定义的迭代器都是随机访问迭代器,用作访问内置数组元素的指针也是随机访问迭代器。istream_iterator是输入迭代器,ostream_iterator是输出迭代器。
另外,虽然map和set类型提供双向迭代器,但关联容器只能使用这部分算法的一个子集。因为关联容器的键是const对象。因此,关联容器不能使用任何写序列元素的算法。只能使用与关联容器绑在一起的迭代器来提供用于读操作的实参。因此,在处理算法时,最好将关联容器上的迭代器视为支持自减运算的输入迭代器,而不是完整的双向迭代器。
泛型算法的结构
就像所有的容器都建立在一致的设计模式上一样,算法也具有共同的设计基础。
算法最基本的性质是需要使用的迭代器种类。
另一种算法分类方法是前面介绍的按实现的功能分类:只读算法,不改变元素的值和顺序;给指定元素赋新值的算法;将一个元素的值移给另一个元素的算法。
另外,算法还有两种结构上的算法模式:一种模式是由算法所带的形参定义;另一种模式则通过两种函数命名和重载的规范定义。
算法的形参模式
大多数算法采用下面四种形式之一:
alg (beg, end, other parms);
alg (beg, end, dest, other parms);
alg (beg, end, beg2, other parms);
alg (beg, end, beg2, end2, other parms);
其中,alg是算法名,[beg, end)是输入范围,beg, end, dest, beg2, end2都是迭代器。
对于带有单个目标迭代器的算法:dest形参是一个迭代器,用于指定存储输出数据的目标对象。算法假定无论需要写入多少个元素都是安全的。注意:调用这类算法时,算法是将输出内容写到容器中已存在的元素上,所以必须确保输出容器中有足够大的容量存储输出数据,这也正是通过使用插入迭代器或者 ostream_iterator来调用这些算法的原因。
对于带第二个输入序列的算法:beg2和end2标记了完整的输出范围。而只有beg2的算法将beg2视为第二个输入范围的首元素,算法假定以beg2开始的范围至少与beg和end指定的范围一样大。
算法的命名规范
包括两种重要模式:第一种模式包括测试输入范围内元素的算法,第二种模式则应用于输入范围内元素的重新排序的算法。
1)区别带有一个值或一个谓词函数参数的算法版本
很多算法通过检查其输入范围内的元素实现其功能。这些算法通常要用到标准关系操作符:== 或 < 。其中的大部分算法都提供了第二个版本的算法,允许程序员提供比较或测试函数取代默认的操作符的使用。
例如, 排序算法默认使用 < 操作符,其重载版本带有一个额外的形参,表示取代默认的 < 操作符。
sort (beg, end); // use < operator to sort the elements
sort (beg, end, comp); // use function named comp to sort the elements
又如,查找算法默认使用 == 操作符。标准库为这类算法提供另外命名的(而非重载的)版本,带有谓词函数形参。对于带有谓词函数形参的算法,其名字带有后缀 _if:
find (beg, end, val); // find first instance of val in the input range
find_if (beg, end, pred); // find first instance for which pred is true
标准库为这类算法提供另外命名的版本,而非重载版本,原因在于这两种版本的算法带有相同的参数个数,容易导致二义性。
2)区别是否实现复制的算法版本
默认情况下,算法将重新排列的写回其范围。标准库也为这类算法提供了另外命名的版本,将元素写到指定的输出目标。此版本的算法在名字中添加 _copy后缀,例如:
reverse (beg, end);
reverse_copy (beg, end, dest);
第一个版本将输入序列中的元素反向重新排列;而第二个版本将复制输入序列中的元素,并将它们以逆序存储到dest开始的序列中。
容器特有的算法
list容器上的迭代器是双向的,而不是随机访问类型。由于list容器不支持随机访问,因此,在此容器上不能使用需要随机访问迭代器的算法。如sort 类算法。其它有些算法,如merge, remove, reverse, unique等,虽然可以用在list上,但性能太差。list容器结合自己的结构专门实现了更为高效的算法。因此,对于list对象,应该优先使用 list容器特有的成员版本,而不是泛型算法。
下表列出了list容器特有的操作:
lst.merge(lst2) lst.merge(lst2, comp) |
将lst2的元素合并到lst中。这两个list对象必须都已排序。合并后,lst2中元素被删除,为空。返回void类型
|
lst.remove(val) lst.remove_if(unaryPred)) |
调用lst.erase删除所有等于指定值或满足谓词的元素,返回void类型。 |
lst.reverse() |
反向排列lst中的元素 |
lst.sort() |
对lst中的元素排序 |
lst.splice(it, lst2) lst.splice(it, lst2, it2) lst.splice(it, beg, end) |
将lst2的元素移到lst中迭代器it所指向的元素前面。在lst2中删除移出的元素。第一个版本将lst2的所有元素移到lst中,合并后,lst2为空。lst和lst2不能是同一个list对象。第二个版本只移动it2所指向的元素,这个元素必须是lst2的元素。在这种情况下,lst和lst2可以是同一list对象。第三个版本移动迭代器beg和end标记的范围内的元素。这两个可以标记任意list对象的范围,包括 lst。当它们指定lst的一段范围时,如果it也指向这个范围内的一个元素,则该操作未定义。 |
lst.unique() lst.unique(binaryPred) |
调用erase删除同一值的连续版本。第一个版本使用 == 操作符判断元素是否相等;第二个版本则使用指定的谓词函数实现判断。 |
list容器特有的算法与其泛型算法版本之间有两个重要的差别:1)remove和unique的list版本修改了其关联的基础容器:真正删除了指定的元素;2)list容器提供的merge和splice操作会破坏它们的实参。使用泛型算法的merge版本,合并的序列将写入目标迭代器指向的对象,而它的两个输入序列保持不变。