STL学习笔记-迭代器

6 篇文章 2 订阅

一. 迭代器类型

Input迭代器                        istream

Output迭代器                     ostream

Forward迭代器

Bidirectional迭代器           list, set, multiset, map,multimap

Random access迭代器      vector, deque, string, array

注:随机迭代器,提供”迭代器算术运算”,比如:

Iter[n] 索引位置为n的元素

Iter+=n向前跳n个元素

Iter-=n  向后跳n个元素

Iter1 < iter2 判断iter1是否在iter2之前

二. 迭代器相关辅助函数

1. advance(), 可将迭代器的位置增加或减少,增加的幅度由参数决定。

#include <iterator>
void advance (InputIterator& pos, Dist n)
n可以是负值,advance()不检查迭代器是否超过序列end()
2. distance(),用来处理两个迭代器之间的距离。

#include <iterator>
Dist distance (InputIterator pos1, InputIterator pos2)
两个迭代器必须指向同一个容器,如果不是随机迭代器,则pos2的位置必须与pos1相同或在其后
3. iter_swap(), 可交换两个迭代器所指的内容。

#include <algorithm>
void iter_swap (ForwardIterator1 pos1, ForwardIterator2 pos2)
注:交换迭代器pos1和pos2所指的值,迭代器类型不必相同,但其中所指的值必须可以相互赋值。

三. 迭代器配接器

1. Reverse(逆向)迭代器

逆向迭代器用逆向的方式对容器进行所有操作,它将递增运算转换成递减。通过它完成对容器的倒序操作。所有容器都可以通过rbegin()和rend()产生reverse iterators。

#include "stdafx.h"
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
int _tmain(int argc, _TCHAR* argv [])
{
     vector<int > iVec;
     for (int i=1 ; i<= 9; i ++)
     {
          iVec .push_back(i);
     }
     vector<int >::iterator pos;
     pos = find (iVec. begin(), iVec .end(), 5 );
     cout<<"pos :  " <<*pos<<endl ;
   
     vector<int >::reverse_iterator rpos(pos);
     cout<<"rpos : " <<*rpos<< endl;
}

输出结果:
pos :5
rpos :4
将一个双向迭代器转换成逆向迭代器竟然改变了迭代器所指元素的值。这也是逆向迭代器最值得注意的地方。
     STL的逆向迭代器之所以这样实现,是为了保证区域的半开性:为了能够指定容器内的所有元素,我们必须指定最后一个元素的下一个位置。而对于Reserve而言,这个位置位于第一个位置之前,而这个位置可能并不存在,容器并不保证这一点,也就是说,我们可能无法获取rend()。
     也就是说,元素值(实际位置)和迭代器值(逻辑位置)之间必须有所调整,要么保持迭代器值,调整元素值位置,或者保持元素值位置,调整迭代器值。根据前面的例子可以看出,STL采用方案一:保持逻辑位置。直接将原来的第一个位置begin()作为rend() 而将原来的end()作为rbegin()。这样使得rend指向的位置存在元素,而rbegin()指向的位置却不存在元素,因此还需要将元素依次"右移"一位,这样就能保证rend()是"最后一个元素的下一个位置"并且rend()存在。(这样定义的好处是:1.维护半开性,2.倒序避免修改迭代器)

2. Insert(插入)迭代器

Insert iterator迭代器可以使算法以安插(Insert)方式而非覆写(Overwrite)方式运作。使用它,可以解决目标空间分配不足问题,它会促使目标容器空间的大小按需成长。

Insert Iterator迭代器对如下接口进行了新定义:
a. 赋值(assign):如果你对某个元素设值,会引发"对其所属容器的安插(insert)操作",至于插入位置是在最前或最后,或是在某个特定位置上,视三种insert iterators而定。
b. 单步前进(step forward):不会造成任何动静(no-op)
三种Insert iterator迭代器:
          back_inserter
:安插于容器最尾端,back_inserter内部调用push_back() 因此需要容器支持push_back()操作,这样的容器有三个:vector deque list
          front_inserter:  安插于容器最前端,需要容器支持push_front()操作,这样的容器只有deque和list
          inserter:         一般型安插迭代器,它将元素插入迭代器初始化时传入的第二参数所指位置的前方。Inserters内部调用Insert()成员函数,因此支持所有的STL容器。也是唯一可用于关联式容器的安插迭代器。


3. Stream(流)迭代器

不同于普通迭代器和安插迭代器,流迭代器是用于读写stream的迭代器,它提供了必要的抽象性,使得来自键盘或文件的输入像是个群集(collection),你能对它进行读取,同样,也能够将计算结果写入(输出)到文件或者屏幕上。

     下面的代码展示了迭代器适配器的巨大威力:

#include "stdafx.h"
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
int _tmain(int argc, _TCHAR* argv [])
{
     vector<string > vecStr;
     //从标准输入读取单词到vecStr
     copy(istream_iterator <string>( cin),
          istream_iterator <string>(),
          back_inserter (vecStr));
     //将单词排序
     sort(vecStr .begin(), vecStr.end ());     
     //将排序后的单词输出到cout,并去除相邻重复单词
     unique_copy (vecStr. begin(), vecStr .end(),
          ostream_iterator <string>( cout, "\n"));
     return 0;
}

上面的代码充分显示了算法,流迭代器和安插迭代器配合的巨大威力,在不到10行的核心代码里,实现了从标准输入读入单词,对单词排序,去除重复单词并输出到屏幕。
     上面用到两种流迭代器,istream_iterator和ostream_iterator:
     istream_iterator<string>(cin):
          该迭代器通过Input 迭代器接口从标准输入cin读取数据,其中模板参数string代表该流迭代器从标准输入读入型别是字符串,型别是通过>>运算符被读进来的,因此每当算法企图读取下一个元素时istream_iterator就会将读取操作转换为cin>>string
          注意:istream迭代器的构造函数会将stream打开,读取第一个元素,因此在使用istream迭代器之前,被过早定义它。否则一旦operator*被调用,就无法返回第一个元素了。(参见Input iterator特性)

     istream_iterator<string>():
          调用istream_iterator<string>()的默认构造函数,产生一个代表"流结束符号"的迭代器,它代表的意义是:你不能再从中读取任何东西。
          只要不断读取的第一个参数不等于第二个参数,算法就将继续执行。而这里的第二个参数"流结束符"的作用就是代表文件结束符或者ctrl+c,这个算法从cin读取所有string,直到遇到文件结束符(控制台下ctrl+c)为止,将读取到的所有元素安插到vector vecStr中。

     ostream_iterator<string>(cout, "\n"):
          会产生一个output stream iterator,实现output iterator接口,通过operator<<向cout写入strings。第二个参数是可选的,作为cout输出元素之间的分隔符。本例输出换行。
          ostream_iterator和前面所说Insert迭代器概念一致(包括对自增无操作,解引用返回iter),唯一的区别在于它将赋值操作转换为operator<<。


四. traits技术

template<typename Iterator, typename T>
void func_impl(Iterator iter, T t)
{
    T temp;//这里就解决了问题
    //这里做原本func()的工作
}

template<typename Iterator>
void func(Iterator iter)
{
    func_impl(iter, *iter);//func的工作全部都移到func_impl里面了
}

int main(int argc, const char *argv[])
{
    int i;
    func(&i);
}
函数func作为对外接口,实际的操作却由函数func_impl执行,通过函数func_impl的参数类型推导,获取到Iterator指向对象的类型T,从而解决了问题。本质定义:加上一层间接性,换来以定的灵活性

STL解决 “如何让返回类型是迭代器所指对象的类型” 这种问题的办法就是内嵌类型声明,即在迭代器内部添加一种“特性”,通过这种“特性”,算法可以很容易地获知迭代器所指对象的类型:

template<typename T>
class Iterator
{
public:
    typedef T value_type;//内嵌类型声明
    Iterator(T *p = 0) : m_ptr(p) {}
    T& operator*() const { return *m_ptr;}
    //...

private:
    T *m_ptr;
};

template<typename Iterator>
typename Iterator::value_type func(Iterator iter) //以迭代器所指对象的类型作为返回类型
{
    return *iter;
}

int main(int argc, const char *argv[])
{
    Iterator<int> iter(new int(10));
    cout<<func(iter)<<endl;  //输出:10
}
函数func()的返回类型前面必须加上关键词typename,因为T是一个template参数,编译器在编译实例化func之前,对T一无所知,就是说,编译器 并不知道Iterator<T>::value_type是一个类型,或者是一个静态成员函数,还是一个静态数据成员,关键词typename的作用在于告诉编译器这是一个类型,这样才能顺利通过编译。

原生指针如何定义类型?

原生指针也是一种迭代器,此时问题就来了,原生指针并不是一种类类型,它是无法定义内嵌类型的。因此,上面的内嵌类型实现还不能完全解决问题,那可不可以针对原生指针做特殊化的处理呢?答案是肯定的,利用模板偏特化就可以做到了。《泛型思维》一书对模板偏特化的定义是:针对template参数更进一步的条件限制所设计出来的一个特化版本。

//这个泛型版本允许T为任何类型
template<typename T>
class C
{ 
    //...
};  
我们很容易接受上面的类模板有一个形式如下的偏特化版本:
template<typename T>
class C<T*> 
{
    //... 
};
这个特化版本仅适用于T为原生指针的情况,”T为原生指针”就是“T为任何类型”的一个更进一步的条件限制。那如何利用模板偏特化解决原生指针不能内嵌类型的问题呢?下面介绍的iterator_traits就是关键了。

迭代器萃取机--iterator_traits

STL里面使用iterator_traits这个结构来专门“萃取”迭代器的特性,STL定义了迭代器最常用到的五种类型:value_type、difference_type、pointer、reference、iterator_category,任何开发者如果想将自己开发的容器与STL结合在一起,就一定要为自己开发的容器的迭代器定义这五种类型,这样都可以通过统一接口iterator_traits萃取出相应的类型,下面列出STL中iterator_traits的完整定义:

tempalte<typename I>
struct iterator_traits
{
    typedef typename I::iterator_category iterator_category;
    typedef typename I::value_type value_type;
    typedef typeanme I:difference_type difference_type;
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};

(1) 迭代器类型之一:value_type
       value_type就是指迭代器所指对象的类型,例如,原生指针也是一种迭代器,对于原生指针int*,int即为指针所指对象的类型,也就是所谓的value_type。

(2) 迭代器类型之二:difference_type
       difference_type用来表示两个迭代器之间的距离,例如:

int array[5] = {1, 2, 3, 4, 5};
int *ptr1 = array + 1;//指向2
int *ptr2 = array + 3;//指向4
ptrdiff_t distance = ptr2 - ptr1;//结果即为difference_type

上面代码中,指针ptr2与ptr1相减的结果的类型就是difference_type,对于原生指针,STL以C++内建的ptrdiff_t作为原生指针的difference_type。

(3) 迭代器类型之三:reference_type

       reference_type是指迭代器所指对象的类型的引用,reference_type一般用在迭代器的*运算符重载上,如果value_type是T,那么对应的reference_type就是T&;如果value_type是const T,那么对应的reference_type就是const T&。

(4) 迭代器类型之四:pointer

       pointer就是指迭代器所指的对象,也就是相应的指针,对于指针来说,最常用的功能就是operator*和operator->两个运算符。因此,迭代器需要对这两个运算符进行相应的重载工作:

T& operator*() const { return *ptr; } // T& is reference type
T* operator->() const { return ptr; } // T* is pointer type

traits使用示例:

//用于input_iterator的版本
template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
  while (__n--) ++__i;
}

//用于 bidirectional_iterator的版本
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n, 
                      bidirectional_iterator_tag) {
  __STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator);
  if (__n >= 0)
    while (__n--) ++__i;
  else
    while (__n++) --__i;
}

//用于 random_access_iterator的版本
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n, 
                      random_access_iterator_tag) {
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __i += __n;
}

//advance方法的对外接口
template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
  __STL_REQUIRES(_InputIterator, _InputIterator);
  __advance(__i, __n, iterator_category(__i));
}


五. __type_traits

STL只对迭代器进行了traits规范,制定类iterator_traits。SGI 把这种规范扩大到类迭代器之外,也就是__type_traits(__前缀表示是SGI 内部使用的,不在STL规范内)。

iterator_traits负责萃取iterator特性,而__type_traits则负责萃取一下五种型别(type)特性:

  has_trivial_default_constructor  是否有平凡的默认构造函数

  has_trivial_copy_constructor  是否有平凡的拷贝构造函数

  has_trivial_assignment_operator  是否有平凡的分配操作

  has_trivial_destructor  是否有平凡的析构函数

  is_POD_type  是否为POD类型(POD:Plain Old Data)

如果class内含指针成员,并对它进行内存动态分配,那么这个class就需要实现自己的 non-trivial-xxx

上述特性应该响应我们“真”或“假”,但却不能是bool值,因为我们要利用其响应来进行参数推导,而编译器只有面对class object形式的参数时才能进行参数推导,因此响应应该是带有“真”“假”性质的不同类。SGI STL中如下定义:

template <class _Tp>
struct __type_traits { 
   typedef __true_type     this_dummy_member_must_be_first;
                   /* Do not remove this member. It informs a compiler which
                      automatically specializes __type_traits that this
                      __type_traits template is special. It just makes sure that
                      things work if an implementation is using a template
                      called __type_traits for something unrelated. */

   /* The following restrictions should be observed for the sake of
      compilers which automatically produce type specific specializations 
      of this class:
          - You may reorder the members below if you wish
          - You may remove any of the members below if you wish
          - You must not rename members without making the corresponding
            name change in the compiler
          - Members you add will be treated like regular members unless
            you add the appropriate support in the compiler. */
 

   typedef __false_type    has_trivial_default_constructor;
   typedef __false_type    has_trivial_copy_constructor;
   typedef __false_type    has_trivial_assignment_operator;
   typedef __false_type    has_trivial_destructor;
   typedef __false_type    is_POD_type;
};

为保守起见都定义为__false_type


 


在SGI STL的Type_traits.h中对C++ 内建的bool/char/signed char/unsigned char等标记为__true_type。


 


为什么要引入__type_triats?


答案还是提高效率


对于标记为__true_type的型别,对其对象进行构造/析构/拷贝/赋值等操作时就可以采用最有效率的措施。(如:不必调用高层次的constructor/destructor,而采用内存直接处理操作malloc()/memcpy()等)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值