算法与迭代器
在C++的STL中,我们最经常使用的只有两部分,一是容器(container),如vector、list、set、map等;二是各种算法,如swap、sort等。前者是模板类,后者是模板函数,也就是说,STL 中提供的算法对它们要操纵的数据一无所知,它所需要的一切信息都必须由Iterator取得,这样算法才能正常运行。
下面是find()函数的源代码
template <class _InputIter, class _Tp>
inline _InputIter find(_InputIter __first, _InputIter __last,
const _Tp& __val,
input_iterator_tag)//需要3个迭代器,前两个确定搜寻区间,后一个确定搜寻目标
{
while (__first != __last && *__first != __val)
++__first;
return __first;
}
事实上基本所有的泛型算法的都和find一样,前两个参数都是一对迭代器,通常称为first 和 last,用以表示该算法的操作区间。STL习惯采用前闭后开区间,即[first,last)。另外,在这个区间上,++操作必须能够运用,否则在运行时会导致不可预期的结果(原因之后会讲)。
迭代器是对指针的封装,所以我们暂时可以将其简单理解为指针。一共有5种类型,它们的关系如下:
- random_access_iterator:涵盖所有指针的运算能力,对应vector等容器;
- bidirectional_iterator:只能双向移动,即只能执行 +、- 操作,对应list(即双向链表)等容器;
- forward_iterator:只能单向移动,即只能执行+ 操作,对应farward_list(即单向链表)等容器;
- input_iterator:可理解为常量指针,即这种迭代器所指的对象不允许外界改变,是只读的;
- output_iterator:只写。
前三种都由容器提供,后两种是适配器。
由上面的图可以看出,input_iterator是除了output_iterator之外的所有父类,所以find函数中的前两个参数可以传入除了output_iterator之外的任何容器的迭代器。
下面通过stl中advance函数来展现迭代器对算法的影响,advance函数的实现如下
template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
__advance(__i, __n, iterator_category(__i));
}
可以看到advance会调用__advance函数,而这个函数的实现有多个版本。如下:
template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
while (__n--) ++__i;
}
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n,
bidirectional_iterator_tag) {
if (__n >= 0)
while (__n--) ++__i;
else
while (__n++) --__i;
}
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n,
random_access_iterator_tag) {
__i += __n;
}
其他函数也大多如此,这样stl 中的算法才能为基本数据类型和种类繁多的容器服务。
迭代器与Traits
迭代器是一种包装了原生指针的对象,而指针最重要工作就是由 * 和 -> 两个操作符完成的,所以迭代器必须对这两个操作符进行重载。
另外,迭代器要向算法提供容器的信息,比如 容器类型。前面提到,容器会提供迭代器供算法使用,但stl中有多种容器,但容器所能提供的迭代器却只有3种,这就意味着,我们不能可能不能通过迭代器使用到相应型别(比如如果我们要声明或返回某一中数据类型的变量,这种数据类型是不确定的)。变量声明其实还算简单,可以利用模板的参数推导机制,例如:
template<class T>
void func(T t){
... //这里编译器会自动进行参数推导,此例中T为int类型
}
int main(){
int i;
func(i);
}
但是迭代器的相应型别不只是迭代器所指的对象的类型,最常用的相应型别有5种,所指对象的类型被称为 value type,但并不是每一种都可以用模板的参数推导来取得,另外参数推导也不能用于函数的返回值,我们需要其他方法,比如内嵌型别。其实就是
template <class T>
struct A{
typedef T value_type;
...
};
这是一个泛化版本,针对原生指针和常量指针有特化版本如下
template <class T>
struct A<T*>{//针对原生指针的特化版本
typedef T value_type;
...
};
template <class T>
struct A<const T*>{//针对常量指针的特化版本
typedef T value_type;
...
};
特化的定义是:针对任何template参数更进一步的条件限制所设计出来的一个特化版本。可以看出来,上面的模板类专门用来萃取迭代器的特性,它的名字是Traits。实际上除了value_type之外,traits还有多种型别要“萃取”,最常用的为以下几种:
- value_type
- difference_type :表示两个迭代器之间的距离,也可以用来表示一个容器的最大容量
- reference_type:T&,
- pointer_type:T*,
- iterator_category:迭代器的分类,就是前面提到的5种。
input_iterator 及 input_traits的实现如下
template <class _Tp, class _Distance> struct input_iterator {
typedef input_iterator_tag iterator_category;
typedef _Tp value_type;
typedef _Distance difference_type;
typedef _Tp* pointer;
typedef _Tp& reference;
};
template <class _Iterator>
struct iterator_traits {
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
当我们想要自己实现一个iterator的时候,可以继承stl 中的iterator 类,它的代码为
template <class _Category, class _Tp, class _Distance = ptrdiff_t,
class _Pointer = _Tp*, class _Reference = _Tp&>
struct iterator {
typedef _Category iterator_category;
typedef _Tp value_type;
typedef _Distance difference_type;
typedef _Pointer pointer;
typedef _Reference reference;
};
它不含任何数据成员,纯粹只是型别定义,所以继承它没有任何负担。比如要实现一个ListIterator
template <class T>
struct ListIterator: public:std::iterator<std::forward_iterator_tag,Item>
{
...
};
SGI STL 中的__type_traits
iterator_traits负责萃取迭代器的特性,而__type__traits负责萃取type 的其他特性,这些特性有:
- has_trivial_default_constructor
- has_trivial_copy_constructor;
- has_trivial_assignment_operator;
- has_trivial_destructor;
- is_POD_type;
__type_traits同样是为了根据数据类型的特点而为算法选择更有效率的版本。它的实现如下
struct __true_type {//为了将__type_traits的返回值进行参数推导,所以将返回值定义为object
};
struct __false_type {
};
template <class _Tp>
struct __type_traits {
typedef __true_type this_dummy_member_must_be_first;
typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
};
基本数据类型和原生指针的5个特性都被设置为true_type.
参考 《STL 源码剖析》 作者:侯捷