00 写在前面
算法,本质上就是解决问题的方法。
我们经常见面的数据结构和算法系列,其实数据结构就是我们所说的STL中的容器,而算法就是解决各类问题的方法。
在STL中说算法,更侧重的是算法的实现剖析而不是用法,这也是和课堂中的算法最重要的区别。
在本系列的开始时,我们就有谈到过算法,我们说算法需要处理容器中的数据,迭代器就是算法和数据之间的桥梁。
可以看出迭代器(iterator)在算法中扮演着重要的角色。
迭代器中的iterator category对算法影响深远,我们在前面也举过一个例子。
【STL源码剖析】总结笔记(6):iterator的设计与神奇的traits
这次需要站在新的高度重新理解算法。
01 算法概述
这部分不需要说太多,算法可以说是存在于每个程序中,我们写的函数都可以看作是算法。
但对于STL来说,只有具有复用性的算法才是其研究的重点。
算法和数据结构也是相互依赖的。在不同的数据结构中算法的使用场景会有很大不同,而根据不同的数据结构也会设计出更适合该数据结构的算法。
02 算法与iterator_category
再谈一次之前说过的category。
根据移动特性和施行操作,迭代器被分为以下几类:
- input iterator:所指对象不允许外界改变,是只读的。
- output iterator:只写
- forward iterator:允许“写入型”算法,在此迭代器所形成的区间上进行读写操作。
- bidirectional iterator:可双向移动,需要逆向走访迭代器区间时使用。
- random access iterator:前四种只提供一部分算术能力,比如前三种支持operator++,第四种支持operator–,最后一种则涵盖所有算术能力。
而它们之间是一种不断强化的关系(不是绝对的继承,虽然定义的时候是这样的)
对于容器来说,不同的类型对应不同的iterator_category。
-
array,vector,deque是random_access_iterator,表示iterator可以随机跳跃。
-
list是bidirectional——iterator,表示只可以双向行走。
-
set和map的底层是红黑树,所以是bidirectional——iterator,支持双向行走。
-
而unordered中的容器是以哈希表作为底层,所以是forward_iterator,只可以向前走。
-
还有两个特殊的iterator,istream_iterator是input_iterator,ostream_iterator是output_iterator。
advance()
在前面的iterator中我们提到了advance()的实现,再来看一次。
advance()有两个参数,迭代器p和数值n。作用是将p累进n次,也就是前进n。
先来实现三个版本的advence():
template <class InputIterator,class Distance>//针对InputIterator的版本
void advance_II(InputIterator& i,Distance n){
while(n--)++i;
}
template <class BidirectionalIterator,class Distance>//针对BidirectionalIterator的版本
void advance_BI(BidirectionalIterator& i,Distance n){
if(n>=0)
while(n--)++i;
else
while(n++)--i;
}
template <class RandomAccessIterator,class Distance>//针对RandomAccessIterator的版本
void advance_RAI(RandomAccessIterator& i,Distance n){
i+=n;
}
上面实现了三种版本的advance(),(fowarditerator和inputiterator一样)
再根据五个标记类
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{ };
设计为根据不同的参数调用不同的方法
template <class InputIterator,class Distance>
void _advance(InputIterator& i,Distance n,input_iterator_tag){
while(n--)++i;
}
template <class ForwardIterator,class Distance>
void _advance(ForwardIterator& i,Distance n,forward_iterator_tag){
_advance(i,n,input_iterator_tag());//单纯用来传递调用函数
}
template <class BidirectionalIterator,class Distance>
void _advance(BidirectionalIterator& i,Distance n,bidirectional_iterator_tag){
if(n>=0)
while(n--)++i;
else
while(n++)--i;
}
template <class RandomAccessIterator,class Distance>
void _advance(RandomAccessIterator& i,Distance n,random_access_iterator_tag){
i+=n;
}
这时候根据traits机制可以进行类型推导。
template <class InputIterator,class Distance>
inline void advance(InputIterator& i,Distance n)
{
_advance(i,n,iterator_traits<InputIterator>::iterator_category());
}
根据traits询问后得到的category类型不同,调用不同的形式。
distance()
再来看一个distance()的例子,distance()有两个参数,求两个iterator之间的距离。
我们直接看根据category设计的版本
template<class InputIterator>
inline iterator_traits<InputIterator>::difference_typ
e _distance(InputIterator first,InputIterator last,input_iterator_tag){
iterator_traits<InputIterator>::difference_type n=0;
while(first!=last){
++first;
++n;
}
return n;
}
template<class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_typ
e _distance(RandomAccessIterator first,RandomAccessIterator last,random_access_iterator_tag){
return last-first;
}
对于InputIterator和RandomAccessIterator的两个版本的实现,可以看出代码中的区别。
然后在traits部分根据参数实现重载
template<class InputIterator>
inline iterator_traits<InputIterator>::difference_typ
e distance(InputIterator first,InputIterator last){
typedef typename iterator_traits<InputIterator>::iterator_category category;
return _distance(first,last,category());
}
03 算法与traits
type traits机制也是算法实现过程中的关键。
比如上面的advance(),我们想要得到不同的类型,就需要traits根据实际情况做区分(分为有没有在内部实现iterator),然后再返回算法需要的答案。也就是偏特化的实现。
template <class Iterator>
struct iterator_traits{
typedef typename Iterator::iterator_category iterator_category;
};
template <class T>
struct iterator_traits<T*>{
typedef random_access_iterator_tag iterator_category;
};
template <class T>
struct iterator_traits<const T*>{
typedef random_access_iterator_tag iterator_category;
};
copy可以很好地展示traits的运作机制。
可以看出只有在特定情况下才会使用较慢的for循环。如果可以使用memmove()就会直接使用更快的动作。
这也让我们回想起在分配器所说的destroy。
【STL源码剖析】总结笔记(4):幕后功臣–分配器(allocator)
当时所说的“利用_type_traits来判断是否需要析构函数”其实就是萃取机制。
需要注意STL的算法命名规范:以算法能接受的最低阶的迭代器命名类型参数