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...
  • There's more...

Was this helpful?

  1. 第3章 迭代器

使用迭代器实现算法

迭代器通常根据指向位置的移动,来遍历容器中的元素,但不需要迭代对应的数据类型。迭代器也会被用来实现算法,其可以通过++it指向下一个元素,并且通过*it解引用得到对应的值。

本节中,我们将用迭代器来实现斐波那契函数。斐波那契函数会有类似如下的迭代:F(n) = F(n - 1) + F(n - 2)。数列的初始值F(0) = 0和 F(1) = 1。这样下列序列就可以进行计算:

  • F(0) = 0

  • F(1) = 1

  • F(2) = F(1) + F(0) = 1

  • F(3) = F(2) + F(1) = 2

  • F(4) = F(3) + F(2) = 3

  • F(5) = F(4) + F(3) = 5

  • F(6) = F(5) + F(4) = 8

  • ...

我们要实现一个函数,可以输出斐波那契第n个数的值。通常我们都会使用函数迭代,或者是循环来实现这个函数。这样的话,我们只能一个个的将相应的值算出来,然后才能计算出下一个值。这里我们有两个选择——递归调用斐波那契函数计算整个数列,这样很浪费计算时间,或者将最后两个斐波那契数作为临时变量,并用它们来计算下一个数。第二种方法我们需要重新实现斐波那契算法循环。这样我们就可以将斐波那契数列计算的代码和我们实际的代码放在一起:

size_ta{0};
size_tb{1};
for(size_ti{0};i< N;++i){
    constsize_told_b{b};
    b+=a;
    a=old_b;
    // do something with b, which is the current fibonacci number
}

使用迭代器实现斐波那契数列是一件很有意思的事情。如何将循环中的迭代,使用迭代器的前向自加操作来代替呢?其实很简单,让我们来看一下。

How to do it...

本节中,我们主要关注如何用一个迭代器实现生成斐波那契数列。

  1. 为了打印斐波那契数列在终端,我们需要包含标准输入输出流头文件。

    #include <iostream>
  2. 我们调用斐波那契迭代器——fibit。其会指向一个值i,其保存的值为斐波那契数列对应的位置,a和b保存斐波那契数列中最后两个值。实例化迭代器时,需要将斐波那契迭代器初始化为F(0)的值:

    class fibit
    {
        size_t i {0};
        size_t a {0};
        size_t b {1};
  3. 下一步,定义标准构造函数和另一个构造函数用来初始化迭代器。

    public:
        fibit() = default;
        explicit fibit(size_t i_)
            : i{i_}
        {}
  4. 当我们对迭代器解引用时,迭代器将返回对应位置的数值。

        size_t operator*() const { return b; }
  5. 当移动迭代器++时,其会移动到下一个斐波那契数上。这里的实现与基于循环的实现几乎是一样的。

        fibit& operator++() {
            const size_t old_b {b};
            b += a;
            a = old_b;
            ++i;
            return *this;
        }
  6. 当使用循环时,增加后的迭代器将会和end迭代器进行比较,所以这里需要为迭代器实现不等于!=操作符。我们只比较当且迭代器所对应的步数,这比循环1000000次再结束迭代器简单许多,这样我们就不需要计算太多的斐波那契数:

        bool operator!=(const fibit &o) const { return i != o.i; }
    };
  7. 为了能让斐波那契迭代器适应for循环的范围写法,我们需要实现一个范围类。我们称这个类为fib_range,其构造函数只需要一个参数,这个参数能够告诉我们我们想要遍历的范围:

    class fib_range
    {
        size_t end_n;
    public:
        fib_range(size_t end_n_)
            : end_n{end_n_}
        {}
  8. begin和end函数将会返回对应位置上的迭代器,也就是F(0)和F(end_n)对应的迭代器。

        fibit begin() const { return fibit{}; }
        fibit end() const { return fibit{end_n}; }
    };
  9. 好了,其他与迭代器相关的代码我们就不管了。因为我们辅助类就能很好的帮助我们将这些细节的东西隐藏掉!让我们打印10个斐波那契数字:

    int main()
    {
        for (size_t i : fib_range(10)) {
               std::cout << i << ", ";
        }
        std::cout << '\n';
    }
  10. 编译运行后,我们会在终端上看到如下的打印:

    1, 1, 2, 3, 5, 8, 13, 21, 34, 55,

There's more...

为了兼容STL中的迭代器,这里实现的迭代器必须支持std::iterator_traits类。想要知道怎么做,要参考一下3.2节(让自己的迭代器与STL的迭代器兼容),其对如何兼容进行了明确地说明。

Note:

试着从迭代器的角度思考,这样的代码在很多情况下就显得十分优雅。不用担心性能,编译器会根据模板对迭代器相关的代码进行优化。

为了保证例子的简洁性,我们并没有对其做任何事情,不过要是作为斐波那契迭代器的发布库的话,其可用性还是比较差的——fibit传入一个参数的构造函数,可以直接使用end迭代器替换,因为fibit并没有包含任何一个合法的斐波那契值,这里的库并不强制使用这种方式。

还有些方面需要进行修复:

  • 将fibit(size_t i_)声明为私有构造函数,并在fibit类中将fib_range类声明为一个友元类。这样用户就只能使用正确的方式进行迭代了。

  • 可以使用迭代器哨兵,避免用户引用end迭代器。可以参考一下3.6节(使用哨兵终止迭代)中内容,以获得更多信息。

Previous使用迭代适配器填充通用数据结构Next使用反向迭代适配器进行迭代

Last updated 6 years ago

Was this helpful?