迭代器(转)

插入迭代器
 
   插入迭代器是一种迭代器适配器,带有一个容器参数,并生成一个迭代器,用于在指定的容器中插入元素。通过插入迭代器赋值时,迭代器将会插入一个新的元素。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版本,合并的序列将写入目标迭代器指向的对象,而它的两个输入序列保持不变。
posted @ 2012-09-25 16:41  轻典  阅读(377)  评论(0编辑  收藏  举报