用法 stl_STL迭代器 基础篇



引言

  • 想必自己实现过C数据结构的同学,都深有体会,如果底层使用线性表实现存储,则遍历可以直接通过索引查询数据,但是如果通过链表实现存储,则遍历要通过类似next()指针不断的移动来比例不同的节点。
  • STL容器底层存储不一,但是遍历却是那么的统一。不知使用STL容器进行遍历的时候,是否想过,为什么呢?怎么搞的?

概念

  • 迭代器是一种抽象的设计概念,iterator模式定义如下:
    提供一种方法,使之能够依序访问某个聚合物所含的各个元素,从而无需暴露该聚合物的内部表述方式
  • 使用STL能够使泛型化的容器和算法撮合在一起
  • 迭代器是一种类似于指针的存在,迭代器最重要就是对operator*operator->进行重载。直接看下面的用法例子
int a[] = {1, 2, 3, 4};
vector<int> v(a, a + sizeof(a) / sizeof(int));
//使用迭代器遍历
vector<int>::iterator t = v.begin();
for(; t != v.end(); ++t) {
    cout << *t << endl;
}
  • 可以通过上面的例子看到,通过t我们可以直接像使用指针一样遍历vector,而不需要关注vector的实现。毕竟对链表的访问和数组的访问是不同的,但是我们都可以通过++t去递增。

初探iterator

  • 这一节将会通过一个小的daemon(链表)来理解迭代器
//链表节点
template <typename T>
class ListItem {
public:
    ListItem* next();
private:
    T m_value;
    ListItem* m_next;
};

//链表
template <typename T>
class List {
public:
    void insert_front(T value);
private:
    ListItem<T>* m_end;
    ListItem<T>* m_front;
};

//迭代器
template <class Item>
struct ListIter {
    Item  *m_ptr;
    //前置++
    ListIter& operator++() {
        m_ptr = m_ptr->next(); 
        return *this;
    }
    //后置++
    ListIter operator++(int) {
        ListIter tmp = *this;
        ++*this;
        return tmp;
    }
    Item& operator*() const {
        return *m_ptr;
    }
    bool operator !=(const ListIter& i) const {
        return m_ptr != i.m_ptr;
    }
};

void daemon() {
    List<int> my_list;
    for(int i=0; i<5; ++i) 
        my_list.insert_front(i);

    ListIter<ListItem<int>> t = my_list.begin();
    for (; t != my_list.end(); ++t) {
        cout << *t << endl;
    }
}
  • 从上面的例子我们可以看到:
  1. 迭代器的实现有针对性,比如此处的ListIter只能针对list,如果想针对vector,那内部许多函数又需要重新设计实现。
  2. 要实现List的迭代器,我们无可避免的暴露了太多List的实现细节,比如:ListItem, next()。如果不是为了迭代器,这些本可以不用暴露的。
可以看出要设计出ListIter首先要对List的内部有详细的了解,因此ListIter的设计和开发理所应到的交给了List的设计者。提前设计一套iterator规范,然后让容器的实现者去实现容器对应的iterator。STL每种容器都有自己的迭代器,但是它们都实现了相同的接口。在使用者看来,迭代器的使用时统一的。

深入iterator

  • 上面章节通过一个简单的例子,提供了一个迭代器的雏形。但是距离STL的迭代器还相差很多,下面我们继续深入

traits

  • 在迭代器使用的时候,我们可能会想要知道迭代器所指对象的型别, 比如:所指对象的类型
  • 一种解决拌饭是使用function template的参数推导机制,如下:
int main() {
    int i;
    func(&i);
}

//此处I 为 int*
template<class I>
void func(I iter) {
    func1(iter, *iter);
}

template<clas I, class T>
void func1(I iter, T t) {
    T tmp;  //T就是迭代器所指之物的型别,此处为int
}
  • 根据经验,一般这种型别有五种,并不是所有的都能通过上面这种方法解决。我们需要更加通用的解决办法,正好,通过声明内嵌型别可以完成,如下:
template <class T>
struct MyIter {
    typedef T value_type;  //内嵌型别

    T *m_ptr;
};

template <class I>
//此处typename用于高速编译器这是一个型别,不能删除
typename I::value_type func(I ite) {
    return *ite;
}
  • 但是对于所有的迭代器都是 class type, 原生指针就不是。但是STL又必须接受原生指针作为迭代器。解决办法就是偏特化,就是为每种原生指针提供一个特化版本。如下:
template <typename T>
class C<T*> {
};
  • 如果我们想要使用迭代器的型别,肯定不能直接通过迭代器来获取,因为无法处理原生指针的问题,因此,我们需要一个工具,可以提供统一的接口,来获取所有迭代器的型别。这既是traits,也叫萃取器,如下:
//I为迭代器,是class type类型
template <class I> 
struct iterator_traits {
    //迭代器类型
    typedef typename I::iterator_category   iterator_category;
    //迭代器所指对象的型别
    typedef typename I::value_type          value_type;     
    //两个迭代器之间的距离,可以表示一个容器的最大容量
    typedef typename I::difference_type     difference_type;
    //左值传递,地址
    typedef typename I::pointer             pointer;
    //左值传递,引用
    typedef typename I::reference           reference;
};
//针对原生指针,需要进行偏特化
template <class T>
struct iterator_traits<T*> {
    typedef random_access_iterator_tag      iterator_category;
    typedef T                               value_type;
    typedef ptrdiff_t                       difference_type;
    typedef T*                              pointer;
    typdef T&                               reference;

};
//针对原生const指针
template <class T>
struct iterator_traits<const T*> {
    typedef random_access_iterator_tag      iterator_category;
    typedef T                               value_type;
    typedef ptrdiff_t                       difference_type;
    typedef const T*                        pointer;
    typdef const T&                         reference;
};

分类

  • 根据移动特性与实施操作,迭代器可以分为五类:
  1. input iterator
  • 只读,迭代器所指对象不允许外界改变。支持opeator ++
output iterator
  • 只写,支持opeator ++
forward iterator
  • 读写,允许‘写入型’算法,通过迭代器读写.支持opeator ++
bidirectional iterator
  • 可双向移动,某些算法可能需要逆向访问迭代器。支持opeator ++operator--
random access iterator
  • 支持多种运算,包括p+n, p-n, p[n], p1-p2, p1<p2
  • 五种迭代器分类,依次强化
  • 设计算法时,我们尽量针对某种迭代器提供一个明确的定义,这样才能在不同的情况下提供最大的效率。将入有个算法可以接受 forward iterator, 使用random access iterator当然也可以,但是并不是最佳的。
  • 下面的例子可以加深我们的理解
//逐步前进
template <class InputIterator, class Distance>
void advance_1(InputIterator& i, Distance n) {
    while (n--)
        ++i;
}
//双向前进
template <class BidrectionalIterator, class Distance>
void advance_2(BidrectionalIterator& i, Distance n) {
    if (n >= 0)
        for (; n>0; --n, ++i);
    else
        for (; n<0; ++n, --i);
}
//跳跃前进
template <class RandomAccessIterator, class Distance> 
void advance_3(RandomAccessIterator& i, Distance n) {
    i += n;
}
  • 上面的例子可以看出,对于RandomAccess Iterator而言,使用advance_1,原本O(1)编程了O(n)。因此我们需要根据迭代器的类型,来选择不同的版本,这样可以提高效率如下:
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n) {
    if (is_random_access_iterator(i))
        advance_3(i, n);
    else if (is_bidirectional_iteator(i))
        advance_2(i, n);
    else
        advance_1(i, n);
}
  • 如果执行期间才决定使用哪个版本,会影响效率,最好是在编译期就选择正确的版本,刚好,重载函数机制可以办到。相应的我们需要修改advance函数集,添加iterator型别,如果再配合traits就更加完美。改进后的算法如下:
//五种类型iterator的定义,只用于标记类型,因此不需要任何成员
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 {};

//逐步前进
template <class InputIterator, class Distance>
void _advance(InputIterator& i, Distance n, input_iterator_tag) {
    while (n--)
        ++i;
}
template <class InputIterator, class Distance>
void _advance(InputIterator& i, Distance n, forward_iterator_tag) {
    _advance(i, n, input_iterator_tag());
}
//双向前进
template <class BidrectionalIterator, class Distance>
void _advance(BidrectionalIterator& i, Distance n, bidirectional_iterator_tag) {
    if (n >= 0)
        for (; n>0; --n, ++i);
    else
        for (; n<0; ++n, --i);
}
//跳跃前进
template <class RandomAccessIterator, class Distance> 
void _advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) {
    i += n;
}
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n) {
    //iterator_traits<InputIterator>::iterator_category()会产生一个临时的对象
    _advance(i, n, iterator_traits<InputIterator>::iterator_category());
}

template <class I>
inline typename iterator_traits<I>::iterator_category iterator_category(const I&) {
    typedef typename iterator_traits<I>::iterator_category category;
    return category();
}

规范

  • STL规范要求,任何迭代器都应该提供五个内嵌型别,以便traits萃取。STL提供了一个iterators class,如下,新设计的迭代器继承它,就能保证符合STL规范。
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不包含任何成员,只是型别定义,所以继承它不会有任何负担。使用如下:
template <class Item>
struct ListIter : public std::iterator<std::forward_iterator_tag, Item> {};
  • 总结:
  1. 设计适当的型别是迭代器的责任。
  2. 设计适当的迭代器是容器的责任。因为只有容器才知道怎么遍历自己。

SGI __type_traits

  • STL只对迭代器指定了iterator_traits, 而SGI把这种技术扩大到了迭代器以外,也就是所谓的__type_traits
  • 此处的型别指
  1. non-trivial defalt ctor? 构造
  2. no-trivial copy ctor? 拷贝
  3. non0-trivial assignment operator? 赋值
  4. non-trivial dtor? 析构
__type_traits<T>::has_trivial_default_constructor
__type_traits<T>::has_trivial_copy_constructor
__type_traits<T>::has_trivial_assignment_operator
__type_traits<T>::has_trivial_destructor
__type_traits<T>::is_POD_type

//以上只需要知道 '真' 或 '假'

struct __true_type {};
struct __flase_type {};
  • 有了以上这些__type_traits定义如下:
template <class type>
struct __type_traits {
    typedef __true_type     this_dummy_member_must_be_first;
    typedef __false_type    has_trivial_default_constructor;
    typedef __false_type    has_trivial_assignment_operator;
    typedef __false_type    has_trivial_destructor;
    typedef __false_type    is_POD_type;
};
  • SGI 将所有值定义为__false_type,是因为定义最保守的值 。然后通过定义特化版本来优化比如
__STL_TEMPLATE_NULL struct __type_traits<unsigned int> {
    /*它通知有能力__type_traits特化的编译器,当前__type_traits是特殊的。为了保证编译器使用一个同名的但功能与此无关联的template时,所有的时期都能够顺利运行。
    */
    typedef __true_type     this_dummy_member_must_be_first;
    typedef __true_type     has_trivial_default_constructor;
    typedef __true_type     has_trivial_assignment_operator;
    typedef __true_type     has_trivial_destructor;
    typedef __true_type     is_POD_type;
};
  • 某些编译器会自动为所有型别提供偏特化版本。
  • 在一些算法中会通过使用__type_traits来提高效率,因为可以跳过没有必要的拷贝,构造,析构,赋值
  • 如果我们自己定义了一个类,我们可以通过定义它的偏特化版本,告诉编译器,在使用一些算法时使用效率高的版本。否则将全部默认为__type_false

完结

  • 关于迭代器的初步了解就在这里。更深的了解会在终极篇中和大家分享
  • 最后向大家推荐 《STL源码剖析这本书》,非常适合STL迭代器原理的入门学习。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值