STL7—迭代器

来源:互联网 发布:金融数据分析师是什么 编辑:程序博客网 时间:2024/05/01 03:12

三种迭代器类型:

    1插入迭代器:这类迭代器与容器绑定在一起,实现在容器中插入元素的功能。

    2iostream迭代器:这类迭代器可以与输入与输出流绑定在一起,用于迭代遍历所关联的IO流。

    3反向迭代器:这类迭代器实现向后遍历,而不是向前遍历,所有的容器都定义了自己的reverse_iterator类型,由rbeginrend成员函数返回。

上述迭代器都在iterator头文件中定义。

一、插入迭代器

    前面曾经提到的back_inserter函数是一种插入器,插入器适宜用迭代器适配器,其带有一个容器参数,并生成一个迭代器,用于在指定容器中插入元素。通过插入迭代器赋值时,迭代器将会插入一个新的元素。

C++提供了三种插入迭代器,其差别在于插入元素的位置不同:

   1)back_inserter:创建使用push_back实现插入的迭代器。

   2)front_inserter使用push_front实现插入。       (  只有当容器提供 push_front操作,才能使用front_inserter。在vector或其他没有push_front运算的容器上使用front_inserter,将产生错误。

   3)inserter使用insert实现插入操作。出了所关联的容器外,inserter还带有第二个实参:指向插入起始位置的迭代器


1front_inserter需要使用push_front

    front_inserter的操作类似于back_inserter:该函数创建一个迭代器,调用它所关联的基础容器的push_front成员函数代替赋值操作。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. deque<int> iDeq;  
  2. front_inserter(iDeq) = 1;   //OK  
  3. vector<int> iVec;  
  4. front_inserter(iVec) = 1;   //Error  
  5. back_inserter(iVec) = 1;    //OK  


2inserter将产生在指定位置实现插入的迭代器

     inserter适配器提供更普通的插入形式。这种适配器带有两个实参:所关联的容器和指示起始插入位置的迭代器

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. list<int>::iterator it = find(iList.begin(),iList.end(),42);  
  2. /* 
  3. *在创建 inserter 时,应指明新元素在何处插入。 
  4. *inserter 函数总是在它的迭代器实参所标明的位置前面插入新元素。 
  5. */  
  6. replace_copy(iVec.begin(),iVec.end(),inserter(iList,it),0,100);  //<插入过程中将0修改为100

3inserter+容器的begin组合与 front_inserter的区别:

    在使用front_inserter,元素始终在容器的第一个元素前面插入。而使用inserter,元素则在指定位置前面插入。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //理解下面输出的区别  
  2. list<int> ilst,ilst1,ilst2;  
  3.   
  4. for (list<int>::size_type index = 0; index != 4; ++index)  
  5. {  
  6.     ilst.push_front(index);  //3,2,1,0
  7. }  
  8.   
  9. copy(ilst.begin(),ilst.end(),front_inserter(ilst1));  //0,1,2,3  总在对象的所有元素之前插入
  10. copy(ilst.begin(),ilst.end(),inserter(ilst2,ilst2.begin()));  //3,2,1,0     刚开始的时候是在对象的头部插入,但是之后就不在头部了


二、iostream迭代器

      虽然iostream类型不是容器,但是标准库同样提供了iostream对象上使用的迭代器:istream_iterator用于读取输入流,而ostream_iterator用于写输出流。这些迭代器将它们所对应的流视为特定类型的元素序列。使用流迭代器时,可以用泛型算法从流对象中读取数据或写数据到流对象中。

iostream迭代器的构造函数

istream_iterator<T>in(strm);

创建从输入流strm读取T类型对象istream_iterator对象

istream_iterator<T>in;

istream_iterator对象的超出末端迭代器

ostream_iterator<T>in(strm);

创建T类型的对象写入到输出流strmostream_iterator对象

ostream_iterator<T>in(strm,delim);

创建将T类型的对象写入到输出流strmostream_iterator对象,再写入过程中使用delim作为元素的分隔符。delimit是以空字符结束的字符数组。

     流迭代器只定义了最基本的迭代器操作:自增、解引用和赋值。此外,可比较两个istream迭代器是否相等(或不等)ostream迭代器则不提供比较运算

istream_iterator的操作

it1== it2

it1!= it2

比较两个istream_iterator对象是否相等/不等。迭代器必须读取的是相同的类型。如果两个迭代器都是end值,则它们相等。对于两个都不指向流结束符位置的迭代器,如果它们使用同一个输入流构造,则它们也相等

*it

返回从流中读取的值

it-> mem

(*it).mem的同义词,返回从流中读取的对象的mem成员

++it

it++

通过使用元素类型提供>>操作符从输入流中获取下一个元素值,使迭代器向前移动。通常,前缀版本使迭代器再流中向前移动,并返回对加1后的迭代器的引用。而后缀版本使迭代器在流中向前移动后,返回原值


1、流迭代器的定义

    流迭代器都是类模板:任何已定义输入操作符(>>操作符)的类型都可以定义stream_iterator。类似地,任何已定义输出操作符(<<操作符)的类型也可定义ostream_iterator

    在创建流迭代器时,必须指定迭代器所读写的对象类型:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. istream_iterator<int> cin_it(cin);  
  2. istream_iterator<int> end_of_stream;  
  3.   
  4. ofstream outfile;  
  5. ostream_iterator<Sales_item> cout_it(outfile," ");  

【注意:】

ostream_iterator对象必须与特定的流绑定在一起

在创建istream_iterator,可直接将它绑定到一个流上。另一种方法是在创建时不提供实参,则该迭代器指向超出末端位置。

ostream_iterator不提供超出末端迭代器,在创建ostream_iterator对象时,可提供第二个(可选的)实参,指定将元素写入输出流时使用的分隔符。分隔符必须是C风格字符串。因为它是C风格字符串,所以必须以空字符结束;否则,其行为将是未定义的。


2istream_iterator对象上的操作

    构造与流绑定在一起的istream_iterator对象时,将对迭代器定位,以便第一次对该迭代器进行解引用时即可从流中读取第一个值。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. vector<int> iVec;  
  2. istream_iterator<int> in_iter(cin);  
  3. istream_iterator<int> eof;  
  4.   
  5. while (in_iter != eof)  
  6. {  
  7.     iVec.push_back(*in_iter++);  
  8. }  

    while循环从cin中读取 int型数据,并将读入的内容保存在iVec中。每次循环都检查in_iter是否为 eof。其中eof迭代器定义为空的istream_iterator对象,用作结束迭代器。绑在流上的迭代器在遇到文件结束或某个错误时(如输入的是字符),将等于结束迭代器的值。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //可以这样重写该程序  
  2. istream_iterator<int> in_iter(cin);  
  3. istream_iterator<int> eof;  
  4.   
  5. vector<int> iVec(in_iter,eof);  

这里,用一对标记元素范围的迭代器构造vector对象。这些迭代器是istream_iterator对象,这就意味着这段范围的元素是通过读取所关联的流来获得的


3ostream_iterator对象和ostream_iterator对象的使用

     可以使用ostream_iterator对象将一个值序列写入流中,其操作过程与使用迭代器将一组值逐个赋值给容器中的元素相同:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ostream_iterator<string> out_iter(cout,"\n");  
  2. istream_iterator<string> in_iter(cin),eof;  
  3.   
  4. while (in_iter != eof)  
  5. {  
  6.     *out_iter++ = *in_iter++;  
  7. }  

读取cin,并将每个读入的值依次写入到cout中不同的行中。


4、在类类型上使用istream_iterator

     提供了输入操作符>>任何类型都可以创建istream_iterator对象。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. istream_iterator<Sales_item> item_iter(cin),eof;  
  2. Sales_item sum;  
  3. sum = *item_iter ++;  
  4.   
  5. while (item_iter != eof)  
  6. {  
  7.     if (item_iter -> same_isbn(sum))  
  8.     {  
  9.         sum += *item_iter;  
  10.     }  
  11.     else  
  12.     {  
  13.         cout << sum << endl;  
  14.         sum = *item_iter;  
  15.     }  
  16.     ++ item_iter;  
  17. }  
  18. cout << sum << endl;  

5、流迭代器的限制

    1)不可能从ostream_iterator对象读入,也不可能写到istream_iterator对象中。

    2)一旦给ostream_iterator对象赋了一个值,写入就提交了。赋值后,没有办法再改变这个值。此外,ostream_iterator对象中每个不同的值都只能正好输出一次。

    3)ostream_iterator没有 ->操作符。


6、与算法一起使用流迭代器

    由于算法是基于迭代器操作实现的,而流迭代器定义了一些迭代器操作。由于流迭代器操作,因此,至少可在一些泛型算法上使用这类迭代器。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //从文件中读取一些数,再将读取的不重复的数写到标准输出  
  2. ifstream inFile("input");  
  3. istream_iterator<int> in_iter(inFile),eof;  
  4.   
  5. vector<int> iVec(in_iter,eof);  
  6. sort(iVec.begin(),iVec.end());  
  7.   
  8. ostream_iterator<int> out_iter(cout," ");  
  9. unique_copy(iVec.begin(),iVec.end(),out_iter);  


三、反向迭代器

    反向迭代器是一种反向遍历容器的迭代器:从最后一个元素到第一个元素遍历容器。反向迭代器将自增/自减的含义反过来了:对于反向迭代器,++运算将访问前一个元素,而—元素将访问下一个元素。

   容器定义的rbeginrend成员,分别返回指向容器尾元素和首元素前一位置的反向迭代器。


1、反向迭代器需要使用自减操作符

     从一个既支持--也支持++的迭代器就可以定义反向迭代器。毕竟,反向迭代器的目的是移动迭代器反向遍历序列。标准容器上的迭代器既支持自增运算,也支持自减运算。但是,流迭代器却不然,由于不能反向遍历流,因此流迭代器不能创建反向迭代器


2、反向迭代器与其他迭代器之间的关系

    输出 string对象line中的第一个单词。使用find可很简单地实现这个任务:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. string::iterator comma = find(line.begin(),line.end(),',');  
  2. cout << string(line.begin(),comma) << endl;  

如果要输出列表中的最后一个单词,可以使用反向迭代器:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. string::reverse_iterator rcomma = find(line.rbegin(),line.rend(),',');  
  2. //下面的输出结果可能不能满足你的要求...  
  3. cout << string(line.rbegin(),rcomma) << endl;  

四、const迭代器

    算法要求用于指定范围的两个迭代器必须具有完全一样的类型。比如我们曾将使用的find_first_of算法:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. find_first_of(it, roster1.end(),roster2.begin(), roster2.end())  

     该函数调用的输入范围由it和调用roster1.end()返回的迭代器指定。算法要求用于指定范围的两个迭代器必须具有完全一样的类型。roster1.end()返回的迭代器依赖于roster1的类型。如果该容器是const对象,则返回的迭代器是const_iterator类型;否则,就是普通的iterator类型。在这个程序中,roster1不是 const对象,因而end返回的只是一个普通的迭代器。

    如果我们将it定义为const_iterator,那么find_first_of的调用将无法编译。用来指定范围的两个迭代器的类型不相同。it是 const_iterator类型的对象,rotser1.end()返回的则是一个iterator对象。


五、五种迭代器

    可以根据算法要求它的迭代器的提供什么类型的操作,对算法分类:

迭代器分类

输入迭代器

读,不能写:只支持自增运算

输出迭代器

写,不能读:只支持自增运算

前向迭代器

读和写:只支持自增运算

双向迭代器

读和写:支持自增、自减运算

随机访问迭代器

读和写:支持完整的迭代器算术运算



1、输入迭代器

可用于读取容器中的元素,但是不保证能够支持容器的写入操作。输入迭代器必须至少提供下列操作:

    1)相等和不等操作符(==,!=),比较两个迭代器。

    2)前置和后置的自增运算(++),使迭代器向前递进指向下一个元素。

    3)用于读取元素的解引用操作符(*),此操作符只能出现在赋值运算的右操作数上。

   4)箭头操作符(->),这是(*it).member的同义语,也就是说,对迭代器进行解引用来获取其所关联的对象的成员。

输入迭代器只能顺序使用;一旦输入迭代器自增了,就无法再用它检查之前的元素。要求在这个层次上提供支持的泛型算法包括find和 accumulate标准库istream_iterator类型输入迭代器。


2、输出迭代器

可以视为与输入迭代器功能互补的迭代器;输入迭代器可用于向容器写入元素,但不保证能支持读取容器的内容。输入迭代器要求:

    1)前置和后置的自增运算(++),使迭代器向前递进指向下一个元素。

    2)解引用操作符(*),引操作符只能出现在赋值运算的左操作数上。给解引用的输出迭代器赋值,将对该迭代器所指向的元素做写入操作。

     输出迭代器可以要求每个迭代器的值必须正好写入一次。使用输出迭代器时,对于指定的迭代器值应该使用一次*运算,而且只能用一次。输出迭代器一般用作算法的第三个实参,标记起始写入的位置。例如,copy算法使用一个输出迭代器作为它的第三个实参,将输入范围内的元素复制到输出迭代器指定的目标位置。标准库ostream_iterator类型输出迭代器


3、前向迭代器

     用于读写指定的容器。这类迭代器只会以一个方向遍历序列。前向迭代器支持输入迭代器和输出迭代器提供的所有操作,除此之外,还支持对同一个元素的多次读写。需要前向迭代器的泛型算法包括replace


4、双向迭代器

   从两个方向读写容器。出了提供前向迭代器的全部操作,还支持前置和后置的自减运算(--)。需要使用双向迭代器的泛型算法包括reverse所有标准库容器提供的迭代器都至少达到双向迭代器的要求。


5、随机访问迭代器

    提供在常量时间内访问容器任意位置的功能。不仅支持双向迭代器的所有功能,还支持下面的操作:

    1)关系操作符<<=>>=,比较两个迭代器的相对位置

    2)迭代器与整型数值n之间的加法和减法操作符++=-和 -=,结果是迭代器在容器中向前(或退回)n个元素。

    3)两个迭代器之间的减法操作符(--),得到两个迭代器间的距离

    4)下标操作符iter[n],这是*(iter+ n) 的同义词。

   需要随机访问迭代器的泛型算法包括sort算法。vectordequestring迭代器是随机访问迭代器,用作访问内置数组元素的指针也是随机访问迭代器

   除了输出迭代器,其他类别的迭代器形成了一个层次结构:需要低级类别迭代器的地方,可使用任意一种更高级的迭代器


【总结】

     mapset和 list类型提供双向迭代器,stringvector和 deque容器上定义的迭代器都是随机访问迭代器都是随机访问迭代器,用作访问内置数组元素的指针也是随机访问迭代器。istream_iterator是输入迭代器,ostream_iterator则是输出迭代器。


【关键概念:关联容器与算法】

    尽管map和 set类型提供双向迭代器,但关联容器只能使用算法的一个子集。问题在于:关联容器的键是const对象。因此,关联容器不能使用任何写序列元素的算法。只能使用与关联容器绑在一起的迭代器来提供用于读操作的实参。

    在处理算法时,最好将关联容器上的迭代器视为支持自减运算的输入迭代器,而不是完整的双向迭代器。


【重点理解】

    C++标准为所有泛型和算术算法的每一个迭代器形参指定了范围最小的迭代器种类。例如:find至少需要一个输入迭代器。replace函数至少需要一对前向迭代器。replace_copy函数的头两个迭代器必须至少是前向迭代器,第三个参数代表输出目标,必须至少是输出迭代器。

    对于每一个形参,迭代器必须保证最低功能。将支持更少功能的迭代器传递给函数是错误的;而传递更强功能的迭代器则没问题

  向算法传递无效的迭代器类别所引起的错误,无法保证会在编译时被捕获到

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P358 习题11.26(d)  
  2.     vector<int> iVec1,iVec2;  
  3.     //...  
  4.     //解释下列语句错误的原因,以及编译器能否检测出这类错误?  
  5.     sort(iVec1.begin(),iVec2.end());  
0 0