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

原因:

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

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

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

 

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

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

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

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

template<class T>

class ListItem

{

public:

    T vaule(){return _value;}

    ListItem*    next() const {return _next;}

private:

    T    _value;

    ListItem*    _next;

}

template<class T>

class List

{

public:

    void Insert_begin(T value);

        void  Insert_end(T value);

        T*    begin();

        T*    end();

private:

     ListItem<T>*    _end;

    ListItem<T>*    _begin;  

    long    size;

}

 

template<class T>

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<int> myList;

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

        mylist.insert_begin(i);

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

    ListIter<ListItem<int> >    end(myList.end());    

    ListIter<ListItem<int> >    it;

    it = find(begin,end,4);

}

vector<int>::iterator

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

 

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

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

template<class T>

class ListIter

{

public:

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

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

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

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

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

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

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

private:

        ListItem<T>* _ptr;

}

template<class T>

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<T>*    _end;

    ListItem<T>*    _begin;  

    long    size;

}

List<int> myList;

List<int>::iterator it = myList.begin();

*it 为int类型

 

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

template<class T>

void FunC(T iter)

{

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

}

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

template<class T,class I>

void FunC(T iter,I t)

{

    I temp;

}

template<class T>

void FunC(T iter)

{

    Func(iter,*iter);    

}

//

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

 

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

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

template<class T>

class ListIter

{

public:

typedef T value;

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

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

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

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

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

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

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

private:

        ListItem<T>* _ptr;

}

template<class T>

typename T::value func(I iter)

{return *iter;}

 

void main()

{

    List<int> myList;

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

   List<int>::iterator  end(myList.end());        

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

}

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

 

2、偏特化

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

int data[200];

template<class T>

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

{

typename iter_traits<T>::value_type  sum;

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

sum += *it;'

return sum;

}

Sum(data,data+200);

template<class _InIt,class _Ty> 

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

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

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

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

template <class Iterator>

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 <class T>

struct iter_traits<T*> {

    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 <class T>

struct iter_traits<const T*> {

    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<class T>

typename iter_traits<T>::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 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;

};

 

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<class InputIterator>

inline typename iter_traits<InputIterator>::difference_type

__distance(InputIterator first, InputIterator last, input_iter_tag)

{

    iter_traits<InputIterator>::difference_type n = 0;

    while (first != last)

    {

        first++;

        n++;

    }

    return n;

}

 

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

template<class RandomAccessIterator>

inline typename iter_traits<RandomAccessIterator>::difference_type

__distance(RandomAccessIterator first, RandomAccessIterator last, random_access_iter_tag)

{

    return last - first;

}

 

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

template<class Iterator>

inline typename iter_traits<Iterator>::difference_type

distance(Iterator first, Iterator last)

{

    return __distance(first, last, iter_traits<Iterator>::iterator_category());

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STL(Standard Template Library)是C++标准库的一部分,它提供了一系列高效、灵活的数据结构和算法,用于处理动态数据。在STL,常见的容器主要包括以下几种: 1. **序列容器**(Sequence Containers): - `std::vector`:动态数组,支持随机访问。 - `std::deque`:双端队列,可以在两端进行高效的插入和删除操作。 - `std::list`:双向链表,元素按插入顺序排列,但查找效率较低。 - `std::forward_list`:单向链表,类似于`list`,但不支持在任意位置插入或删除。 - `std::array`:固定大小的数组,类似C语言的数组。 2. **关联容器**(Associative Containers): - `std::map`(或`std::unordered_map`):关联键值对,使用哈希表实现高效查找。 - `std::set`(或`std::unordered_set`):无序的键集合,不允许重复。 - `std::multiset`:有序的键集合,允许重复。 - `std::multimap`:关联键值对的多值集合,允许多个键对应同一值。 3. **堆容器**(Priority Container): - `std::priority_queue`:堆数据结构,常用于实现优先级队列。 4. **集合容器**(Set-like Containers): - `std::set`:无序集合,使用哈希表实现。 - `std::unordered_set`:无序且无重复的集合。 5. **容器适配器**(Container Adapters): - `std::stack`:栈,基于`vector`或`deque`实现。 - `std::queue`:队列,同样基于`vector`或`deque`实现。 - `std::bitset`:位集,表示一系列二进制位。 STL迭代器是一种抽象概念,它是容器和算法之间通用的接口,使得我们能够遍历容器的元素,而不必关心底层的具体实现细节。迭代器提供了读取和修改容器元素的方法,可以指向容器的开始、结束和间位置。无论是序列还是关联容器,都有相应的迭代器类型,如`iterator`和`const_iterator`等,分别用于读写操作。迭代器的生命周期管理也非常重要,确保它们不会超出容器的有效范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值