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::string

std::string类是一个十分有用的类,因为其对字符串的处理很方便。其有一个缺陷,当我们想要根据一个字符串获取其子字符串时,我们需要传入一个指针和一个长度变量,两个迭代器或一段拷贝的子字符串。我在之前的章节也这样使用过,消除字符串前后的空格的最后,使用的是拷贝的方式获得前后无空格的字符串。

当我们想要传递一个字符串或一个子字符串到一个不支持std::string的库中时,需要提供裸指针,这样的用法就回退到C的时代。与子字符串问题一样,裸指针不携带字符串长度信息。这样的话就需要将指针和字符串长度进行捆绑。

另一个十分简单的方式就是使用std::string_view。这个类是C++17添加的新特性,并且能提供将字符串指针与其长度捆绑的方法,其体现了数组引用的思想。

当设计函数时,将std::string实例作为参数,但在函数中使用了额外的内存来存储这些字符,以确保原始的字符串不被修改,这时就可以使用std::string_view,其可移植性很好,与STL无关。可以让其他库来提供一个string_view实现,然后将复杂的实现隐藏在背后,并且可以将其用在我们的STL代码中。这样,string_view类就显得非常小,非常好用,因为其能在不同的库间都可以用。

string_view另一个很酷的特性,就是可以使用非拷贝的方式引用大字符串中的子字符串。本节将使用string_view,从而了解其优点和缺点。我们还会看到如何使用字符串代理来去除字符两端的空格,并不对原始字符串进行修改和拷贝。

How to do it...

本节,将使用string_view的一些特性来实现一个函数,我们将会看到有多少种类型可以输入:

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

    #include <iostream>
    #include <string_view>
    
    using namespace std;
  2. 将string_view作为函数的参数:

    void print(string_view v)
    {
  3. 对输入字符串做其他事情之前,将移除字符开头和末尾的空格。将不会对字符串进行修改,仅适用字符串代理获取没有空格字符串。find_first_not_of函数将会在字符串找到第一个非空格的字符,适用remove_prefix,string_view将指向第一个非空格的字符。当字符串只有空格,find_first_not_of函数会返回npos,其为size_type(-1)。size_type是一个无符号类型,其可以是一个非常大的值。所以,会在字符串代理的长度和words_begin中选择较小的那个:

        const auto words_begin (v.find_first_not_of(" \t\n"));
        v.remove_prefix(min(words_begin, v.size()));
  4. 我们对尾部的空格做同样的事情。remove_suffix将收缩到代理的大小:

        const auto words_end (v.find_last_not_of(" \t\n"));
        if (words_end != string_view::npos) {
            v.remove_suffix(v.size() - words_end - 1);
        }
  5. 现在可以打印字符串代理和其长度:

        cout << "length: " << v.length()
             << " [" << v << "]\n";
    }
  6. 主函数中,将使用print的函数答应一系列完全不同的参数类型。首先,会通过argv传入char*类型的变量,运行时其会包含可执行文件的名字。然后,传入一个string_view的实例。然后,使用C风格的静态字符串,并使用""sv字面字符构造的string_view类型。最后,传入一个std::string。print函数不需要对参数进行修改和拷贝。这样就没有多余的内存分配发生。对于很多大型的字符串,这将会非常有效:

    int main(int argc, char *argv[])
    {
        print(argv[0]);
        print({});
        print("a const char * array");
        print("an std::string_view literal"sv);
        print("an std::string instance"s);
  7. 这里还没对空格移除特性进行测试。这里也给出一个头尾都有空格的字符串:

        print(" \t\n foobar \n \t ");
  8. string_view另一个非常酷的特性是,其给予的字符串是不包含终止符的。当构造一个字符串,比如"abc",没有终止符,print函数就能很安全的对其进行处理,因为string_view携带字符串的长度信息和指向信息:

        char cstr[] {'a', 'b', 'c'};
        print(string_view(cstr, sizeof(cstr)));
    }
  9. 编译并运行程序,就会得到如下的输出,所有字符串都能被正确处理。前后有很多空格的字符串都被正确的处理,abc字符串没有终止符也能被正确的打印,而没有任何内存溢出:

    $ ./string_view
    length: 17 [./string_view]
    length: 0 []
    length: 20 [a const char * array]
    length: 27 [an std::string_view literal]
    length: 23 [an std::string instance]
    length: 6 [foobar]
    length: 3 [abc]

How it works...

我们可以看到,函数可以接受传入一个string_view的参数,其看起来与字符串类型没有任何区别。我们实现的print,对于传入的字符串不进行任何的拷贝。

对于print(argv[0])的调用是非常有趣的,字符串代理会自动的推断字符串的长度,因为需要将其适用于无终止符的字符串。另外,我们不能通过查找终止符的方式来确定string_view实例的长度。正因如此,当使用裸指针(string_view::data())的时候就需要格外小心。通常字符串函数都会认为字符串具有终止符,这样就很难出现使用裸指针时出现内存溢出的情况。这里还是使用字符串代理的接口比较好。

除此之外,std::string接口阵容已经非常豪华了。

Note:

使用std::string_view用于解析字符或获取子字符串时,能避免多余的拷贝和内存分配,并且还不失代码的舒适感。不过,对于std::string_view将终止符去掉这点,需要特别注意。

Previous消除字符串开始和结束处的空格Next从用户的输入读取数值

Last updated 6 years ago

Was this helpful?