文章目录
一. 迭代器简介
什么是iterator
《Design Patterns》:提供一种方法,使之能够依序寻访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式
STL中迭代器起到的作用
将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以一贴胶着剂将它们撮合在一起
迭代器应该如何设计
- 迭代器应该可以访问容器的内部细节,因此设计在外的话会暴露出很多接口,因此迭代器应该被设计在容器的内部(内部类)
- 迭代器的操作形如指针(类似智能指针),因此需要重载指针常用的操作:例如++,+,*,–,-等操作
迭代器的类型
原生指针(native pointer,也就是普通指针)对于内存是随机存取的,但是对于一些特殊的数据结构,比如说单向链表,只有指针域中只有一个指针指向后一个节点,因此只能往后不能往前。因此对于指针的类型需要有所规范。
同时,迭代器指向的值(也就是容器中的元素类型),这是算法所需要的(可能要赋值一些中间变量),这也是迭代器需要告知的。
两个迭代器相减的值的类型,也是迭代器需要告知的。
如何告知容器这些细节,这就要用到接下来介绍的知识——traits
二. traits萃取器
typedef在泛型编程中的作用
在泛型编程的时候,因为类型不明确,需要提供一个获得类型的接口。这个时候可以用到typedef,在class内部使用typedef定义一个类型,然后通过class名::定义的别名
的形式将对应的类型取出。因为这些操作是在编译层面执行的,在运行的时候完全没有开销。
为什么要加一层萃取器
假如按照标准,每个对应的迭代器内部都定义的对应的类型,这样其实可以直接使用class名::定义的别名
的形式取出。但是这样的后果是原生指针无法获取(其只是一个默认类型,根本无法像class那样定义),而在使用算法的时候,需要使用数组,因此就需要一个中间层来处理原生指针。
而处理的技术就是traits
Traits
traits是一种特性萃取技术,它在Generic Programming中被广泛运用,常常被用于使不同的类型可以用于相同的操作,或者针对不同类型提供不同的实现.traits在实现过程中往往需要用到以下三种C++的基本特性:
enum
、typedef
、template (partial) specialization其中:
enum用于将在不同类型间变化的标示统一成一个,它在C++中常常被用于在类中替代define,你可以称
enum
为类中的define;typedef则用于定义你的模板类支持特性的形式,你的模板类必须以某种形式支持某一特性,否则类型萃取器traits将无法正常工作
template (partial) specialization被用于提供针对特定类型的正确的或更合适的版本.
在使用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时,如上文,正是要对指针进行特殊处理,因此要采用第二种偏特化
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
对于true
和false
,并不能用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进行琐碎的一个个定义了。