您的位置:首页 > 编程语言

迭代器(iterators)与traits编程技巧

2017-06-17 17:28 495 查看

1 迭代器基础

迭代器(iterator):在不暴露容器内部的前提下,能依序访问容器的每个元素。
迭代器是STL的关键,STL的中心思想是:将容器与算法分开,彼此独立,然后用粘合剂进行关联,迭代器就是这个粘合剂。

通过平时对STL的应用可以发现,每个容器都有自己的迭代器,例如:vector<int>::iterator ;list<int>::iterator等,迭代器总是依附于容器的。为了将实现细节封装,STL将迭代器的开发交给了容器的设计者。所以,每种STL中每种容器都有自己专属的迭代器。

2 traits技巧与容器的内嵌类型

上篇博客讲配置器时,有提过traits编程技巧的一个应用,_type_traits。traits技巧在STL中应用的非常广泛,这里通过例子再次加深对traits技巧的理解。
问题1:
如果某一个算法,传入迭代器参数,需要用到“迭代器所指对象”的类型,怎么办?

//func是对外接口
template<typename I>
void func(I it)
{
//将主要工作交给impFunc完成
//关键在于将it所指对象传递过去
//通过模板"参数推导"功能可获得迭代器所指对象的类型
implFunc(it,*it);
}
template<typename I ,typename T>
void implFunc(I it,T val)
{
T tmp;//可以构造T类型的对象了
//主要工作在这里完成
}
问题2:
模板只能推导参数类型,不能推导返回类型。如果上述算法需要返回 迭代器所指对象的类型,又该怎么办??

这里就要用到类模板的内嵌类型了!!

template<typename T>
class MyIter
{
public:
typedef T value_type;//声明内嵌类型(nest type)
T *ptr;
//.....
}
template<truename I>
typename I::value_type//返回类型
func(I it)
{
//实际工作
}


问题3:
对于原生指针,例如int *,char *,double *等。声明内嵌类型是实现不了的,那么上面的func()函数对原生指针就运行不了了。如果想让func()在支持迭代器的同时也支持原生指针,又该怎么办呢???

这里就得用到traits编程技巧了!!!

//iterator_traits的一般化
template<typename I>
struct  iterator_traits
{
typedef  typename I::value_type  value_type;
}
//针对 I为原生指针 iterator_traits的特化版本

template<typename T>
struct  iterator_traits(T *)
{
//对于原生指针 value_type 就是T
typedef T value_type;
}

//所以func()函数就可以这样写
template<typename I>
typename iterator_traits<I>::value_type
func(I it)
{
//这样不管I是迭代器类型,还是原生指针
//该函数都能正常工作
return *ite;
}


STL为了让iterator_traits能正常运行,要求每个迭代器都必须定义自己的内嵌类型。

3 迭代器的相应类型

除了上面value_type外,迭代器还有四个相应类型
value type 、difference type 、pointer、reference、iterator catagoly>。
value type:表示迭代器所指对象的类型。
difference type :表示两个迭代器之间的距离。
pointer :指向迭代器操作对象的指针。
reference :迭代器所指对象的引用类型。
iterator catagoly:表示迭代器的类型。下面会详细介绍。
和上述的value type一样,每种容器的迭代器都必须定义这五种类型。然后,iterator_traits就可以如下定义:

//iterator_traits的默认版本
template<typename I>
class iterator_traits
{
//每种容器的迭代器都必须定义value_type,value_type,pointer,reference,iterator_category
typedef typename I::value_type value_type;
typedef typename I::difference_type value_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
typedef typename I::iterator_category iterator_category;
}
//针对 I为 原始指针的特化版本
template<typename T>
class iterator_traits<T *>
{
typedef  T value_type;
typedef ptrdiff_t difference_type;//build-in 类型ptrdiff_t定义在<cstddef>头文件中
typedef T* pointer;
typedef T & reference;
typedef random_access_iterator_tag iterator_category;//关于iterator_category,下面会详细介绍
}
//针对 I为 const原始指针的特化版本
template<typename T>
class iterator_traits<const T *>
{
typedef  T value_type;
typedef ptrdiff_t difference_type;//build-in 类型ptrdiff_t定义在<cstddef>头文件中
typedef const T* pointer;
typedef const T & reference;
typedef random_access_iterator_tag iterator_category;//关于iterator_category,下面会详细介绍
}

迭代器的类型 iterator_category

迭代器被分为五类:

Input iterator :只读(read only)

Output iterator:只写(write only)

Forward Iterator:读写(read and write)

Bidirectional Iterator:双向迭代器,可前进可后退。

Random Access Iterator:可随机访问,跳跃式,p+n,p-n,p
,p1-p2,p1<p2等都支持。

举个例子说明iterator_traits怎么处理五种类型迭代器的,同时对traits编程技巧加上印象。

实现advance(I,n)函数,两个参数I表示迭代器,n表示前进的距离。针对不同类型的迭代器,调用效率最高的函数版本。

思路一:

定义多个版本,在对外接口中判断调用哪个版本。

//针对input iterator
template<typename InputIterator,typename Distance>
void _addvance_II(InputIterator & i,Distance n)
{
while(n--)++i;
}
//针对 bidirectional  iterator
template<typename Bidirectional,typename Distance>
void _addvance_IB(Bidirectional & b,Distance n)
{
if(n>=0)
while(n--)++b;
else
while(n++)--b;
}
//针对Random  Access Iterator
template<typename RandomAccessIterator,typename Distance>
void _addvance_RAI(RandomAccessIterator & r,Distance n)
{
r+=n;
}

//对外接口
template<typename InputIterator,typename Distance>
void advance(InputIterator i,Distance n)
{
//在这里判断i到底是哪种类型的迭代器
if(is_input_iterator(i))
_addvance_II(i,n);
else if(is_bidirectional_iterator(i))
_addvance_IB(i,n);
else
4000
_addvance_RAI(i,n);
}


这种方法有个严重的缺陷:在运行期间才确定执行哪个版本,影响效率,最后在编译期间就能决定调用哪个函数。函数重载可解决这个问题,1.为了实现重载机制需要提供额外的参数来区分不同函数;2.然后再在对外接口内确定该额外参数的类型。

//五个作为标记的类型
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag:public input_iterator_tag{};
struct bidirectional_iterator:public forward_iterator_tag{};
struct random_access_iterator_tag:public bidirectional_iterator{};

//针对input iterator
template<typename InputIterator,typename Distance>
void _addvance(InputIterator & i,Distance n,input_iterator_tag)
{
while(n--)++i;
}
//forward iterator版本不用定义,因为它的实现与 input iterator版本完全一样
//上面的五个类型标记的继承关系使得 当传进来的迭代器类型是forward iterator时
//由于没有定义该版本的_advance,所以会调用基类input iterator版本的_advance.

//针对 bidirectional  iterator
template<typename Bidirectional,typename Distance>
void _addvance(Bidirectional & b,Distance n,bidirectional_iterator)
{
if(n>=0)
while(n--)++b;
else
while(n++)--b;
}
//针对Random  Access Iterator
template<typename RandomAccessIterator,typename Distance>
void _addvance(RandomAccessIterator & r,Distance n,random_access_iterator_tag)
{
r+=n;
}

//对外接口
template<typename InputIterator,typename Distance>
void advance(InputIterator i,Distance n)
{
//这里利用iterator_traits判断调用哪个版本的_advance
_addvance(i,n,iterator_traits<InputIterator>::iterator_category());
}


为了规范,任何迭代器都需提供上述五个内嵌类型,以利用traits提取特性。为了方便,STL提供了一个基类 iterator,每个特定的迭代器只需继承该基类即可。

template<typename Category,

typename T,
typename Distance=pdfdiff_t,
typename Pointer= T*,
typename Reference=T&;>
class iterator
{
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
}

//例如 list的迭代器,类型为forward iterator
template<typename Item>
class struct ListIter:public std::iterator<std::forward_iterator_tag,Item>
{
//......
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: