STL学习笔记(六)---迭代器

《Design Pattens》一书中iterator模式定义如下:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式。不论是泛型思维或STL的实际运用,迭代器(iterators)都扮演着重要的角色。STL的中心思想在于:将数据容器(containers) 和算法(algonithms) 分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。

迭代器iterator就是一种智能指针。它对原始指针进行了封装,而且提供一些等价于原始指针的操作,做到既方便又安全。是一种连接容器和算法的桥梁。由于迭代器是一种行为类似指针的对象,也就说迭代器是一种广义指针,即迭代器对解除引用操作(operator*)和访问成员操作(operator->)进行重载。然而要对这两个操作符进行重载,对容器内部对象的数据类型和存储结构需有所了解,于是在 STL 中迭代器的最终实现都是由容器本身来实现的,每种容器都有自己的迭代器实现。

一、迭代器的分类

在STL中,迭代器主要分为5类。各自是:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。每种迭代器均可进行表中前一种迭代器可进行的操作。 
输入迭代器和输出迭代器是最低级的迭代器。后三种迭代器都是对该迭代器的一种派生,这五类迭代器的从属关系如下图所示,其中箭头A→B表示,B是A的强化类型,这也说明了如果一个算法要求A,那么B也可以应用于其中。

二、迭代器型别推导(traits)

//这部分看了很久,自己的理解是:在用STL中的各种算法对容器中的元素进行操作时,由于容器“所有实现细节封装起来不被使用者看到”且“无需暴露该聚合物的内部表述方式”,所以迭代器设计出了一种实现(traits),在不知道容器内部元素属性的情况下,正确完成函数功能,返回对应数据。1、首先是利用 function template 的参数推导机制定义变量通过模板的推导机制,获得了指针所指向的对象的类型。2、但是函数的"template 参数推导机制"推导的只是参数,无法推导函数的返回值类型。这时候声明内嵌型别就可以直接获取。只要做一个 iterator,然后在定义的时候为其指向的对象类型制定一个别名就好了。3、但是无法支持原生指针(因为原生指针没有 value_type 这个内嵌类型)。此时template partial specialization就派上了用场。STL专门设计了一个iterator_traits模板类来”萃取“迭代器的特性。

C++ STL源码剖析之Traits编程技法 (qq.com)

带你深入理解STL之迭代器和Traits技法_ZeeCoder -CSDN博客_iterator traits

在 C++ 中,traits 习惯上总是被实现为 struct ,但它们往往被称为 traits classes。Traits classes 的作用主要是用来为使用者提供类型信息。

在算法中运用迭代器时,很可能会用到其相应类型(associated type) 。假设算法中有必要声明一个变量,以"迭代器所指对象的类型"为类型,可以利用 function template 的参数推导机制。

以func() 为对外接口,把实际操作全部置于func_impl()之中.由于func_impl() 是一个function template,一旦被调用编译器会自动进行template参数推导。于是导出型别T顺利解决了问题。但是,函数的"template 参数推导机制"推导的只是参数,无法推导函数的返回值类型。万一需要推导函数的传回值,就无能为力了。因此引出下面的方法。

声明内嵌型别

#include<iostream>
#include<cstddef>//ptrdiff_t相应的头文件
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{};//随机访问迭代器
 
//std::iterator,标准迭代器的类模板
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_traits,用于提取迭代器的属性,该类的对象不应该让用户看到
template<class Iterator>
struct iterator_traits
{
	//以下的操作相当于一个递归的操作。用于递归提取原始指针的相关值
    //迭代器类型, STL提供五种迭代器
	typedef typename Iterator::iterator_category iterator_category;
    //迭代器所指对象的型别
    //如果想与STL算法兼容, 那么在类内需要提供value_type定义
	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 iterator_traits<T*>
{
	//相当于递归终止条件
	typedef random_access_iterator_tag iterator_category;
	typedef T         value_type;
	typedef ptrdiff_t diffrence_type;
	typedef T*		  pointer;
	typedef 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  diffrence_type;
	typedef const T *  pointer;
	typedef const T &  reference;
};

迭代器所指对象的类型,称为迭代器的 value_type。对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了typename关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。因此,直接告诉编译器T::iterator是类型而不是变量,只需用typename修饰,这样编译器就可以确定T::iterator是一个类型,而不再需要等到实例化时期才能确定。


func() 的返回类型必须加上关键词typename,因为I是一个template参数,在它被编译器具现化之前,编译器对T一无所悉。编译器此时并不知道MyIter<T>::value_type 代表的是一个类型或是一个成员函数或是一个数据成员。关键词typename的用意在于告诉编译器这是一个类型而不是变量,如此才能顺利通过编译。
但是,并不是所有迭代器都是class type,原生指针就不是。如果不是class type,就无法为它定义内嵌型别。但STL (以及整个泛型思维)必须接受原生指针作为一种迭代器。此时,template partial specialization 就派上了用场。

template partial specialization

 它的大致意义是,如果class template 拥有一个以上的template参数,可以针对其中某个(或数个,但非全部) template参数进行特化工作。可以在泛化设计中提供一个特化版本( 也就是将泛化版本中的某些template 参数赋予明确的指定)。

所谓partial specialization的意思是提供另一份template定义式,而其本身仍为templaized” 。《泛型思维》一书对partial specialzation的定义是:“针对(任何) template参数更进一步的条件限制所设计出来的一个特化版本”。有了这项利器便可以解决前述“内嵌型别”未能解决的问题。先前的问题是,原生指针并非class,因此无法为它们定义内嵌型别。现在可以针对“ 迭代器之template参数为指针”者,设计特化版的迭代器.。下面这个class template专门用来“萃 取迭代器的特性,而value type正是迭代器的特性之一:

这个类如何完成迭代器的型别萃取呢? 

template <class I>
typename iterator_traits<I>::value_type // 通过iterator_traits类萃取I的型别
func(I iter){
    return *iter;
}

这个traits意义是,如果I定义有自己的value type,那么通过这个traits 的作用,萃取出来的value_type 就是I::value_type。这样做的好处是traits可以拥有特化版本。令iterator-traites拥有一个partial specializations如下:

于是,原生指针int*虽然不是一种 class type,亦可通过traits 取其value type,这就解决了先前的问题。但如果遇到这种情况:

获得的是const int而非int。我们希望利用这种机制来声明一个临时变量,使其型别与迭代器的value type相同,而现在声明一个无法赋值(因const之故)的临时变量没什么用。因此如果迭代器是个pointer-to-const 应该设法令其value type 为一个non-const型别。只要另外设计一个特化版本就能解决这个问题:

 

现在不论面对的是迭代器MyIter,或是原生指针int* 或const int*, 都可以通过traits 取出正确的value type。
下图说明了traits所扮演的“特性萃取机” 角色,萃取各个迭代器的特性。这里的迭代器特性指的是迭代器的相应型别。当然,若要这个“特性萃取机” traits 能够有效运作,每一个迭代器必须遵循约定,自行以内嵌型别定义的方式定义出相应型别 。

通过定义内嵌类型获得了知晓 iterator 所指元素类型的方法。通过 traits 技法,将函数模板对于原生指针和自定义 iterator 的定义都统一起来。使用 traits 技法主要是为了解决原生指针和自定义 iterator 之间的不同所造成的代码冗余,这就是 traits 技法的妙处所在。 

 三、迭代器的设计

实现一个迭代器。需要做以下工作:

        1.定义5类迭代器的标志类,该标志类用于实现函数的差别调用(即重载)。比如求两迭代器距离函数distance(iter1,iter2,tag)。移动函数advance(iter,n,tag)。这五个标志类分别为:input_iterator_tag,output_iterator_tag,forward_iterator_tag,bidirectional_iterator_tag,random_access_iterator_tag。
        2.对于每个iterator类,都必须包括5个属性,分别为:iterator_category、value_type、difference_type、pointer、reference。

       3.定义一个迭代器的“属性榨汁机”iterator_traits,用于获取iterator的五种属性值。

       4.定义迭代器的操作。分别为:

           1) 获取iterator的标志----->iterator_category(iter)。

           2)获取两迭代器差值的类型----->distance_type(iter)。

           3)获取迭代器的原始类型--------->value_type(iter);

           4)求两迭代器的距离---------------->distance(iter1,iter2,tag);

           5)将迭代器移动n位------------------>advance(iter,n,tag)。

iterator_traits 必须针对传入之型别为pointer 及pointer-to-const 者设计特化版本。

四、各个容器支持的迭代器

list提供的是Bidirectional Iterator,set和map提供的 iterators是 Forward Iterator,
只有顺序容器和关联容器支持迭代器遍历,各容器支持的迭代器的类别如下:
容器                 支持的迭代器类别            
vector              随机访问     
deque              随机访问                   
set                   双向                          
multimap          双向                                                
multiset            双向                                          
list                   双向
map                 双向
queue              不支持
stack                不支持    

​C++ STL源码剖析之知其然,知其所以然,源码面前了无秘密! (qq.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值