C++中STL为什么要使用迭代器?

原因:

1、通过迭代器访问容器,可以避免许多错误,同时还能隐藏容器的具体实现。

2、迭代器可以保证对所有容器的基本遍历方式,都是一样的,实现算法时若需要遍历,则使用迭代器,则可以不用关注容器的具体类型,实现数据结构和算法的分离。

3、迭代器本身有很多优点,可以弥补C++语言的不足,比如它的iterator_category,可以得到迭代器所指向的类别,这样可以根据不同的类别的特性,提供不同的算法。

 

迭代器是一种抽象的设计概念,在设计模式一书中的定义为:提供一种方法,使之能够依序的访问某个聚合物(容器)中所含的各个元素,而又不需要暴露该聚合物中的内部实现细节。也就是说迭代器能够访问容器内部实现,而不需要管容器中是怎么实现的。

STL中将容器与算法分开实现,容器负责数据的存储,算法代表着用系统的方法描述解决问题的策略机制。也就是说一个输入-处理-输出的流程,其中在处理过程中就需要对输入的数据能够进行读取或者修改等。而算法中的输入为某些容器时,算法可能就需要知道容器能够提供对其的访问函数。但是每个容器的访问函数都不一定相同,这时候如果针对每一个容器,算法都需要有特定的版本,这样会造成大量的代码冗余。那么怎么办呢?

我们可以在容器与算法之间,用迭代器将其撮合在一起,算法通过迭代器来访问容器,而无需关心容器内的具体实现。通过设计一个抽象的迭代器接口定义一个统一的实现方式。由于每一个容器中的内部存储模式都不一样,因此对于每一个类别的容器都需要有一个专属迭代器来完成对数据的访问,该迭代器继承并实现于抽象的迭代器接口。

那么我们可以简单的实现一个容器及迭代器,应用到STL算法函数中。

template

class ListItem

{

public:

    T vaule(){return _value;}

    ListItem*    next() const {return _next;}

private:

    T    _value;

    ListItem*    _next;

}

template

class List

{

public:

    void Insert_begin(T value);

        void  Insert_end(T value);

        T*    begin();

        T*    end();

private:

     ListItem*    _end;

    ListItem*    _begin;  

    long    size;

}

 

template

class ListIter

{

public:

    ListIter(T* p = NULL) : _ptr(p){}

    //实现取值、取地址、自加、等于、不等于等重载函数

    T&    operator*() const {return *_prt;}

    T*    operator->() const {return _ptr;}

    T&    operator++() {_ptr = _ptr->next();return *_ptr;}

    bool  operator==(const T& i) {return _ptr==i._ptr;}

    bool  operator!=(const T& i) {return _ptr!=i._ptr;}

private:

        T* _ptr;

}

void main()

{

    List myList;

    for (int i = 0; i < 10; ++i)

        mylist.insert_begin(i);

       ListIter >    begin(myList.begin())    //这里迭代器实际上是一个指针,那么指针类别就需要和容器存储的类别一致

    ListIter >    end(myList.end());    

    ListIter >    it;

    it = find(begin,end,4);

}

vector::iterator

由于find里面实现,主要使用 取迭代器的值与给定值判断是否不相等来实现的,即 *it != value;此时*it类别为ListItem 而value类别为int,两者之间并没有可供使用的operator!=。因此需要写一个全局函数的operator!=函数。

 

在这个迭代器中,我们暴露了许多容器的实现细节,如果不是为了使用迭代器,这些细节应该要被隐藏起来,换句话说,既然实现迭代器就需要了解容器的实现细节,那么我们为什么不让容器的设计者去实现迭代器呢?

这样所有细节反而得以封装起来不被使用者看到。

template

class ListIter

{

public:

    ListIter(ListItem* p = NULL) : _ptr(p){}

    //实现取值、取地址、自加、等于、不等于等重载函数

    T&    operator*() const {return _prt->value;}

    T*    operator->() const {return _ptr->next();}

    ListItem&    operator++() {_ptr = _ptr->next();return *_ptr;}

    bool  operator==(const  ListItem& i) {return _ptr->value()==i->value();}

    bool  operator!=(const  ListItem& i) {return _ptr->value()!=i->value();}

private:

        ListItem* _ptr;

}

template

class List

{

public:

    typedef ListIter<ListItem<T> >    iterator;

    void Insert_begin(T value);

        void  Insert_end(T value);

        iterator    begin(){return iterator(_begin);}

        iterator   end(){return iterator(_end);}

private:

     ListItem*    _end;

    ListItem*    _begin;  

    long    size;

}

List myList;

List::iterator it = myList.begin();

*it 为int类型

 

上面的迭代器提供了一个雏形,但是在算法中,我们有时候需要知道迭代器所指之物的类别,如下

template

void FunC(T iter)

{

    //这里我们需要定义一个临时变量,类型为迭代器所指之物的类别。那么该怎么办呢

}

//我们可以函数模板的推导机制来实现

template

void FunC(T iter,I t)

{

    I temp;

}

template

void FunC(T iter)

{

    Func(iter,*iter);    

}

//

这样可以解决一部分问题,但是如果该函数有返回值呢?那怎么办呢?最常见的迭代器相应类别有五种,然而并非任何情况下任何一种都可以利用上述的模板参数推导机制来获取。我们需要更全面的解法。

 

Traits编程技术就是用来解决这个问题的?

1、如果函数有返回值,我们应该怎么样获取其类型。我们可以在迭代器中声明一个内嵌类别

template

class ListIter

{

public:

typedef T value;

    ListIter(ListItem* p = NULL) : _ptr(p){}

    //实现取值、取地址、自加、等于、不等于等重载函数

    T&    operator*() const {return _prt->value;}

    T*    operator->() const {return _ptr->next();}

    ListItem&    operator++() {_ptr = _ptr->next();return *_ptr;}

    bool  operator==(const  ListItem& i) {return _ptr->value()==i->value();}

    bool  operator!=(const  ListItem& i) {return _ptr->value()!=i->value();}

private:

        ListItem* _ptr;

}

template

typename T::value func(I iter)

{return *iter;}

 

void main()

{

    List myList;

    ListIter    it(myList.begin());  // 此时*it类型为ListItem

   List::iterator  end(myList.end());        

    int i = Func(it);        //返回值为int型

}

func的返回值必须加上关键词typename,因为T是一个模板参数,在它被编译器具现化前,编译器并不知道ListIter::value是一个成员函数还是数据成员,关键词typename的用意在于告诉编译器这是一个类别。

 

2、偏特化

并不是所有的迭代器都是class type,比如原声指针如Int*,我们知道STL中的算法也适应于数组,而数组就是一个原声指针。

int data[200];

template

typename iter_traits::value_type  Sum(T iter, T iter2)

{

typename iter_traits::value_type  sum;

for (T it = iter; it != iter2; ++it)

sum += *it;'

return sum;

}

Sum(data,data+200);

template 

inline _InIt find(_InIt _First, _InIt _Last, const _Ty& _Val)

此时传进来的迭代器类别为int*,在find函数中会通过萃取得到该迭代器所指值的类型。

将偏特化应用在迭代器设计中,使迭代器既可以萃取出值类型,又可以包容原生指针

// 以迭代器为模板参数,用于萃取相应的型别

template

struct iter_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

struct iter_traits {

    typedef typename random_access_iter_tag iterator_category;

    typedef typename T value_type;

    typedef typename ptrdiff_t difference_type;

    typedef typename T* pointer;

    typedef typename T& reference;

};

 

// 原生const的偏特化版

template

struct iter_traits {

    typedef typename random_access_iter_tag iterator_category;

    typedef typename T value_type;

    typedef typename ptrdiff_t difference_type;

    typedef typename const T* pointer;

    typedef typename const T& reference;

};

//如果要获取模板类型,我们只需要使用萃取来得到其类型如:

template

typename iter_traits::value_type  func(I iter)

{ return *iter;}

2、type_traits类型萃取,对待特殊类型,特殊处理,提高效率

对于没有构造函数,析构函数等的内置类型,如果与复杂类型一样,执行同样的操作,显然是效率不高的

先实现一个对所有类型都设置一个最保守值的type_traits模板类,然后再对每个内置类型设置偏特化版本,内置类型设置一个更为激进的值,表明可以采取更为高效的操作来提高效率

比如copy函数,如果传递的对象是一个复杂类型,那么可能只能采取最保守的处理方式,一个一个的构造;如果是内置类型,这样显然太低效,使用memcpy()可能会好一些

其实iterator_traits也不止是处理兼容原生指针的问题,它也可以提高效率.

迭代器分为很多种,有可以随机访问的(vector),有只能前后一个一个移动的(list),也有只能单向移动的(slist),所以一般把迭代器分为五种:

InputIterator       输入迭代器

OutputIterator        输出迭代器

ForwardIterator      单向迭代器

BidirectionIterator     双向迭代器

RandomAccessIterator   随机访问迭代器

比如一个__distance(n)函数,对于单向迭代器只能一个一个移动过去,而随机迭代器,可以很快速的计算出距离。

处理的方式就是先实现这五个类,用作标记用,在每个迭代器里面都定义迭代器类型的类型成员iterator_catagory,再对不同版本的迭代器实现不同的advance(n)处理方式

// 这是迭代器的基类 所有迭代器必须定义这5种型别

template

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;

};

 

5种迭代器类型,只是一个标志,用于根据萃取出来的iterator_category来选择相应的算法。

struct input_iter_tag {};

struct output_iter_tag {};

struct forward_iter_tag : public input_iter_tag {};

struct bidirectional_iter_tag : public forward_iter_tag {};

struct random_access_iter_tag : public bidirectional_iter_tag {};

 

// input迭代器只支持operator++

template

inline typename iter_traits::difference_type

__distance(InputIterator first, InputIterator last, input_iter_tag)

{

    iter_traits::difference_type n = 0;

    while (first != last)

    {

        first++;

        n++;

    }

    return n;

}

 

// random access迭代器支持迭代器之间的加减法

template

inline typename iter_traits::difference_type

__distance(RandomAccessIterator first, RandomAccessIterator last, random_access_iter_tag)

{

    return last - first;

}

 

// 根据迭代器的iterator_category来调用适合的版本

template

inline typename iter_traits::difference_type

distance(Iterator first, Iterator last)

{

    return __distance(first, last, iter_traits::iterator_category());

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(C++)