【《STL源码剖析》提炼总结】 第2节:迭代器 iterator

一. 迭代器简介

什么是iterator

《Design Patterns》:提供一种方法,使之能够依序寻访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式

STL中迭代器起到的作用

将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一贴胶着剂将它们撮合在一起

迭代器应该如何设计

  • 迭代器应该可以访问容器的内部细节,因此设计在外的话会暴露出很多接口,因此迭代器应该被设计在容器的内部(内部类)
  • 迭代器的操作形如指针(类似智能指针),因此需要重载指针常用的操作:例如++,+,*,–,-等操作

迭代器的类型

​ 原生指针(native pointer,也就是普通指针)对于内存是随机存取的,但是对于一些特殊的数据结构,比如说单向链表,只有指针域中只有一个指针指向后一个节点,因此只能往后不能往前。因此对于指针的类型需要有所规范。

​ 同时,迭代器指向的值(也就是容器中的元素类型),这是算法所需要的(可能要赋值一些中间变量),这也是迭代器需要告知的。

​ 两个迭代器相减的值的类型,也是迭代器需要告知的。

​ 如何告知容器这些细节,这就要用到接下来介绍的知识——traits

二. traits萃取器

typedef在泛型编程中的作用

​ 在泛型编程的时候,因为类型不明确,需要提供一个获得类型的接口。这个时候可以用到typedef,在class内部使用typedef定义一个类型,然后通过class名::定义的别名的形式将对应的类型取出。因为这些操作是在编译层面执行的,在运行的时候完全没有开销。

为什么要加一层萃取器

​ 假如按照标准,每个对应的迭代器内部都定义的对应的类型,这样其实可以直接使用class名::定义的别名的形式取出。但是这样的后果是原生指针无法获取(其只是一个默认类型,根本无法像class那样定义),而在使用算法的时候,需要使用数组,因此就需要一个中间层来处理原生指针。

​ 而处理的技术就是traits

Traits

traits是一种特性萃取技术,它在Generic Programming中被广泛运用,常常被用于使不同的类型可以用于相同的操作,或者针对不同类型提供不同的实现.traits在实现过程中往往需要用到以下三种C++的基本特性:

enumtypedef、template (partial) specialization

其中:

  • enum用于将在不同类型间变化的标示统一成一个,它在C++中常常被用于在类中替代define,你可以称enum为类中的define;

  • typedef则用于定义你的模板类支持特性的形式,你的模板类必须以某种形式支持某一特性,否则类型萃取器traits将无法正常工作

  • template (partial) specialization被用于提供针对特定类型的正确的或更合适的版本.

C++ Traits

​ 在使用traits之前,我们需要先了解泛型编程中的偏特化

偏特化

特化

定义模板的时候,假如定义如下

template<>
class Name< type_name > { ... }
template<>
returnVAlue FuncName< type_name > (...) { ... }

这被称为特化,即在泛化的模板中对某些类型进行特殊处理。编译器会优先选择特化的版本

偏特化

偏特化的意思是提供另一份template定义式,而其本身仍为模板化的

比如

template<typename U,typename V,typename T> class C { ... }

便可以偏特化为

template<typename V,typename T> class< U_name > C { ... }

同时,面对指针特殊处理,也是一种偏特化

template<typename T>
class C { ... }

template<typename T>
class C<T*> { ... }

这样便是对指针的偏特化。在使用traits时,如上文,正是要对指针进行特殊处理,因此要采用第二种偏特化

拓展资料:特化与偏特化 https://www.cnblogs.com/tianzeng/p/9782207.html

traits的具体实现

如上文所说,因为原生指针无法取出对应类型,因此traits应运而生,其对指针的特殊处理正是偏特化

首先,已知迭代器需要提供的类型有五种:

  • value type,
  • difference type,
  • pointer,
  • reference,
  • iterator catagory

因此traits的泛化版本如下:

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;
};
// 直接从I中取出对应的类型

traits对指针的偏特化版本就不能像泛化那样直接取出,需要自己指定:

template <class I>
struct iterator_traits<T *>
{
    typedef random_access_iterator_tag  iterator_category;
    typedef T                           value_type;
    typedef ptrdiff_t                   difference_type;
    typedef T *                         pointer;
    typedef T &                         reference;
};
// 对const指针的操作与之类似,就不重复展示了

迭代器的五个相应类型

1.value type

迭代器指向的元素的类型

2.difference type

两个迭代器之间的距离,因此也可以表示一个容器的最大容量——最多有 2^(bits of difference_type)个元素

3.reference type

若要传引用,对应的引用类型 (其实就是T&)

4. pointer type

对应的指针类型(就是T*)

5.iterator_category

迭代器本身的类型

迭代器有5个类型:

类型特征
input iterator只读
output iterator只写
forward iterator单向移动
bidirectional iterator双向移动
random access iterator随机读取

相比于前4个接口只用已存在的类型来进行修饰即可,而迭代器的类型是凭空创造的一个特征,因此需要自己定义一系列的class来作为重载时选择分支的依据

即:定义5个class,代表5种迭代器类型

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

​ 使用原理:创建一个空的类型,因为其内部没有成员(一般是占1字节),所以开销很小,创建对应实例后,将其作为参数,可以通过函数重载进入不同的处理逻辑。

​ 对版本的选择是在编译时期就决定的,也不会影响运行效率

​ 因为在定义的过程中使用了继承,假如有相同的处理方式,参数写父类就可以全部进入。比如若input-forward-bidirectional-random_access都是相同的处理逻辑,参数列表处为input_iterator_tag则会接受这四个类型(子类可以对应父类),因此STL的命名规则为:参数会命名为算法能接受的最低阶迭代器类型

使用样例

例如 advance 函数:将迭代器前进 n 个位置 (n可以是负数),有3个可能:只能单向移动,可以双向移动,可以随机移动

  • 单向移动:只能用++ ,使用forward_iterator_tag
  • 双向移动:使用++/–,使用bidirectional_iterator_tag
  • 随机存取:直接使用+=n来到达对应位置,使用random_access_iterator_tag

三. std::iterator

STL中定义了一个标准模板类 iterator,里面定义了常用的五种基本属性,因此迭代器只需要继承这个类型(在继承的时候通过"<>"声明好五种类型对应的类型),便可以保证不会遗漏,也使得程序更安全(不会出现名字错误),也更简洁(不用自己手写定义部分)

附:type_traits

​ 同iterator_traits一样,type_traits是用于萃取每种类型的若干个特征的,其中一个就是is_POD_type

​ 对于truefalse,并不能用bool来定义,因为bool同时代表true和false,因此需要像input_iterator_tag等class一样,自己定义两个class,分别代表true和false,即__true_type__false_type

​ 对于每个类型中这些属性是true还是false,同样通过type_traits来处理,即通过对每个类型的特化,来定义对应的值。STL内部已经对所有基本类型都定义好了特化版本(全部都是true,即都是trivial),而type_traits的泛化版本是全为false,即默认所有类型为 no-trivial类型。

​ PS: 对现在的新版编译器来说,已经有了可以直接判断type的工具,而不需要对type_traits进行琐碎的一个个定义了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值