C++STL学习第三讲(讲解 STL 算法 Algorithm)

C++STL学习第三讲(讲解 STL 算法 Algorithm)

1. STL 的算法 Algorithm

1. STL的算法是什么
  • 从语言层面讲:
    • 算法 Algorithm 是个 function template(函数模板);
    • 其他的 STL 部件 是 class template(类模板);
  • 算法定义的两种基本形式:如图;
  • Algorithm 需要从 Iterators 获取 Containers 的信息;
    在这里插入图片描述

2. 容器的 iterators 中的 iterator_category

1. 五种 iterator category
  • iterator category 种类:

    • input_iterator_tag:只读 iterator;
    • output_iterator_tag:只写 iterator;
    • forward__iterator_tag;
    • bidirectional_iterator_tag;
    • random_access_iterator_tag;
  • STL 设计五个空 struct 作为五个 iterator category(不是设置1、2、3、4、5,五个 num);

    • 同时 iterator category 之间具有继承关系;
      在这里插入图片描述
2. STL 不同容器的 iterator category
  • array:
    • 连续空间分布,iterator == pointer;
    • random_access_iterator_tag;
  • vector:
    • 连续空间分布,iterator == pointer;
    • random_access_iterator_tag;
  • deque:
    • 虽然是分段连续,但给使用者感觉是连续空间分布;
    • 所以是 random_access_iterator_tag;
  • list:
    • bidirectional_iterator_tag;
  • forward_list:
    • forward__iterator_tag;
  • set / multiset / map / multimap:
    • 底层是 rb_tree,rb_tree 是双向的
    • 所以是 bidirectional_iterator_tag;
  • unordered_set / unordered_multiset / unordered_map / unordered_multimap:
    • 底层是 hashtable;
    • iterator category 取决于 hashtable 链表的实现方式,是单向链表还是双向链表;
  • 两个特殊的 iterator:
    • istream_iterator:
      • input_iterator_tag;
    • ostream_iterator:
      • output_iterator_tag;
3. 用程序输出每个容器的 iterator category

在这里插入图片描述

4. 用程序输出每个容器的 iterator category 的 typeid
  • typeid 的输出内容与 C++ 库的实现相关;
  • 大体形式:前缀 + 主要名称 + 后缀(基本上是前、后缀有区别);
  • 不同的 C++ 实现库,输出内容不一样;
    在这里插入图片描述

3. 特殊 iterator 的 iterator category

1. istream_iterator 的 iterator category
  • G2.9版的 istream_iterator 有两个模板参数;
  • G3.3版的 istream_iterator 有四个模板参数;
  • G4.9版的 istream_iterator 同样有四个模板参数,而且有继承关系:
    • 【将五个 associated types 的 typedef 划分到了父类,这样可以实现代码复用,不用每个 iterator 都手动写一遍五个 associated types 的 typedef;】
  • istream_iterator 的 iterator category 定义是 input_iterator_tag
    在这里插入图片描述
2. ostream_iterator 的 iterator category
  • G2.9版的 ostream_iterator 有一个模板参数;
  • G3.3版的 ostream_iterator 有三个模板参数;
  • G4.9版的 ostream_iterator 同样有三个模板参数,而且有继承关系:
    • 【将五个 associated types 的 typedef 划分到了父类,这样可以实现代码复用,不用每个 iterator 都手动 写一遍五个 associated types 的 typedef;】
  • ostream_iterator 的 iterator category 定义是 output_iterator_tag
    在这里插入图片描述

4. iterator category 对算法的影响

1. distance 函数
  • distance 函数:计算两个 iterator 之间的距离;
  • 返回值:
    • 向 iterator_traits 询问传入 iterator 的 difference_type
    • 返回 difference_type;
  • 函数内部:
    • 向 iterator_traits 询问传入 iterator 的 iterator category
    • 将 iterator category 临时对象传入次函数,根据 iterator category 调用对应的次函数版本
      • 对于 random_access_iterator_tag,直接使用减法即可;
      • 对于 input_iterator_tag,只能逐步前进,计算移动了多少步;
  • 可见 iterator category 类别的不同会影响 Algorithm 的效率;
  • 注意点:
    • 下图中两个次函数的模板参数名字不同(一个是 InputIterator,另一个是RandomAccessIterator);
    • 不要误认为是传入的参数类型不同,单纯只是名字不同;
    • 前两个参数都是模板参数,第三个参数不是模板参数,所以第三个参数就是用来区分次函数;
      在这里插入图片描述
2. advance 函数
  • advance 函数:将 iterator 进行移动 n 步;
  • 返回值:
    • 不需要返回值;
  • 函数内部:
    • 使用了一个辅助函数 iterator_category()返回 iterator category 的临时对象(只是把向 traits 询问的部分包装起来);
    • iterator category 临时对象传入次函数,根据 iterator category 调用对应的次函数版本
      • 对于 random_access_iterator_tag,直接使用 + n 即可;
      • 对于 bidirectional_iterator_tag,逐步前进 or 逐步后退;
      • 对于 input_iterator_tag,只能逐步前进;
        在这里插入图片描述
3. copy 函数
  • copy 函数:
    • 需要传入三个 iterator;
    • 将前两个 iterator 范围内的内容复制到目标 iterator;
  • 返回值:
    • 返回第三个 iterator;
  • 注意点:
    • function template 没有特化,只能函数重载;
    • 下图中的特化是指函数重载(overloading)
  • 调用函数过程
    • ① 判断 iterator 是否是 C 风格的字符串 pointer,分为三种情况;
    • ② 在①之后判断 iterator 是否是 pointer,分为三种情况;
      • 如果是 pointer,还需要通过 type traits 得知是否有 trivial 拷贝赋值函数,确定是否需要显式调用拷贝赋值函数;
  • type traits** 不是 STL 的内容,但属于 C++ 标准库;
  • 图中右上角和右下角的区别:
    • 右上角:
      • 每次循环都需要判断两个 iterator 是否相等,比起直接判断整数,更慢一些;
    • 右下角:
      • 循环多少次只需要判断整数 n;
        在这里插入图片描述
4. destory 函数
  • 调用函数过程

    • ① 分为四种情况;
    • ② 如果选择泛化版本:
      • 需要通过 type traits 得知是否有 trivial 析构函数,确定是否需要显式调用析构函数;
        在这里插入图片描述
  • 更具体的图示:
    在这里插入图片描述

5. unique_copy 函数
  • 需要注意的部分:

    • forward_iterator_tag 的次函数版本:

      • 其中使用了 iterator 的读取(*运算):

      • if (*result != *first)
        	...
        
    • 【由于 ouput_iterator_tag 是只写 iterator,不允许读取,所以需要为 ouput_iterator_tag 重载一个次函数版本;】

    • ouput_iterator_tag 的次函数版本:

      • 没有 read 操作,只有 write;
        在这里插入图片描述

5. 算法源码对 iterator_category 的“暗示”

1. 算法实现的自由
  • 由于所有的 Algorithm 都是 function template
  • 所以理论上可以传入任何 iterator category 的 iterator;
2. 算法使用时的限制
  • 但是从源码对模板参数的命名可以看出,算法在实际使用时,对 iterator 的类型是有限制的;
  • 如果输入不符合 Algorithm 要求的 iterator,Algorithm 编译不会报错,运行时就会出错;
    在这里插入图片描述

6. iterator category 设计成 class 的原因

  • ① 设计成 class,次函数可以根据 iterator category class 的不同进行函数重载
    • 主函数调用次函数的形式统一,交给编译器进行重载版本选择;
    • 【如果设计成 1、2、3、4、5,就不能实现函数重载(都是整型),只能用 if-else】;
  • ② iterator category 之间可以有继承关系
    • 次函数不需要为每个 iterator category 都重载一个函数;
    • 如果次函数没有子类 iterator category 的重载版本,次函数会自动调用父类的重载版本;【子类自动向上转型】

7. STL 算法的大致设计结构

1. 算法设计结构
  • 一个 主 function template;
  • 多个对应的 次 function template;
  • 次函数根据 iterator_category 参数进行区分;
2. 算法大致内容
  • 内容:
    • 主函数向 traits 询问传入 iterator 的 iterator_category;
      • 使用 iterator 以及 iterator_category 临时对象 调用次函数;
    • ALgorithm 的实现部分在次函数;

8. STL 算法例子

1. accumulate 函数(认识传递 function 的方式)
  • 函数有两个版本:
    • 一个有三个参数(两个 iterator,一个 init 值);
      • 计算 iterator 和 init 的累计和;
    • 一个有四个参数,多了一个 operation;
      • 根据传入的 operation,计算 iterator 和 init 的累计 operation;
  • 函数使用:
    • operation 参数传递:
      • 一般函数;
      • 仿函数(图中的 std::minus() 就是一个仿函数(functor),同时是个template);
        在这里插入图片描述
2. for_each 函数(认识 C++11 新语法)
  • 函数参数:
    • 三个参数(两个 iterator,一个 function):
  • 函数功能:
    • 对容器中的每个值执行同一个操作;
  • C++11 新语法:
    • range-based for statement(基于范围的for语句)
      在这里插入图片描述
3. replace,replace_if,replace_copy 函数(认识参数命名的方式)
  • 函数功能:
    • replace:用新值替代旧值;
    • replace_if:
      • 用新值替代满足条件的值;
      • Predicate:返回 bool 函数称为 Predicate;
    • replace_copy:类似于 replace,但是将新值放至新区间;
      在这里插入图片描述
4. 容器是否有同名的成员函数(即自定义版本)
1. count,count_if 函数
  • 图左边为 STL 中的一般版本;
  • associated containers 有自己的 count 成员函数
    在这里插入图片描述
2. find,find_if 函数
  • find,find_if 是循序式查找;
  • associated containers 有 find 成员函数
    在这里插入图片描述
3. sort 函数
  • list、forward_list 有 find 成员函数
  • 图中使用了 rbegin()、rend(),逆向迭代器;
    在这里插入图片描述
5. 关于 reverse iterator,rbegin(),rend()
  • rbegin() 和 end() 指向的位置相同,但是取值的方式不同;
  • rend() 和 begin() 指向的位置相同,但是取值的方式不同;
  • reverse iterator 的转换,通过 reverse_iterator(这是个 iterator adapter)实现;
    在这里插入图片描述
6. binary_search 函数
  • 函数功能:
    • 已排序的序列中,判断序列中是否存在 value;
  • 函数步骤:
    • 调用 lower_bound 函数:
      • 如果 val 存在,返回第一个 val 的位置 iterator;
      • 如果 val 不存在,返回 val 应该插入的位置 iterator;
    • 根据 lower_bound 返回的位置 iterator,判断是否找到了 value;
  • 函数优化:
    • 在调用 lower_bound 函数之前,应该先判断是否 val < *first(因为序列已经排序);
      在这里插入图片描述
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值