C++17 STL Cook Book
  • Introduction
  • 前言
  • 关于本书
  • 各章梗概
  • 第1章 C++17的新特性
    • 使用结构化绑定来解包绑定的返回值
    • 将变量作用域限制在if和switch区域内
    • 新的括号初始化规则
    • 构造函数自动推导模板的类型
    • 使用constexpr-if简化编译
    • 只有头文件的库中启用内联变量
    • 使用折叠表达式实现辅助函数
  • 第2章 STL容器
    • 擦除/移除std::vector元素
    • 以O(1)的时间复杂度删除未排序std::vector中的元素
    • 快速或安全的访问std::vector实例的方法
    • 保持对std::vector实例的排序
    • 向std::map实例中高效并有条件的插入元素
    • 了解std::map::insert新的插入提示语义
    • 高效的修改std::map元素的键值
    • std::unordered_map中使用自定义类型
    • 过滤用户的重复输入,并以字母序将重复信息打印出——std::set
    • 实现简单的逆波兰表示法计算器——std::stack
    • 实现词频计数器——std::map
    • 实现写作风格助手用来查找文本中很长的句子——std::multimap
    • 实现个人待办事项列表——std::priority_queue
  • 第3章 迭代器
    • 建立可迭代区域
    • 让自己的迭代器与STL的迭代器兼容
    • 使用迭代适配器填充通用数据结构
    • 使用迭代器实现算法
    • 使用反向迭代适配器进行迭代
    • 使用哨兵终止迭代
    • 使用检查过的迭代器自动化检查迭代器代码
    • 构建zip迭代适配器
  • 第4章 Lambda表达式
    • 使用Lambda表达式定义函数
    • 使用Lambda为std::function添加多态性
    • 并置函数
    • 通过逻辑连接创建复杂谓词
    • 使用同一输入调用多个函数
    • 使用std::accumulate和Lambda函数实现transform_if
    • 编译时生成笛卡尔乘积
  • 第5章 STL基础算法
    • 容器间相互复制元素
    • 容器元素排序
    • 从容器中删除指定元素
    • 改变容器内容
    • 在有序和无序的vector中查找元素
    • 将vector中的值控制在特定数值范围内——std::clamp
    • 在字符串中定位模式并选择最佳实现——std::search
    • 对大vector进行采样
    • 生成输入序列的序列
    • 实现字典合并工具
  • 第6章 STL算法的高级使用方式
    • 使用STL算法实现单词查找树类
    • 使用树实现搜索输入建议生成器
    • 使用STL数值算法实现傅里叶变换
    • 计算两个vector的误差和
    • 使用ASCII字符曼德尔布罗特集合
    • 实现分割算法
    • 将标准算法进行组合
    • 删除词组间连续的空格
    • 压缩和解压缩字符串
  • 第7章 字符串, 流和正则表达
    • 创建、连接和转换字符串
    • 消除字符串开始和结束处的空格
    • 无需构造获取std::string
    • 从用户的输入读取数值
    • 计算文件中的单词数量
    • 格式化输出
    • 使用输入文件初始化复杂对象
    • 迭代器填充容器——std::istream
    • 迭代器进行打印——std::ostream
    • 使用特定代码段将输出重定向到文件
    • 通过集成std::char_traits创建自定义字符串类
    • 使用正则表达式库标记输入
    • 简单打印不同格式的数字
    • 从std::iostream错误中获取可读异常
  • 第8章 工具类
    • 转换不同的时间单位——std::ratio
    • 转换绝对时间和相对时间——std::chrono
    • 安全的标识失败——std::optional
    • 对元组使用函数
    • 使用元组快速构成数据结构
    • 将void*替换为更为安全的std::any
    • 存储不同的类型——std::variant
    • 自动化管理资源——std::unique_ptr
    • 处理共享堆内存——std::shared_ptr
    • 对共享对象使用弱指针
    • 使用智能指针简化处理遗留API
    • 共享同一对象的不同成员
    • 选择合适的引擎生成随机数
    • 让STL以指定分布方式产生随机数
  • 第9章 并行和并发
    • 标准算法的自动并行
    • 让程序在特定时间休眠
    • 启动和停止线程
    • 打造异常安全的共享锁——std::unique_lock和std::shared_lock
    • 避免死锁——std::scoped_lock
    • 同步并行中使用std::cout
    • 进行延迟初始化——std::call_once
    • 将执行的程序推到后台——std::async
    • 实现生产者/消费者模型——std::condition_variable
    • 实现多生产者/多消费者模型——std::condition_variable
    • 并行ASCII曼德尔布罗特渲染器——std::async
    • 实现一个小型自动化并行库——std::future
  • 第10章 文件系统
    • 实现标准化路径
    • 使用相对路径获取规范的文件路径
    • 列出目录下的所有文件
    • 实现一个类似grep的文本搜索工具
    • 实现一个自动文件重命名器
    • 实现一个磁盘使用统计器
    • 计算文件类型的统计信息
    • 实现一个工具:通过符号链接减少重复文件,从而控制文件夹大小
Powered by GitBook
On this page
  • How to do it...
  • How it works...

Was this helpful?

  1. 第6章 STL算法的高级使用方式

将标准算法进行组合

gather为STL算法中最好的组合性例子。Sean Parent在任Adobe系统首席科学家时,就在向世人普及这个算法,因为其本身短小精悍。其使用方式就如同做一件艺术品一样。

gather算法能操作任意的元素类型。其更改元素的顺序,通过用户的选择,其会将对应的数据放置在对应位置上。

How to do it...

本节,我们来实现gather算法,并对其进行一定的修改。最后,将展示如何进行使用:

  1. 包含必要的头文件,声明所使用的命名空间。

    #include <iostream>
    #include <algorithm>
    #include <string>
    #include <functional>
    
    using namespace std;
  2. gather算法是表现标准算法组合性很好的一个例子。gather接受一对begin/end迭代器和另一个迭代器gather_pos,其指向begin和end迭代器的中间位置。最后一个参数是一个谓词函数。使用谓词函数,算法会将满足谓词条件的所有元素放置在gather_pos迭代器附近。使用std::stable_partition来完成移动元素的任务。gather算法将会返回一对迭代器。这两个迭代器由stable_partition所返回,其表示汇集范围的起始点和结束点:

    template <typename It, typename F>
    pair<It, It> gather(It first, It last, It gather_pos, F predicate)
    {
        return {stable_partition(first, gather_pos, not_fn(predicate)),
                stable_partition(gather_pos, last, predicate)};
    }
  3. 算法的另一种变体为gather_sort。其工作原理与gather相同,不过其不接受一元谓词函数;其能接受一个二元比较函数。这样,其就也能将对应值汇集在gather_pos附近,并且能知道其中最大值和最小值:

    template <typename It, typename F>
    void gather_sort(It first, It last, It gather_pos, F comp_func)
    {
        auto inv_comp_func ([&](const auto &...ps) {
            return !comp_func(ps...);
        });
    
        stable_sort(first, gather_pos, inv_comp_func);
        stable_sort(gather_pos, last, comp_func);
    }
  4. 让我们来使用一下这些算法。先创建一个谓词函数,其会告诉我们当前的字母是不是a,再构建一个字符串,仅包含a和-字符:

    int main()
    {
        auto is_a ([](char c) { return c == 'a'; });
        string a {"a_a_a_a_a_a_a_a_a_a_a"};
  5. 继续构造一个迭代器,让其指向字符串的中间位置。这时可以调用gather算法,然后看看会发生什么。a字符将汇集在字符串中间的某个位置附近:

        auto middle (begin(a) + a.size() / 2);
    
        gather(begin(a), end(a), middle, is_a);
        cout << a << '\n';
  6. 再次调用gather,不过这次gather_pos的位置在字符串的起始端:

        gather(begin(a), end(a), begin(a), is_a);
        cout << a << '\n';
  7. 再将gather_pos的位置放在末尾试试:

        gather(begin(a), end(a), end(a), is_a);
        cout << a << '\n';
  8. 最后一次,这次将再次将迭代器指向中间位置。这次与我们的期望不相符,后面我们来看看发生了什么:

        // This will NOT work as naively expected
        gather(begin(a), end(a), middle, is_a);
        cout << a << '\n';
  9. 再构造另一个字符串,使用下划线和一些数字组成。对于这个输入队列,我们使用gather_sort。gather_pos迭代器指向字符串的中间,并且比较函数为std::less<char>:

        string b {"_9_2_4_7_3_8_1_6_5_0_"};
        gather_sort(begin(b), end(b), begin(b) + b.size() / 2,
                   less<char>{});
        cout << b << '\n';
    }
  10. 编译并运行程序,就会看到如下的输出。对于前三行来说和预期一样,不过第四行貌似出现了一些问题。最后一行中,我们可以看到gather_short函数的结果。数字的顺序是排过序的(中间小,两边大):

    $ ./gather
    _____aaaaaaaaaaa_____
    aaaaaaaaaaa__________
    __________aaaaaaaaaaa
    __________aaaaaaaaaaa
    _____9743201568______

How it works...

gather算法有点难以掌握,因为其非常短,但处理的是比较复杂的问题。让我们来逐步解析这个算法:

  1. 初始化相应元素,并提供一个谓词函数。图中满足谓词函数条件的元素为灰色,其他的为白色。迭代器a和c表示整个范围的长度,并且迭代器b指向了最中间的元素。这就表示要将所有灰色的格子聚集在这个迭代器附近。

  2. gather算法会对范围(a, b]调用std::stable_partition,并对另一边使用不满足谓词条件的结果。这样就能将所有灰色格子集中在b迭代器附近。

  3. 另一个std::stable_partition已经完成,不过在[b, c)间我们将使用满足谓词函数的结果。这样就将灰色的格子汇集在b迭代器附近。

  4. 所有灰色的格子都汇集在b迭代器附近,这是就可以返回起始迭代器x和末尾迭代器y,用来表示所有连续灰色格子的范围。

我们对同一个范围多次调用gather算法。最初,将所有元素放在范围中间位置。然后,尝试放在开始和末尾。这种实验很有趣,因为这会让其中一个std::stable_partition无元素可以处理。

最后一次对gather进行调用时,参数为(begin, end, middle),但没有和我们预期的一样,这是为什么呢?这看起来像是一个bug,实际上不是。

试想一个字符组aabb,使用谓词函数is_character_a,用来判断元素是否为a,当我们将第三个迭代器指向字符范围的中间时,会复现这个bug。原因:第一个stable_partition调用会对子范围aa进行操作,并且另一个stable_partition会对子范围bb上进行操作。这种串行的调用时无法得到baab的,其结果看起来和开始一样,没有任何变化。

Note:

要是想得到我们预期的序列,我们可以使用std::rotate(begin, begin + 1, end);

gather_sort基本上和gather差不多。签名不同的就是在于谓词函数。实现的不同在于gather调用了两次std::stable_partition,而gather_sort调用了两次std::stable_sort。

这是由于not_fn不能作用域二元函数,所以反向比较不能由not_fn完成。

Previous实现分割算法Next删除词组间连续的空格

Last updated 6 years ago

Was this helpful?