【STL源码剖析】总结笔记(11):算法(algorithm)初识

本文主要探讨STL中的算法,包括算法与iterator_category的关系,如advance()和distance()的实现,并介绍了traits机制在算法设计中的作用。算法在STL中强调复用性,与数据结构相互依赖。通过iterator_category的不同,算法行为有所差异,如input、output、forward、bidirectional和random access iterator。文章还举例分析了advance()和distance()的类型推导和重载实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

00 写在前面

算法,本质上就是解决问题的方法。

我们经常见面的数据结构和算法系列,其实数据结构就是我们所说的STL中的容器,而算法就是解决各类问题的方法。

在STL中说算法,更侧重的是算法的实现剖析而不是用法,这也是和课堂中的算法最重要的区别。

在本系列的开始时,我们就有谈到过算法,我们说算法需要处理容器中的数据,迭代器就是算法和数据之间的桥梁。

【STL源码剖析】总结笔记(1):开篇1

可以看出迭代器(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–,最后一种则涵盖所有算术能力。

而它们之间是一种不断强化的关系(不是绝对的继承,虽然定义的时候是这样的)

image-20211122113951555

对于容器来说,不同的类型对应不同的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的运作机制。

image-20211122155811786

可以看出只有在特定情况下才会使用较慢的for循环。如果可以使用memmove()就会直接使用更快的动作。

这也让我们回想起在分配器所说的destroy。

【STL源码剖析】总结笔记(4):幕后功臣–分配器(allocator)

当时所说的“利用_type_traits来判断是否需要析构函数”其实就是萃取机制。

需要注意STL的算法命名规范:以算法能接受的最低阶的迭代器命名类型参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值