关于迭代器的详细说明


1.   迭代器是什么?

我们先从一个例子说起:

int a[10], *p;

for (p = &a[0]; p !=&a[10], ++p) std::cout << *p << std::endl;

在上述for语句构成的迭代操作中,指针p首先指向了数组a的首元素,然后通过++运算和!=运算来完成迭代。这里,指针p“知道”它指向了一片连续的存储区,所以++运算就有意义了。

这里,指针p可以视为一个最为简单的迭代器。程序员可以通过该“迭代器”方便灵活地访问容器(即a数组)。

当然,这里所说的容器(a数组)过于简单。对于更复杂的情况,迭代器被定义为一个某种容器类的伴随类,一般定义在容器类的内部,具有public属性,是其包围容器类的友元。它可以在容器类的外部使用。

在多数情况下,迭代器内部有一个内部普通指针,它可以指向容器类的内部存储机构。而迭代器类本身模拟了一个指针的行为,为此它重载了用于指针操作的三个关键运算符:

l  !=

l  ++

l  *

为了使用迭代器,其包围容器类提供了两个public成员:

l  begin(),它返回一个迭代器对象,并使该对象的内部指针指向了容器对象的存储结构的首元素;

l  end(),它返回一个“空”迭代器对象,该对象的内部指针指往往是个nullptr。而在一般情况下,容器对象的内部存储结构都用nullptr标识存储的结尾,所以可以说,空迭代器对象的内部指针指向了存储结构中最后一个元素的“后”一个元素。这个迭代器对象往往称为“哨兵”。注意:空迭代器仅仅参与了比较运算(及两个迭代器内部指针的比较运算),而没有引用该迭代器对象“指向的对象”,因此在使用上是安全的。

 

2.   为什么需要迭代器

对于内部结构相对复杂的容器,我们不能使用指针直接指向其内部存储结构去访问它,因为这样直接破坏了数据封装的原则。

因此,一个折中的方法是为容器类设计一个遍历成员traverse(),它带有一个谓词函数作为参数去访问每一个容器元素。

然而,这虽然实现了数据封装,却是遍历这类的迭代操作变得很不灵活,因为程序员可能不得不编写多种谓词函数去处理不同的情况。

迭代器的引入可以彻底解决上述种种不便。迭代器模拟了一个指针的行为,配合容器提供的begin()和end(),程序员可以像使用指针那样去使用迭代器,

而不需要直到容器的内部结构,也不需要编写太多的谓词函数,从而起到两全其美的效果。

 

3.   如何定义迭代器

例如,对于教材上使用链式存储的容器类List,其迭代器的定义如下:

class list

{

public:

    typedef int _elemType; //元素的类型

 

private:

    struct _NODE_ //链表的节点。这个用于存储的类型定义在private段中,因为类的外部无法获悉其存储结构的详情

    {

        _elemType data;

        _NODE_* next;

    } _head;

    typedef _NODE_* _NODEPTR_;

 

public:

    typedef _NODEPTR_  _range; //_range是_NODE_*的别名

    //省略了其它容器成员

 

    friend class iterator; //因为该迭代器类要访问List类的私有类型_NODE和其内部数据

    class iterator

    {

    private:

        _range _p; //迭代器的内部指针

 

    public:

        iterator(range = nullptr) : _p(range){}

 

        iterator& operator++()

        {

            _p = _p->next; //内部指针指向下一个节点。根据容器类存储结构的不同,该语句也不尽相同

            return *this;

        }

 

        bool operator!=(const iterator& i)

        {

            return _p != i._p; //两个指针的比较,判断两个指针是否指向了同一个节点

        }

 

        _elemType operator*()

        {

            return _p->data;

        }

 

        iterator& operator=(constiterator& i)

        {

            _p = i._p;

            return *this;

        }

    };

 

    iterator begin() const

    {

        return iterator(_head); //返回的迭代器对象(的内部指针)指向了容器存储结构的首元素

    }

 

    iterator end() const

    {

        return iterator(); //返回的迭代器对象(的内部指针)是nullptr

    }

};

 

在使用迭代器时,就像使用指针那样方便:

List l{1, 2,3, 4, 5};

 

List::iteratoritr; //iterator是List类的内部类型,所以必须说明成这样。注意:iterator类定义在public段中

for (itr =l.begin(); itr != l.end(); ++itr) std::cout << *itr << std::endl;

如果使用C++ 11标准,上述for语句还要更为简单:

for (auto e :l) std::cout << e << std::endl;

这条语句等价于:

for(List::iterator itr = l.begin(); itr != l.end(); ++itr)

{

    _List::elemType e = *itr;

    std::cout << e << std::endl;

}

 

4.   伪迭代器

如果容器类的内部存储机构是类似于数组的、连续存储的线性结构,那么迭代器可以不用那么复杂:它可以不是一个类,而只是指针。

例如:

class Vect

{

public:

    typedef int _elemType;

    typedef _elemType* _range;

 

private:

    _range comp; //容器的内部存储是一片用new分配的指针内存,其行为类似于数组

    size_t len;

 

public:

    typedef _range iterator; //由于知道容器是连续存储,因此不需要定义迭代器类,而只是为指针去一个迭代器别名

 

    iterator begin() { return comp; } //返回的“迭代器”指向数组的首元素

    iterator end() { return comp + len; }//comp + len - 1指向了数组的尾元素,而comp + len指向了改尾元素的“后”一个元素

    //这里,编译器知道!=、++和*运算符作用于指针之上是什么含义,因此不需要重载这些运算符

};

这种伪迭代器的使用与真正迭代器的使用没有区别。

 

5.   迭代器的种类

实际上,除了上述讲到的迭代器之外,容器类还有常量迭代器、反向迭代器等等,用于不同的场合。

更多的细节请查阅相关资料。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值