• C++STL为什么要有const_iterator


    这是个困扰我很久了的问题,可能一开始对面向对象的理解不够深。

    刚刚想明白了,随手记录一下。


    先从const iterator和const_iterator说起

    const iterator 是iterator本身是个常量,iterator本身里面存的是指针,也就是iterator的值,也就是那个指针不能改变,也就是不能指向其他的位置,但是所指向的位置的元素是可以通过这个iterator来改变的。

    const_iterator 其实本质来说,是另一个类。我们可以想象成,它的数据成员是一个指向常量元素的指针,(比如 const T*)也就是说,这个const_iterator里存着的指针是可以改变的,即可以 ++ 或 - - 操作,但是,这是一个指向常量的指针,指向的元素是常量,不可改变。

    有点绕,我们再从一个例子扒一扒。

    vector<int> ivec1(10);
    const vector<int> ivec2(10);
    vector<int>::iterator iter1 = ivec1.begin(); // true
    vector<int>::iterator iter2 = ivec2.begin();  // error
    vector<int>::const_iterator iter3 = ivec2.begin(); // true
    vector<int>::const_iteartor iter4 = ivec1.begin();  // true

    通过上面这个例子,我们就可以更直观的再深入理解一下了。
    因为ivec2本身就是一个常量的vector,所以ivec2里面的元素必然是不能被改变的。如果直接定义一个iterator是会报错的,因为iterator意味着这个iterator可以遍历容器,且可以改变容器元素,显然如果容器被定义成常量了之后,这个iterator是不合理的。

    所以这才有了const_iterator的出现。作为一个迭代器,遍历元素是必须要的,不然就丧失了一个迭代器的意义了。但是由于常量容器的存在,iterator不能满足这个需求。const_iteartor代表的就是不能改变容器元素,但是可以遍历容器的迭代器。

    但是const_iterator不仅仅是只针对已经被声明为常量的容器用的,如果一个非常量容器,但是你不想改变容器的元素,那么也可以用const_iterator,因为里面存的是一个指向常量的指针。

    最后再来看一下底层实现,更深入的扒一扒。

    直接看最简单的vector容器吧,list和deque要更加复杂一些,但是本质都是一样的。(list是指向node节点的指针,duque的iterator更加复杂,因为它的内存分配是一段一段的,详情可见STL源码或者我的github www.github.com/linxiaoye/TinySTL)

    /*vector的数据结构一般是这样的*/
    T* start;
    T* finish;
    size_t n;
    
    typedef T            value_type;
    typedef T*           iterator;
    typedef const T*     const_iterator;
    
    iterator begin() { return start; }     // ①
    const_iterator begin() const { return start; }  // ②
    const_iterator cbegin() const { return start; }  // ③

    我们可以看到,vector里面是这样实现的。
    begin()有两个实现,外加一个cbegin(),有三个实现。
    ①就是最一般的实现,可遍历,可改变元素值。一般用于非常量容器。
    ②是可遍历,不可改变元素值,一般用于常量容器。当你对一个常量容器直接调用begin(),就是调用的②。调用值得一提的是,它这个不可改变元素值是全方位表现出来了的。从 typedef const T* const_iterator 开始,就限制了这是一个指向常量的指针,不可改变元素值;②中返回值是const_iterator也说明了返回的是一个存有指向常量的指针的迭代器;cosnt成员函数也说明了,不能通过这个函数来改变元素的值。
    ③其实跟②是几乎一样的,只是函数名不同,也就是说,当你实例化一个常量vector时,你调用begin() 和 cbegin()其实是一样的,没有区别。但是,当你定义的是一个非常量容器时,你想调用一个不能改变所指向元素的迭代器,那么就应该调用cbegin(),它可以满足你的要求。

    最后总结一下 

    • iterator 可遍历,可改变所指元素
    • const_iterator 可遍历,不可改变所指元素
    • const iterator 不可遍历,可改变所指元素
    • const_iterator 主要是在容器被定义成常量、或者非常量容器但不想改变元素值的情况下使用的,而且容器被定义成常量之后,它返回的迭代器只能是const_iterator
    • begin() 与 cbegin() 其实在容器被定义成常量之后,本质上是一样的,没有区别,调用哪个都行;但是容器时非常量时,如果你不想改变元素值,就只能调用cbegin()
  • 相关阅读:
    Android Service 启动和停止服务
    Android 子线程中进行UI操作遇到的小问题
    JZ66 机器人的运动范围
    JZ65 矩阵中的路径
    JZ64 滑动窗口的最大值
    JZ63 数据流中的中位数
    Z62 二叉搜索树的第k个结点
    JZ61 序列化二叉树
    JZ60 把二叉树打印成多行
    JZ59 按之字形顺序打印二叉树
  • 原文地址:https://www.cnblogs.com/yyehl/p/yyehl.html
Copyright © 2020-2023  润新知