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. 第7章 字符串, 流和正则表达

迭代器填充容器——std::istream

上节中,我们学习了如何从输入流中向数据结构中读入数据,然后用这些数据填充列表或向量。

这次,我们将使用标准输入来填充std::map。问题在于我们不能将一个结构体进行填充,然后从后面推入到线性容器中,例如list和vector,因为map的负载分为键和值两部分。

完成本节后,我们会了解到如何从字符流中将复杂的数据结构进行序列化和反序列化。

How to do it...

本节,我们会定义一个新的结构体,不过这次将其放入map中,这会让问题变得复杂,因为容器中使用键值来表示所有值。

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

    #include <iostream>
    #include <iomanip>
    #include <map>
    #include <iterator>
    #include <algorithm>
    #include <numeric>
    
    using namespace std;
  2. 我们会引用网络上的一些梗。这里的梗作为一个名词,我们记录其描述和诞生年份。我们会将这些梗放入std::map,其名称为键,包含在结构体中的其他信息作为值:

    struct meme {
        string description;
        size_t year;
    };
  3. 我们暂时先不去管键,我们先来实现结构体meme的流操作符operator>>。我们假设相关梗的描述由双引号括起来,后跟对应年份。举个栗子,"some description" 2017。通过使用is >> quoted(m.description),双引号会被当做限定符,直接被丢弃。这就非常的方便。然后我们继续读取年份即可:

    istream& operator>>(istream &is, meme &m) {
        return is >> quoted(m.description) >> m.year;
    }
  4. OK,现在将梗的名称作为键插入map中。为了实现插入map,需要一个std::pair<key_type, value_type>实例。key_type为string,那么value_type就是meme了。名字中允许出现空格,所以可以使用quoted对名称进行包装。p.first是名称,p.second代表的是相关meme结构体变量。可以使用operator>>实现直接对其进行赋值:

    istream& operator >>(istream &is,
                        pair<string, meme> &p) {
        return is >> quoted(p.first) >> p.second;
    }
  5. 现在来写主函数,创建一个map实例,然后对其进行填充。因为对流函数operator>>进行了重载,所以可以直接对istream_iterator类型直接进行处理。我们将会从标准输入中解析出更多的信息,然后使用inserter迭代器将其放入map中:

    int main()
    {
        map<string, meme> m;
    
        copy(istream_iterator<pair<string, meme>>{cin},
               {},
            inserter(m, end(m)));
  6. 对梗进行打印前,先在map中找到名称最长的梗吧。可以对其使用std::accumulate。累加的初始值为0u(u为无符号类型),然后逐个访问map中的元素,将其进行合并。使用accumulate合并,就意味着叠加。例子中,并不是对数值进行叠加,而是对最长字符串的长度进行进行累加。为了得到长度,我们为accumulate提供了一个辅助函数max_func,其会将当前最大的变量与当前梗的名字长度进行比较(这里两个数值类型需要相同),然后找出这些值中最大的那个。这样accumulate函数将会返回当前梗中,名称最长的梗:

        auto max_func ([](size_t old_max,
                         const auto &b) {
            return max(old_max, b.first.length());
        });
        size_t width {accumulate(begin(m), end(m),
                                0u, max_func)};
  7. 现在,对map进行遍历,然后打印其中每一个元素。使用<< left << setw(width)打印出漂亮的“表格”:

        for (const auto &[meme_name, meme_desc] : m) {
            const auto &[desc, year] = meme_desc;
    
            cout << left << setw(width) << meme_name
                 << " : " << desc
                 << ", " << year << '\n';
        }
    }
  8. 现在需要一些梗的数据,我们写了一些梗在文件中:

    "Doge" "Very Shiba Inu. so dog. much funny. wow." 2013
    "Pepe" "Anthropomorphic frog" 2016
    "Gabe" "Musical dog on maximum borkdrive" 2016
    "Honey Badger" "Crazy nastyass honey badger" 2011
    "Dramatic Chipmunk" "Chipmunk with a very dramatic look" 2007
  9. 编译并运行程序,将文件作为数据库进行输入:

    $ cat memes.txt | ./filling_containers
    Doge: Very Shiba Inu. so dog. much funny. wow., 2013
    Dramatic Chipmunk : Chipmunk with a very dramatic look, 2007
    Gabe: Musical dog on maximum borkdrive, 2016
    Honey Badger: Crazy nastyass honey badger, 2011
    Pepe: Anthropomorphic frog, 2016

How it works...

本节有三点需要注意。第一,没有选择vector或list比较简单的结构,而是选择了map这样比较复杂的结构。第二,使用了quoted控制符对输入流进行处理。第三,使用accumulate来找到最长的键值。

我们先来看一下map,结构体meme只包含一个description和year。因为我们将梗的名字作为键,所以没有将其放入结构体中。可以将std::pair实例插入map中,首先实现了结构体meme的流操作符operator>>,然后对pair<string, meme>做同样的事。最后,使用istream_iterator<pair<string, meme>>{cin}从标准输入中获取每个元素的值,然后使用inserter(m, end(m))将组对插入map中。

当我们使用流对meme元素进行赋值时,允许梗的名称和描述中带有空格。我们使用引号控制符,很轻易的将问题解决,得到的信息类似于这样,"Name with spaces" "Description with spaces" 123。

当输入和输出都对带有引号的字符串进行处理时,std::quoted就能帮助到我们。当有一个字符串s,使用cout << quoted(s)对其进行打印,将会使其带引号。当对流中的信息进行解析时,cin >> quoted(s)其就能帮助我们将引号去掉,保留引号中的内容。

叠加操作是对max_func的调用看起来很奇怪:

auto max_func ([](size_t old_max, const auto &b) {
    return max(old_max, b.first.length());
});

size_t width {accumulate(begin(m), end(m), 0u, max_func)};

实际上,max_func能够接受一个size_t和一个auto类型的参数,这两个参数将转换成一个pair,从而就能插入map中。这看起来很奇怪,因为二元函数会将两个相同类型的变量放在一起操作,例如std::plus。我们会从每个组对中获取键值的长度,将当前元素的长度值与之前的最长长度相对比。

叠加调用会将max_func的返回值与0u值进行相加,然后作为左边参数的值与下一个元素进行比较。第一次左边的参数为0u,所以就可以写成max(0u, string_length),这时返回的值就作为之前最大值,与下一个元素的名称长度进行比较,以此类推。

Previous使用输入文件初始化复杂对象Next迭代器进行打印——std::ostream

Last updated 6 years ago

Was this helpful?