STL中的迭代器

来源:互联网 发布:mac如何更改文件夹 编辑:程序博客网 时间:2024/05/19 19:39

最近在学习侯捷老师的STL源码剖析,看到迭代器这里突然有些疑惑,不知道为什么会存在
迭代器? 只知道迭代器是STL库中的算法和容器的胶合剂。

下面我们就来一步一步探究下为什么会存在迭代器。

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

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

答案是肯定的,“智能指针”(smart pointer)正解决此类问题,尤其是在防止内存泄漏
方面做得非常突出,C++标准库std中提供了一种“智能指针”名为“auto_ptr”先看下面
的代码:

void f()  {      int *p=new int(42);      ////////此处发生异常////      delete p;  }  

正如上面的代码显示,如果在这两条语句之中发生异常,会导致指针p所指的那块内存
泄露,因为在执行delete之前发生异常,就不会自动释放堆,然而使用auto_ptr对象代替
常规指针,就会自动释放内存,因为编译器能保证出了这个对象的作用域,会自动调用
析构函数。

#include<memory>//auto_ptr的头文件  void f()  {      auto_ptr<int>p(new int (42));  }  

通过以上的分析,我们便对智能指针有了一定的了解,迭代器iterator就是一种智能指针,
它对原始指针进行了封装,并且提供了一些等价于原始指针的操作,做到既方便又安全。

一提到STL,必须要想到其主要的6个组成部件,分别是:容器,算法,迭代器,仿函数,
适配器,和空间配置器。本文主要介绍迭代器,迭代器是连接容器和算法的桥梁,为什么呢?
我们来看个例子。

void Testfind(){    vector<int> v;    for (size_t i = 0; i < 10; i++)    {        v.push_back(i);    }    vector<int>::iterator it=find(v.begin(), v.end(), 5);    if (it != v.end())        cout << "找到了" << endl;    else        cout << "没找到" << endl;}

上述代码中值得注意的是用到了find函数,find函数原型如下:

template <class InputIterator, class T>   InputIterator find (InputIterator first, InputIterator last, const T& val);

参数InputIterator是迭代器,我们实际传的实参为find(v.begin(),v.end(),5);
这样我们的形参迭代器就将算法和容器vector联系起来了,从这个角度来看,
我们可以很容易的理解为什么说迭代器是算法和容器联系的桥梁了。

为什么上面的形参是一个InputItertor,而能够接受实参v.begin()呢?
要回答这个问题,我们就得从迭代器的分类说起。

在STL迭代器主要分为5类,分别是输入迭代器,输出迭代器,前向迭代器,
双向迭代器和随机访问迭代器。

输入迭代器:只读,支持++ == !=;
输出迭代器:只写,支持++;
前向迭代器:读写,支持++ == !=;
双向迭代器:读写,支持++,–,C++的标准库都至少在双向迭代器的层次上。
随机访问迭代器:读写,支持++ ,–,[n] , -n ,< <= > >=;
输入迭代器和输出迭代器,是最低级的迭代器,后面三种迭代器都是对前边的一种派生。

//迭代器的类型struct InputIteratorTag{};  //只读struct OutputIteratorTag{}; //只写struct ForwardIteratorTag :public InputIteratorTag {}; //读写struct BirdirectionalTag :public ForwardIteratorTag{}; //双向struct RandomAccessIteratorTag : public BirdirectionalTag{}; //随机

输入和输出迭代器是最低级的迭代器,后面三种迭代器都是前一种的派生;

想想为什么要对迭代器进行分类呢?主要是泛型算法可以根据不同类别的迭代器,
所具有的不同能力,来实现不同性能的版本,使得能力大的迭代器用于这些算法
时具有更高的效率,较典型的算法就是distance和advance。

回到刚刚的那个问题,为什么实参v.begin()能够与形参InputIterator虚实结合呢?
我们看看下面的代码

class A{}; //相当ForwardIteratorTagclass B :public A{};   //相当BirdirectionalTagclass C : public B{};   //相当RandomAccessIteratorTagvoid Print(A){    cout << "This is base A" << endl;}void Print(C){    cout << "This is base C" << endl;}void TestABC(){    Print(A());    Print(B());    Print(C());}

从上边的代码我们可以看出来,在TestABC函数中,我们调用Print(B()),
即可用派生类对象作为实参传递给以基类类型为形参的函数,所以find
函数之中的v.begin()作为实参,以输入迭代器作为形参,两者可以达到虚实结合的
目的而不会出错。所以说迭代器为各类算法和各类容器提供了一个交互的平台。

在STL中,容器的迭代器被作为容器元素对象或者I/O流的对象的位置指示,因此可以
把它理解为面向对象的指针——– 一种泛型指针 或者通用指针,它不依赖于元素的真实类型。
我们怎么理解“通用”二字的含义呢?

迭代器的“通用”是一种概念上的通用,所有的泛型容器和泛型算法都使用“迭代器”来指示
元素对象,所有的迭代器都具有相同的或类似的接口,但是每一种都有自己的迭代器类型,
毕竟每一种容器的底层存储方式不尽相同,所以迭代器的实现方式就会不同,千万不要
以为存在一种“通用的迭代器”–它可以应用任何类型的容器

注意:切记不能把迭代器与void* 和“基类指针”这样的通用指针混淆
指针代表真正的内存地址,即对象在内存中的存储位置;迭代器则代表元素在容器之中的相对位置。

STL中迭代器原码

struct input_iterator_tag{};//输入迭代器  struct output_iterator_tag{};//输出迭代器  struct forward_iterator_tag:public input_iterator_tag{};//前向迭代器  struct bidirectional_iterator_tag:public forward_iterator_tag{};//双向迭代器  struct random_access_iterator_tag:public bidirectional_iterator_tag{};//随机访问迭代器  //std::iterator,标准迭代器的类模板  template<class Category,class T,class Distance=ptrdiff_t,           class Pointer=T*,class Reference=T&>  struct iterator//迭代器包含五个常用属性  {      typedef Category iterator_category;//迭代器的类型,五种之一      typedef T        value_type;//迭代器所指向的元素的类型      typedef Distance difference_type;//两个迭代器的差值      typedef Pointer  pointer;//迭代器的原始指针      typedef Reference reference;//迭代器所指向元素的引用  };  //定义iterator_traits,用于提取迭代器的属性,该类的对象不应该让用户看到  template<class Iterator>  struct iterator_traits  {      //下面的操作相当于一个递归的操作,用于递归提取原始指针的相关值      typedef typename Iterator::iterator_category iterator_category;      typedef typename Iterator::value_type        value_type;      typedef typename Iterator::difference_type   difference_type;      typedef typename Iterator::pointer           pointer;      typedef typename Iterator::reference         reference;  };  //针对原始指针的偏特化版本  template<class T>  struct iterator_traits<T*>  {      //相当于递归终止条件      typedef random_access_iterator_tag iterator_category;      typedef T         value_type;      typedef ptrdiff_t diffrence_type;      typedef T*        pointer;      typedef T&        reference;  };  //针对指向常用的原始指针设计的偏特化版本  template<class T>  struct iterator_traits<const T*>  {      typedef random_access_iterator_tag iterator_category;      typedef T          value_type;      typedef ptrdiff_t  diffrence_type;      typedef const T *  pointer;//重点在这里      typedef const T &  reference;//还有这里  };  //定义两个迭代器的差值类型的函数distance_type  template<class Iterator>  inline typename iterator_traits<Iterator>::difference_type *  distance_type(const Iterator&)  {      return static_cast<typename iterator_traits<Iterator>::difference_type *>(0);  }  //获取迭代器的value_type函数  template<class Iterator>  inline typename iterator_traits<Iterator>::value_type *  value_type(const Iterator&)  {      return static_cast<typename iterator_traits<Iterator>::value_type*>(0);  }  //求两个一般迭代器之间的距离的函数_distance,供distance函数分类调用  template<class InputIterator>  inline typename iterator_traits<InputIterator>::difference_type  _distance(InputIterator first,InputIterator last,input_iterator_tag)  {      typename iterator_traits<InputIterator>::difference_type n=0;      while(first!=last)      {          ++first;          ++n;      }      return n;  }  //求两个随机访问迭代器之间的距离的函数_distance,供distance函数分类调用  template<class RandomAccessIterator>  inline typename iterator_traits<RandomAccessIterator>::difference_type  _distance(RandomAccessIterator first,RandomAccessIterator last,            random_access_iterator_tag)  {      return last-first;  }  //自适应地调用distance函数  template<class InputIterator>  inline typename iterator_traits<InputIterator>::difference_type  distance(InputIterator first,InputIterator last)  {      typedef typename iterator_traits<InputIterator>::iterator_category category;      //从typename可以看出,category是一个类型      return _distance(first,last,category());//显示调用category类的构造函数,返回该类的一个对象  }  /*****下面的函数用于将指针移动n位的方法*/  //一般迭代器求向前移动的方法,与双向迭代器和随机反问迭代器不同  template<class InputIterator,class Distance>  inline void _advance(InputIterator& i,Distance n,input_iterator_tag)  {      while(n--)      {          ++i;      }  }  //针对双向迭代器移动的方法  template<class BidirectionalIterator,class Distance>  inline void _advance(BidirectionalIterator &iter,Distance n,                       bidirectional_iterator_tag)  {      if(n>=0)//当n大于0时,向后移动      {          while(n--)          {              ++iter;          }      }      else//当n小于0时,向前移      {          while(n++)          {              --iter;          }      }  }  //定义随机访问迭代器移动的方法  template<class RandomAccessIterator,class Distance>  inline void _advance(RandomAccessIterator &iter,Distance n,                       random_access_iterator_tag)  {      iter+=n;  }  //自适应的调用advance函数  template<class InputIterator,class Distance>  inline void advance(InputIterator &iter,Distance n)  {      _advance(i,n,iterator_catetory(iter));  }  

从上面的代码中不难发现,实现一个迭代器,需要做一下工作,

  1. 定义5类迭代器的标志位,该标志位用于实现函数的重载,例如求两个迭代器
    之间距离的函数distance (it1,it2,tag),移动函数advance(iter,n,tag).这五个标志位
    分别为:input_iterator_tag ,output_iterator_tag,forward_iterator_tag,
    bidirectional_iterator_tag,random_access_iterator_tag.

2.对于每一个iterator类,都必须包含五个属性,分别为 iterator_category,
value_type,difference_type ,pointer,reference

3.定义一个迭代器的“属性榨汁机”iterator_traits,用于获取iterator中的5个属性值。

4.定义迭代器的操作,分别为:
a.获取iterator的标志——>iterator_category
b.获取两个迭代器差值的类型——>distance_type
c.获取迭代器的原始类型——->value_type
d.求两个迭代器之间的距离——->distance
e.将迭代器移动n位—–>advance

建议一:尽量使用迭代器类型,而不是显示的使用指针,例如,使用
vector< int>::iterator,而不是int* ,虽然它们等价。
建议二:只使用迭代器提供的标准操作,不要使用任何非标准操作,以避免
STL版本更新时,出现不兼容的问题,
建议三:当不会改动容器中元素的值的时候,请使用const迭代器。