• C++中的迭代器


    C++STL中的迭代器

          “指针”对所有C/C++的程序员来说,一点都不陌生。在接触到C语言中的malloc函数和C++中的new函数后,我们也知道这两个函数返回的都是一个指针,该指针指向我们所申请的一个“堆”。提到“堆”,就不得不想到“栈”,从C/C++程序设计的角度思考,“堆”和“栈”最大的区别是“栈”由系统自动分配并且自动回收,而“堆”则是由程序员手动申请,并且显示释放。如果程序员不显示释放“堆”,便会造成内存泄漏,内存泄漏的危害大家知道,严重时会导致系统崩溃。

           既然“指针”的使用者一不小心就可能导致内存泄漏,那么我们如何能够使得指针的使用变得更安全呢?从C++面向对象的角度分析,我们有没有可能将“指针”封装起来,使得用户不直接接触指针,而使用一个封装后的对象来替代指针的操作呢?

            答案是显然的,“智能指针”(smart pointer)正解决这类问题,尤其是在防止内存泄漏方面做得非常突出。

            迭代器iterator就是一种智能指针,它对原始指针进行了封装,并且提供一些等价于原始指针的操作,做到既方便又安全。

            简单来说就是提供一种方法,在不需要暴露某个容器的内部表现形式情况下,使之能依次访问该容器中的各个元素,这种设计思维在STL中得到了广泛的应用,是STL的关键所在,通过迭代器,容器和算法可以有机的粘合在一起,只要对算法给予不同的迭代器,就可以对不同容器进行相同的操作。

           尽管我们可以使用下标来访问字符串中的字符或vector的元素,但更一般的机制是使用迭代器(iterator)。
    所有的容器都支持迭代器,但仅少数几个支持下标操作。可以替代下标访问vector对象的元素。

    合法的迭代器:

      • 指示某个元素
      • 指示最后一个元素的下一个位置
      • 其它的迭代器都是不合法的。
      • 使用迭代器

            迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器不仅仅是指针,因此你不能认为他们一定具有地址值。例如,一个数组索引,也可以认为是一种迭代器。

            迭代器有各种不同的创建方法。程序可能把迭代器作为一个变量创建。一个STL容器类可能为了使用一个特定类型的数据而创建一个迭代器。作为指针,必须能够使用*操作符类获取数据。你还可以使用其他数学操作符如++。典型的,++操作符用来递增迭代器,以访问容器中的下一个对象。如果迭代器到达了容器中的最后一个元素的后面,则迭代器变成past-the-end值。使用一个past-the-end值得指针来访问对象是非法的,就好像使用NULL或为初始化的指针一样。

    提示:

    STL不保证可以从另一个迭代器来抵达一个迭代器。例如,当对一个集合中的对象排序时,如果你在不同的结构中指定了两个迭代器,第二个迭代器无法从第一个迭代器抵达,此时程序注定要失败。这是STL灵活性的一个代价。

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

    vector<int>::iterator iter;  

    语句定义了一个名为 iter 的变量,它的数据类型是 vector<int> 定义的 iterator 类型。每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。

    begin 和 end 操作

    每种容器都定义了一对命名为 begin 和 end 的函数,用于返回迭代器。如果容器中有元素的话,由 begin 返回的迭代器指向第一个元素:
    vector<int>::iterator iter = ivec.begin();  
    上述语句把 iter 初始化为由名为 vector 操作返回的值。假设 vector 不空,初始化后,iter 即指该元素为 ivec[0]。由 end 操作返回的迭代器指向 vector 的“末端元素的下一个”。表明它指向了一个不存在的元素。如果 vector 为空,begin 返回的迭代器与 end 返回的迭代器相同。由 end 操作返回的迭代器并不指向 vector 中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完 vector 中所有元素。

    vector 迭代器的自增和解引用运算

    迭代器类型可使用解引用操作符(dereference operator)(*)来访问迭代器所指向的元素:
    *iter = 0;  
    

      解引用操作符返回迭代器当前所指向的元素。假设 iter 指向 vector 对象 ivec 的第一元素,那么 *iter 和 ivec[0] 就是指向同一个元素。上面这个语句的效果就是把这个元素的值赋为 0。

    迭代器使用自增操作符向前移动迭代器指向容器中下一个元素。从逻辑上说,迭代器的自增操作和 int 型对象的自增操作类似。对 int 对象来说,操作结果就是把 int 型值“加 1”,而对迭代器对象则是把容器中的迭代器“向前移动一个位置”。因此,如果 iter 指向第一个元素,则 ++iter 指向第二个元素

    由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。

    迭代器的其他操作

    另一对可执行于迭代器的操作就是比较:用 == 或 != 操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。

    迭代器应用的程序示例

    使用迭代器下标、迭代器改变vector的内容

    #include <iostream>  
    #include <string>  
    #include <vector>  
      
    int print_int_vector(std::vector<int> ivec)  
    {  
        for(std::vector<int>::size_type ix =0, j = 0; ix != ivec.size(); ++ix, ++j)  
        {  
            std::cout<<ivec[ix]<<" "; //加空格!  
        }  
        std::cout<<std::endl;  
        return 0;  
    }  
      
    int main()  
    {  
        std::vector<int> ivec(10, 68); // empty vector  
        print_int_vector(ivec);  
        // reset all the elements in ivec to 0  
        /* 
            // 使用下标 
        for (std::vector<int>::size_type ix = 0; ix != ivec.size(); ++ix) 
        { 
            ivec[ix] = 0; 
        } 
        */  
        // equivalent loop using iterators to reset all the elements in ivec to 0  
        for (std::vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); ++iter)  
            *iter = 0; // set element to which iter refers to 0  
        print_int_vector(ivec);  
        return 0;  
    }   

    const_iterator

    C++为每种容器类型定义了一种名为const_iterator的类型,该类型只能用于读取容器内的元素,但不能改变其值。
    对const_iterator类型解引用,得到的是一个指向const对象的引用。

    for (vector<string>::const_iterator iter = text.begin(); iter != text.end(); ++ iter){  
             cout << *iter << endl; //ok: print each element in text  
             *iter = " ";     // error: *iter is const  
         }  

      const_iterator可以用于const或者非const容器(因为不能修改对象的值),但是const的iterator只能用于非const容器(只能修改唯一指向的值)。

    不要把 const_iterator 对象与 const 的 iterator 对象混淆起来。声明一个 const 迭代器时,必须初始化迭代器。一旦被初始化后,就不能改变它的值

    vector<int> nums(10); // nums is nonconst  
    const vector<int>::iterator cit = nums.begin();  
    *cit = 1; // ok: cit can change its underlying element  
    ++cit; // error: can't change the value of cit  
    

      

    const_iterator 对象可以用于 const vector 或非 const vector,因为不能改写元素值。const 迭代器这种类型几乎没什么用处:一旦它被初始化后,只能用它来改写其指向的元素,但不能使它指向任何其他元素
     

    总结

    1、const_iterator需要注意:这个vector本身还是可变的,只不过对const_iterator类型解引用的对象不可变。
    2、const迭代器也就是只能指向其所指向的元素,不能通过++等操作去指向其他元素。但是,所指向这个元素可以改变
     

    迭代器的算术操作

    1、可以对迭代器对象加上或减去一个整形值。这样做将产生一个新的迭代器,其位置在 iter 所指元素之前(加)或之后(减) n 个元素的位置。加或减之后的结果必须指向 iter 所指 vector 中的某个元素,或者是 vector 末端的后一个元素。加上或减去的值的类型应该是 vector 的 size_type 或 difference_type 类型,例子
    2、iter1 - iter2:
    该表达式用来计算两个迭代器对象的距离,该距离是名为 difference_type 的 signed 类型 size_type 的值,这里的 difference_type 是 signed 类型,因为减法运算可能产生负数的结果。该类型可以保证足够大以存储任何两个迭代器对象间的距离。iter1 与 iter2 两者必须都指向同一 vector 中的元素,或者指向 vector 末端之后的下一个元素。、
    3、可以用迭代器算术操作来移动迭代器直接指向某个元素,例如,下面语句直接定位于 vector 中间元素
     
    vector<int>::iterator mid = vi.begin() + vi.size() / 2
    上述代码用来初始化 mid 使其指向 vi 中最靠近正中间的元素。这种直接计算迭代器的方法,与用迭代器逐个元素自增操作到达中间元素的方法是等价的,但前者的效率要高得多。
    4、任何改变 vector 长度的操作都会使已存在的迭代器失效。例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了。
    请看例子:
    *iter = i; // set element to which iter refers to i  
    ivec.push_back(i*2);  
    

      加上这句代码没问题,正确运行,但是,我们试图在for循环里面执行,即

    {  
        *iter = i; // set element to which iter refers to i  
        ivec.push_back(i*2);  
    }  
    

      则会莫名其妙退出!

    参考博客:

    http://lib.csdn.net/article/cplusplus/25895

    http://blog.csdn.net/jxh_123/article/details/30793397?utm_source=tuicool&utm_medium=referral

    http://blog.csdn.net/xxin_w/article/details/20551939

    http://www.cnblogs.com/besty/p/4039264.html

    http://ceeji.net/blog/cpp-learn-iterator/

    http://blog.csdn.net/zhanh1218/article/details/33340959

    http://blog.csdn.net/u014800748/article/details/47401429

    感谢The_Third_Wave

  • 相关阅读:
    【练习】rust中的复制语义和移动语义
    【VictoriaMetrics】vm单机版和vmstorage的查询功能的对比
    分布式ID生成器
    Python与设计模式
    在Go语言项目中使用Zap日志库
    Gin框架使用Zap日志库
    Go语言Viper配置管理神器
    goimports配置
    gin框架中间件详解
    validator库参数校验若干实用技巧
  • 原文地址:https://www.cnblogs.com/zhaobinyouth/p/6216294.html
Copyright © 2020-2023  润新知