迭代器特性

来源:互联网 发布:网络交易中间商 模式 编辑:程序博客网 时间:2024/06/03 15:31

首先在读<<C++标准程序库>>这本书的迭代器特性这一节很迷茫.不知所云.后来看到一篇类似的文章.是参考这本书的.但是有一些很好的注释.正是这些注释,才使得我有种恍然大悟的感觉.

原文请见:http://www.codeproject.com/KB/stl/Iterator_traits.aspx

简介

如果去查字典,特性就是特点/属性的意思。C++中也不例外,本文中的迭代器特性,指的就是C++ Iterator的特点/属性。继续深入研究之前,我们需要先基本了解一下C++中迭代器是如何设计的。

在C++ STL(标准模板库)中,有3个有意义和重要的东西:

1. 容器:用于管理一系列某种特定类型的对象。容器分为两种:顺序容器(如vector,deque,list)和关联容器(Set,Multiset,Map,MultiMap)。

2. 算法:用于处理一系列的元素(译注:通常是容器中的元素)。就是说,算法将容器中的元素,按照事先定义好的方法进行处理,并把结果放入同一或另外一个容器中。

3. 迭代器:用于遍历对象聚集(collection)中的各个元素(即总所周知的,容器中的元素。aka = as known as)

STL的设计者,采用了奇妙的而且简单通用的方法 - “数据和操作的分离”。

- 数据保存在容器中,并由容器管理它们。

- 对于容器的操作是由可配置的算法来定义和管理的。

因此,如果容器类和算法分别为完全不同的实体,那么他们之间必然存在一种桥梁(bridge),对吧?他们之间必须有专门的通道,必须有某种粘合剂(glue)将它们关联起来。是的,你对了,迭代器就是这样的桥梁/通道/粘合剂。

或许有人会说STL中的容器/算法/迭代器的概念和面向对象编程的基本理念相矛盾:STL分离了数据和操作(算法),而不是将他们融为一体(译注:面向对象的方法,比如在C++中,通常是将成员变量(即数据)和成员函数(即操作)放在同一个类中)。这种设计的优美和灵活之处就是,你可以几乎可以将任何一种容器和任何一类算法进行绑定(任何一类算法,我指的是 – Modifying/Non-Modifying/Mutating/Sorting/Numeric等)。

背景

简单介绍完了,为了写能体现迭代器特性的代码,一些背景知识尚需推敲一下。正如大家知道的,迭代器有不同的分类(随机存取的,双向的等等)。不同分类的迭代器有不同的功能。

尽管如此,有时候仍然必要或者必须以无缝的方式,重载迭代器的功能/行为,你可以就此打住我并且问…等等,你在说些什么?“你要以迭代器独立方式重载迭代器行为?这到底是什么意思?”

是的,你并没有看错 – 有时候在公共平台上,重载迭代器行为是绝对有必要的,这样外部看起来就像是无缝的一样。

哦,你需要例子?例子有很多,下面我给出一些吧:

1. difference_type count(Iterator iter1, Iterator iter2)

2. difference_type count_if(Iterator iterq, Iterator iter2, UnaryPredicate op)

在语句1中,count算法的作用,是对容器中的某种特定取值的元素进行计数;在语句2中,count算法在语句1的基础上,元素还必须满足一个一元判定式(Unary Predicate,译注:Predicate可译为谓词、断言或者判定式)…自然就产生一个问题,返回的数据类型是什么?第一种情况可能是…嗯…可能是一个INT/SHORT。但是no!那不是泛型编程。我们将很快找到答案。

另外一点就是:你应该写你自己的泛型算法(这里指cout/count_if),以便用户可以针对任何容器(当然也针对任何迭代器)来调用它。因此,你需要以泛型函数的方式来写,以便可无缝地适用于任何迭代器。

另外一个例子是distance()方法。该方法有两个Iterator位置参数,返回他们之间的数值距离(numeric distance)。这个方法也适用于所有的容器和迭代器。

还有很多…详情参见STL算法相关章节。

现在大问题来了,我们如何才能写出无缝的泛型函数/算法呢?

多谢STL的设计者们:他们为我们提供了一切。他们提供的一些东西颇有机巧,但是还是很简单并且有效的。

首先:每一迭代器类型,C++库提供了一个<iterator tag>,可以用作迭代器的标签。结构看起来如下:(可以在stl_iterator_base_types.hpp文件找到,我用的是MinGw)。

//@{

/**

* @defgroup iterator_tags Iterator Tags

这些是空类型,用来区分不同的迭代器。

区别不是因为他们包含了什么,而是,简单地,它们是什么。

不同的算法实现,可用于由不同类型的迭代器支持的不同算法。

*/

/// 标记输入迭代器

struct input_iterator_tag {};

/// 标记输出迭代器

struct output_iterator_tag {};

/// 前向迭代器支持输入迭代器操作的超集(译注:原话如此,在OO中,派生类可以说成基类的‘超集’)

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 {};

//@}

注意这里使用了继承。作为例子,任何前向迭代器都是一种输入迭代器。不过要注意,前向迭代器标记仅从输入迭代器标记继承而来,并没有从输出迭代器标记继承什么。因此,前向迭代器肯定不是输出迭代器。实际上,前向迭代器并没有类似输出迭代器那样的需求。

如果编写泛型代码,你可能不仅仅对迭代器分类感兴趣。比如,你可能需要知道迭代器所指元素的数据类型。因此,C++标准库提供了一个特殊的模板结构来定义迭代器特性。这个结构包含了和迭代器相关的所有信息,用作任何一个迭代器应该具备的所有类型定义的公共接口(类别、元素数据类型等等):

namespace std {

template <class T>

struct iterator_traits {

typedef typename T::value_type value_type;

typedef typename T::difference_type difference_type;

typedef typename T::iterator_category iterator_category;

typedef typename T::pointer pointer;

typedef typename T::reference reference;

};

}

在这个模板中,T代表迭代器数据类型。这样,你就能够使用这个种类,即元素的数据类型为T的任何迭代器来编写代码,等等。例如,下面的表达式得出了迭代器数据类型T的值类型:

typename std::iterator_traits<T>::value_type

这个结构两两个有点:

1. 确保一个迭代器提供了所有数据类型定义。

2. 可以针对一些特殊的迭代器,被(部分)特化。后者通过也可用作迭代器的普通指针完成的。

namespace std {

template <class T>

struct iterator_traits<T*> {

typedef T value_type;

typedef ptrdiff_t difference_type;

typedef random_access_iterator_tag iterator_category;

typedef T* pointer;

typedef T& reference;

};

}

因此,对于任何指向”T”的数据类型”指针”,它被定义为随机访问迭代器这个分类。对于常量指针(const T*),存在一个对应的部分特化。

使用代码

为迭代器写泛型函数

使用迭代器特性,可以编写派生自数据类型定义或根据迭代器分类不同的实现代码的泛型函数。

使用迭代器分类

使用为不同迭代器分类而编写的不同实现,你必须准手这两步:

1. 让你的模板函数,将迭代器分类作为一个附加参数来调用另外一个函数,例如:

template <typename Itr>

inline void my_function(Itr begin, Itr end)

{

my_function (beg, end,

std::iterator_traits<Itr>::iterator_category());

}

2. 为所有迭代器实现其他函数,以提供一个不继承其他任何迭代器分类的特殊实现。例如:

// 双向迭代器的my_function()

template <typename BidectionalIterator>

void my_function (BidectionalIterator begin, BidirectionalIterator end,

std::bidirectional_iterator_tag)

{

// 双向迭代器相关代码

}

// 随机存取迭代器的my_function()

template <typename RandomIterator>

void my_function(RandomIterator begin, RandomIterator end,

std::random_access_iterator_tag)

{

// 随机存取迭代器相关代码

}

distance()的实现

本文前面提到的一个例子,就是实现迭代器辅助函数distance()。该函数返回两个迭代器位置之间的距离和元素。随机存取迭代器的实现仅使用操作符 – (即一元减号)。所有其他分类的迭代器,还要返回到达范围结尾所需增加的数字。

请仔细阅读下面代码中的注释。

// 用户总是调用这个方法,distance()的通用方法

// 必须满足下面两个条件:

// 对于随机访问迭代器,第二个迭代器位置必须出现在第一个迭代器位置之后;

// 对于其他所有分类的迭代器,第二个迭代器必须可以从第一个迭代器到达,两种迭代器必须指向同一个// 容器,否则结果是未定义的

template <typename Iterator>

typename std::iterator_traits<Iterator>::difference_type

distance (Iterator pos1, Iterator pos2)

{

return distance (pos1, pos2,

std::iterator_traits<Iterator>

::iterator_category());

}

// 核心实现….随机存取迭代器的distance()

template <typename RandomAccessIterator>

typename std::iterator_traits<RandomAccessIterator>::difference_type

// 注意上面的返回类型,完全依赖与迭代器中声明的类型

// 此处并非所谓的INT/SHORT

distance (RandomAccessIterator first_position, RandomAccessIterator second_position,

std::random_access_iterator_tag)

{

return second_position - first_position;

}

// 输入、前向和双向迭代器的distance()

// 留意这里我们仅用了输入迭代器标记,所以

// 前向和双向迭代器也在这个方案中,因为我们

// 在声明前向和双向迭代器标记时,使用了继承

template <typename InputIterator>

typename std::iterator_traits<lnputIterator>::difference_type

// 注意这里的返回数据类型,真正的泛型

distance (Inputlterator first_position, InputIterator second_position,

std::input_iterator_tag)

{

// 注意这里临时变量的数据类型,真正的泛型。

typename std::iterator_traits<lnputIterator>::difference_type diff;

for (diff=0; first_position != second_position; ++first_position, ++diff) {

;

}

return diff;

}

不同的迭代器数据类型可用作返回类型。注意第二个版本使用了输入迭代器标记,因此这个实现也将被用于前向和双向迭代器,因为它们的标记是从input_iterator_tag继承而来。

我们可以继承同样的逻辑,为count/count_if定义返回数据类型。因此理想的泛型返回数据类型应该是:

template <typename iterator>

typename std::iterator_traits<iterator>::difference_type

count(iterator itr1, iterator itr2){

// 你的逻辑

}

进一步实现

应用这种方式,你可以写出许多泛型方法。这种方法的确必要,在你碰到需要一个可以和任何种类的迭代器无缝协作的泛型函数时,尤其如此。

参考资料

C++编程语言 – 作者Stroustrup

C++标准库:教程和参考,作者Josutis

原创粉丝点击