zoukankan      html  css  js  c++  java
  • C++的迭代器iterator的使用方法

    “过一个平凡无趣的人生实在太容易了,你可以不读书,不冒险,不运动,不写作,不外出,不折腾……但是,人生最后悔的事情就是:我本可以。”——xxx

    要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。

    迭代器是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只用少数容器(如vector)支持下标操作访问容器元素。
    除此之外,STL中迭代器一个最重要的作用就是作为容器(vector,list等)与STL算法的粘结剂,只要容器提供迭代器的接口,同一套算法代码可以利用在完全不同的容器中,这是抽象思想的经典应用。
    迭代器按照定义方式分成以下四种。

    1) 正向迭代器,定义方法如下:

    容器类名::iterator  迭代器名;

    2) 常量正向迭代器,定义方法如下:

    容器类名::const_iterator  迭代器名;

    3) 反向迭代器,定义方法如下:

    容器类名::reverse_iterator  迭代器名;

    4) 常量反向迭代器,定义方法如下:

    容器类名::const_reverse_iterator  迭代器名;

    定义和初始化

      每种容器都定义了自己的迭代器类型,如vector:

    vector<int>::iterator    iter;    //定义一个名为iter的变量

      每种容器都定义了一对名为begin和en的函数,用于返回迭代器。下面对迭代器进行初始化操作:

    vector<int>    ivec;
    vector<int>::iterator    iter1=ivec.bengin();    //将迭代器iter1初始化为指向ivec容器的第一个元素
    
    vector<int>::iterator   iter2=ivec.end();    //将迭代器iter2初始化为指向ivec容器的最后一个元素的下一个位置

      注意end并不指向容器的任何元素,而是指向容器的最后元素的下一位置,称为超出末端迭代器。如果vector为空,则begin返回的迭代器和end返回的迭代器相同。一旦向上面这样定义和初始化,就相当于把该迭代器和容器进行了某种关联,就像把一个指针初始化为指向某一空间地址一样。

    常用操作

      下面列出了迭代器的常用运算操作:

    *iter                //对iter进行解引用,返回迭代器iter指向的元素的引用
    iter->men            //对iter进行解引用,获取指定元素中名为men的成员。等效于(*iter).men
    ++iter                //给iter加1,使其指向容器的下一个元素
    iter++
    --iter                //给iter减1,使其指向容器的前一个元素
    iter--
    iter1==iter2        //比较两个迭代器是否相等,当它们指向同一个容器的同一个元素或者都指向同同一个容器的超出末端的下一个位置时,它们相等 
    iter1!=iter2        

      假设已经声明一个vector<int>的ivec容器,下面用迭代器来遍历ivec容器,把其每个元素重置为0:

    for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
            *iter=0;

      在C++定义的容器类型中,只有vector和queue容器提供迭代器算数运算和除!=和==之外的关系运算:

    iter+n     //在迭代器上加(减)整数n,将产生指向容器中钱前面(后面)第n个元素的迭代器。新计算出来的迭代器必须指向容器中的元素或超出容器末端的下一个元素
    iter-n
    
    iter1+=iter2        //将iter1加上或减去iter2的运算结果赋给iter1。两个迭代器必须指向容器中的元素或超出容器末端的下一个元素
    iter1-=iter2
    
    iter1-iter2            //两个迭代器的减法,得出两个迭代器的距离。两个迭代器必须指向容器中的元素或超出容器末端的下一个元素
    
    >,>=,<,<=        //元素靠后的迭代器大于靠前的迭代器。两个迭代器必须指向容器中的元素或超出容器末端的下一个元素

      注意两个迭代器相减得出两个迭代器对象的距离,该距离名为difference_type的signed类型的值,该类型类似于size_type类型,也是有vector定义的。可以迭代器算术操作来移动迭代器直接指向某个元素:

    vector<int>::iterator    mid=v.begin()+v.size()/2;    //初始化mid迭代器,使其指向v中最靠近正中间的元素

    三.迭代器const_iterator

      每种容器还定义了一种名为const_iterator的类型。该类型的迭代器只能读取容器中的元素,不能用于改变其值。之前的例子中,普通的迭代器可以对容器中的元素进行解引用并修改,而const_iterator类型的迭代器只能用于读不能进行重写。例如可以进行如下操作:

    for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)
         cout<<*iter<<endl;       //合法,读取容器中元素值
    
    for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)
        *iter=0;        //不合法,不能进行写操作

      const_iterator和const  iterator是不一样的,后者对迭代器进行声明时,必须对迭代器进行初始化,并且一旦初始化后就不能修改其值。这有点像常量指针和指针常量的关系。例如:

    vector<int>    ivec(10);
    const    vector<int>::iterator    iter=ivec.begin();
    *iter=0;    //合法,可以改变其指向的元素的值
    ++iter;    //不合法,无法改变其指向的位置
    四.使迭代器失效的操作

      由于一些对容器的操作如删除元素或移动元素等会修改容器的内在状态,这会使得原本指向被移动元素的迭代器失效,也可能同时使其他迭代器失效。使用无效的迭代器是没有定义的,可能会导致和使用悬垂指针相同的问题。所以在使用迭代器编写程序时,需要特别留意哪些操作会使迭代器失效。使用无效迭代器会导致严重的运行时错误。

    1. 迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型。
    (1) 每种容器类型都定义了自己的迭代器类型,如vector:
    vector<int>::iterator iter;这条语句定义了一个名为iter的变量,它的数据类型是由vector<int>定义的iterator类型。
    (2) 使用迭代器读取vector中的每一个元素:
    vector<int> ivec(10,1);
    for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
    {
    *iter=2; //使用 * 访问迭代器所指向的元素
    }
    const_iterator:
    只能读取容器中的元素,而不能修改。
    for(vector<int>::const_iterator citer=ivec.begin();citer!=ivec.end();citer++)
    {
    cout<<*citer;
    //*citer=3; error
    }
    vector<int>::const_iterator 和 const vector<int>::iterator的区别
    const vector<int>::iterator newiter=ivec.begin();
    *newiter=11; //可以修改指向容器的元素
    //newiter++; //迭代器本身不能被修改
    (3) iterator的算术操作:
    iterator除了进行++,--操作,可以将iter+n,iter-n赋给一个新的iteraor对象。还可以使用一个iterator减去另外一个iterator.
    const vector<int>::iterator newiter=ivec.begin();
    vector<int>::iterator newiter2=ivec.end();
    cout<<" "<<newiter2-newiter;

    使用:

    1.  
      vector<int>::iterator it;
    2.  
      vector<int> a;
    3.  
      vector<int> b;
    4.  
      for (it=a.begin(); it<a.end(); it++){
    5.  
      cout<<*it<<" ";
    6.  
      }
    7.  
      for ( it=b.begin(); it<b.end(); it++){
    8.  
      cout<<*it<<" ";
    9.  
      }

    容器一般含有其各自的迭代器型别(iterator types),所以当你使用一般的容器迭代器时,并不需要含入专门的头文件。

    以下是关于代码的解释:

    vector<int>::iterator it;   

    两个冒号表示作用域操作符。::操作符在其左操作数的作用域内找到其右操作数的名字。

    这样就定义了一个迭代器名字为it(名字可以随便取),并且这个迭代器的作用域是vector。

    这个迭代器it只是初始化,并未赋值,所以在下面的两个vector a,b都可以用it来遍历,

    就像定义了一个循环变量i,可以遍历两个数组a,b道理是一样的。

    vector的.begin()和.end()

    vector的.begin()和.end()方法都会返回一个vector::iterator对象,分别指向vector的首元素位置和尾元素的下一个位置(我们可以称之为结束标志位)。 

    *it   迭代器所指向的内容

    对一个迭代器的使用与一个指针变量的使用极为相似,或者可以这样说,指针就是一个非常标准的迭代器。

    迭代器的相应型别

    我们都知道type_traits 可以萃取出类型的型别,根据不同型别可以执行不同的处理流程。那么对于迭代器来说,是否有针对不同特性迭代器的优化方法呢?答案是肯定的。拿一个STL算法库中的distance函数来说,distance函数接受两个迭代器参数,然后计算他们两者之间的距离。显然对于不同的迭代器计算效率差别很大。比如对于vector容器来说,由于内存是连续分配的,因此指针直接相减即可获得两者的距离;而list容器是链式表,内存一般都不是连续分配,因此只能通过一级一级调用next()或其他函数,每调用一次再判断迭代器是否相等来计算距离。vector迭代器计算distance的效率为O(1),而list则为O(n),n为距离的大小。

    因此,根据迭代器不同的特性,将迭代器分为5类:

    • Input Iterator:这种迭代器所指的对象为只读的。
    • Ouput Iterator: 所指对象只能进行一次写入操作。
    • Forward Iterator: 允许”读写型”算法在迭代器区间内进行读写操作,比如说replace函数需要读取区间内容,根据所读内容决定是否写入
    • Bidirectional Iterator : 可双向移动。某些算法需要反向遍历某个迭代器区间
    • Random Access Iterator : 前四种迭代器只提供部分指针算数能力(前三种支持++运算符,后一种还支持–运算符),第五种则支持所有指针的算术运算,包括p + n,p - n,p[n],p1 - p2,p1 < p2

    这五种迭代器的继承关系如下所示。

    这里写图片描述

    了解了迭代器的类型,我们就能解释vector的迭代器和list迭代器的区别了。显然vector的迭代器具有所有指针算术运算能力,而list由于是双向链表,因此只有双向读写但不能随机访问元素。故vector的迭代器种类为Random Access Iterator,list 的迭代器种类为Bidirectional Iterator。

    迭代器用法示例

    通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。

    迭代器都可以进行++操作。反向迭代器和正向迭代器的区别在于:

    • 对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;
    • 而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。


    下面的程序演示了如何通过迭代器遍历一个 vector 容器中的所有元素。

    1. #include <iostream>
    2. #include <vector>
    3. using namespace std;
    4. int main()
    5. {
    6. vector<int> v; //v是存放int类型变量的可变长数组,开始时没有元素
    7. for (int n = 0; n<5; ++n)
    8. v.push_back(n); //push_back成员函数在vector容器尾部添加一个元素
    9. vector<int>::iterator i; //定义正向迭代器
    10. for (i = v.begin(); i != v.end(); ++i) { //用迭代器遍历容器
    11. cout << *i << " "; //*i 就是迭代器i指向的元素
    12. *i *= 2; //每个元素变为原来的2倍
    13. }
    14. cout << endl;
    15. //用反向迭代器遍历容器
    16. for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
    17. cout << *j << " ";
    18. return 0;
    19. }

    程序的输出结果是:
    0 1 2 3 4
    8 6 4 2 0

    第 6 行,vector 容器有多个构造函数,如果用无参构造函数初始化,则容器一开始是空的。

    第 10 行,begin 成员函数返回指向容器中第一个元素的迭代器。++i 使得 i 指向容器中的下一个元素。end 成员函数返回的不是指向最后一个元素的迭代器,而是指向最后一个元素后面的位置的迭代器,因此循环的终止条件是i != v.end()

    第 16 行定义了反向迭代器用以遍历容器。反向迭代器进行++操作后,会指向容器中的上一个元素。rbegin 成员函数返回指向容器中最后一个元素的迭代器,rend 成员函数返回指向容器中第一个元素前面的位置的迭代器,因此本循环实际上是从后往前遍历整个数组。

    如果迭代器指向了容器中最后一个元素的后面或第一个元素的前面,再通过该迭代器访问元素,就有可能导致程序崩溃,这和访问 NULL 或未初始化的指针指向的地方类似。

    第 10 行和第 16 行,写++i++j相比于写i++j++,程序的执行速度更快。回顾++被重载成前置和后置运算符的例子如下:

    1. CDemo CDemo::operator++ ()
    2. { //前置++
    3. ++n;
    4. return *this;
    5. }
    6. CDemo CDemo::operator ++(int k)
    7. { //后置++
    8. CDemo tmp(*this); //记录修改前的对象
    9. n++;
    10. return tmp; //返回修改前的对象
    11. }

    后置++要多生成一个局部对象 tmp,因此执行速度比前置的慢。同理,迭代器是一个对象,STL 在重载迭代器的++运算符时,后置形式也比前置形式慢。在次数很多的循环中,++ii++可能就会造成运行时间上可观的差别了。因此,本教程在前面特别提到,对循环控制变量i,要养成写++i、不写i++的习惯。

    注意,容器适配器 stack、queue 和 priority_queue 没有迭代器。容器适配器有一些成员函数,可以用来对元素进行访问。

    迭代器的功能分类

    不同容器的迭代器,其功能强弱有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。

    常用的迭代器按功能强弱分为输入、输出、正向、双向、随机访问五种,这里只介绍常用的三种。

    1) 正向迭代器。假设 p 是一个正向迭代器,则 p 支持以下操作:++p,p++,*p。此外,两个正向迭代器可以互相赋值,还可以用==!=运算符进行比较。

    2) 双向迭代器。双向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一个双向迭代器,则--pp--都是有定义的。--p使得 p 朝和++p相反的方向移动。

    3) 随机访问迭代器。随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

    • p+=i:使得 p 往后移动 i 个元素。
    • p-=i:使得 p 往前移动 i 个元素。
    • p+i:返回 p 后面第 i 个元素的迭代器。
    • p-i:返回 p 前面第 i 个元素的迭代器。
    • p[i]:返回 p 后面第 i 个元素的引用。


    此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。p1<p2的含义是:p1 经过若干次(至少一次)++操作后,就会等于 p2。其他比较方式的含义与此类似。

    对于两个随机访问迭代器 p1、p2,表达式p2-p1也是有定义的,其返回值是 p2 所指向元素和 p1 所指向元素的序号之差(也可以说是 p2 和 p1 之间的元素个数减一)。

    表1所示为不同容器的迭代器的功能。

    表1:不同容器的迭代器的功能
    容器迭代器功能
    vector 随机访问
    deque 随机访问
    list 双向
    set / multiset 双向
    map / multimap 双向
    stack 不支持迭代器
    queue 不支持迭代器
    priority_queue 不支持迭代器


    例如,vector 的迭代器是随机迭代器,因此遍历 vector 容器有以下几种做法。下面的程序中,每个循环演示了一种做法。

    【实例】遍历 vector 容器。

    1. #include <iostream>
    2. #include <vector>
    3. using namespace std;
    4. int main()
    5. {
    6. vector<int> v(100); //v被初始化成有100个元素
    7. for(int i = 0;i < v.size() ; ++i) //size返回元素个数
    8. cout << v[i]; //像普通数组一样使用vector容器
    9. vector<int>::iterator i;
    10. for(i = v.begin(); i != v.end (); ++i) //用 != 比较两个迭代器
    11. cout << * i;
    12. for(i = v.begin(); i < v.end ();++i) //用 < 比较两个迭代器
    13. cout << * i;
    14. i = v.begin();
    15. while(i < v.end()) { //间隔一个输出
    16. cout << * i;
    17. i += 2; // 随机访问迭代器支持 "+= 整数" 的操作
    18. }
    19. }

    list 容器的迭代器是双向迭代器。假设 v 和 i 的定义如下:

    1. list<int> v;
    2. list<int>::const_iterator i;

    则以下代码是合法的:

    1. for(i=v.begin(); i!=v.end(); ++i)
    2. cout << *i;

    以下代码则不合法:

    1. for(i=v.begin(); i<v.end(); ++i)
    2. cout << *i;

    因为双向迭代器不支持用“<”进行比较。以下代码也不合法:

    1. for(int i=0; i<v.size(); ++i)
    2. cout << v[i];

    因为 list 不支持随机访问迭代器的容器,也不支持用下标随机访问其元素。

    在 C++ 中,数组也是容器。数组的迭代器就是指针,而且是随机访问迭代器。例如,对于数组 int a[10],int * 类型的指针就是其迭代器。则 a、a+1、a+2 都是 a 的迭代器。

    迭代器的辅助函数

    STL 中有用于操作迭代器的三个函数模板,它们是:

    • advance(p, n):使迭代器 p 向前或向后移动 n 个元素。
    • distance(p, q):计算两个迭代器之间的距离,即迭代器 p 经过多少次 + + 操作后和迭代器 q 相等。如果调用时 p 已经指向 q 的后面,则这个函数会陷入死循环。
    • iter_swap(p, q):用于交换两个迭代器 p、q 指向的值。


    要使用上述模板,需要包含头文件 algorithm。下面的程序演示了这三个函数模板的 用法。

    1. #include <list>
    2. #include <iostream>
    3. #include <algorithm> //要使用操作迭代器的函数模板,需要包含此文件
    4. using namespace std;
    5. int main()
    6. {
    7. int a[5] = { 1, 2, 3, 4, 5 };
    8. list <int> lst(a, a+5);
    9. list <int>::iterator p = lst.begin();
    10. advance(p, 2); //p向后移动两个元素,指向3
    11. cout << "1)" << *p << endl; //输出 1)3
    12. advance(p, -1); //p向前移动一个元素,指向2
    13. cout << "2)" << *p << endl; //输出 2)2
    14. list<int>::iterator q = lst.end();
    15. q--; //q 指向 5
    16. cout << "3)" << distance(p, q) << endl; //输出 3)3
    17. iter_swap(p, q); //交换 2 和 5
    18. cout << "4)";
    19. for (p = lst.begin(); p != lst.end(); ++p)
    20. cout << *p << " ";
    21. return 0;
    22. }

    程序的输出结果是:
    1) 3
    2) 2
    3) 3
    4) 1 5 3 4 2

    https://www.cnblogs.com/hdk1993/p/4419779.html

  • 相关阅读:
    linux 进入 GNOME X 界面
    POJ 3436 ACM Computer Factory (拆点+最大流)
    学习:EF(Entity Framwork)结构【转】
    .net上传Excel,解析Excel,导出CSV
    在Handler里面使用Session
    如何用JavaScript判断访问的来源是电脑还是手机,是什么浏览器?
    ASP.NET List泛型分页代码 【转】
    ASP.NET MVC中实现多个按钮提交的几种方法【转】
    清空数据库所有表,所有存储过程SQL语句
    可以不被浏览器拦截的弹出窗口
  • 原文地址:https://www.cnblogs.com/klb561/p/13830453.html
Copyright © 2011-2022 走看看