📔 C++ Primer 0x0A 学习笔记

​更好的阅读体验​

10.1 概述

  • 迭代器令算法不依赖于容器,但算法依赖于元素类型的操作
  • 泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作
  • 算法永远不会改变底层容器的大小,可能改变保存元素的值或位置,但永远不会直接添加或删除元素
  • 大部分在头文件​​algorithm​​​,部分在​​numeric​

10.2 初识泛型算法

10.2.1 只读算法

  • ​accumulate​​求和算法,第三个参数类型决定函数使用哪个加法运算符以及返回值的类型
  • ​accumulate​​​编程假定将元素类型加到和的元素类型上的操作必须是可行的,注意​​""​​​是​​const char*​​​类型的,没有定义​​+​​​运算符,需要用​​string("")​
  • ​equal​​用于确定两个序列是否保存相同的值,编程假定第二个序列至少与第一个序列一样长
  • 那些接受一个单一迭代器表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长
  • 对于只读算法,最好使用​​cbegin()​​​和​​cend()​

10.2.2 写容器算法

  • ​fill​​想给定输入序列写入数据,只要传递有效的输入序列,写入操作就是安全的
  • 算法不检查写操作,向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素
  • 插入迭代器是一种向容器中添加元素的迭代器,保证算法有足够元素空间来容纳输出数据
  • ​back_inserter​​​接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。通过对此迭代器赋值,赋值运算会调用​​push_back​​将一个具有给定值的元素添加到容器中
  • 拷贝算法将输入范围中的元素拷贝到目的序列中,传递给​​copy​​的目的序列至少包含和输入序列一样多的元素

10.2.3 重排容器元素的算法

  • 去重
sort(words.begin(),words.end());
words.erase(unique(words.begin(),words.end()),words.end());
  • 排序后才用​​unique​​,​​unique​​返回最后一个不重复元素之后的位置,实际上并不删除元素,只是覆盖相邻的重复元素(算法不会改变容器元素数量)

10.3 定制操作

10.3.1 向算法传递函数

  • 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。
  • 标准库谓词分为一元谓词和二元谓词
  • 接受谓词参数的算法对输入序列中的元素调用谓词,元素类型必须能转化为谓词的参数类型
  • ​sort​​第三个参数就是一个谓词

10.3.2 lambda 表达式

  • 算法谓词必须严格接受一个或两个参数,但是如果我们需要更多参数,我们需要使用​​lambda​
  • 我们可以向一个算法传递任何类别的可调用对象,对于一个对象或表达式,如果可以对其使用那个调用运算符,则称为可调用的
  • 可调用对象
  • 函数
  • 函数指针
  • 重载了函数调用的类
  • ​lambda​​ 表达式
  • ​lambda​​表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数
  • ​lambda​​​表达式可能定义在函数内部,可以忽略参数列表和返回类型(根据函数体代码推断,如果只是一个​​return​​语句,则返回类型从返回的表示类型推断,否则返回类型为​​void​​)
  • 如果​​lambda​​的函数体包含任一单一语句之外的内容,且未指定返回类型,则返回​​void​
  • 如果​​lambda​​在函数体内,那么​​lambda​​通过使用捕获列表指明所在函数中定义的局部变量列表,只有在捕获列表里的局部变量才能被​​lambda​​的函数体使用
  • 捕获列表只用于局部非​​static​​变量,​​lambda​​可以直接使用局部​​static​​变量和它所在函数之外声明的名字

10.3.3 lambda 捕获和返回

  • 当定义一个​​lambda​​​时,编译器生成一个与​​lambda​​​对于的新的(未命名的)类类型,包含​​lambda​​所捕获的变量的数据成员
  • 变量的捕获方式可以是值或引用
  • 值捕获在​​lambda​​​创建时拷贝,对其修改不会影响​​lambda​​内对应的值
  • 引用捕获和其他任何类型的引用行为类似,注意确保被引用对象在​​lambda​​指向的时候是存在的
  • 将两保证​​lambda​​的变量捕获简单化,尽量减少捕获的数据量,尽可能避免捕获指针或引用
  • 隐式捕获让编译器根据​​lambda​​​体中代码推断要使用那些变量,在捕获里写一个​​&​​​表示采用捕获引用,​​=​​表示采用值捕获
  • 混合使用隐式捕获和显示捕获时,捕获列表第一个必须是​​=​​​或​​&​
  • 值被拷贝的变量,​​lambda​​​不会改变其值,所以我们如果希望改变一个被捕获的变量的值,就必须在参数列表首加上关键字​​mutable​​​,因此可变​​lambda​​可以甚略参数列表
  • 一个引用变量捕获的变量是否可以修改依赖于指向的是一个​​const​​​还是非​​const​​类型
  • 当我们需要为一个​​lambda​​定义返回类型时,必须采用尾置返回类型

10.3.4 参数绑定

  • 对于那种只有一两个地方使用的简单操作,​​lambda​​表达式最有用。如果多次使用,或者需要很多语句才能完成,应该定义一个函数
  • 如果​​lambda​​​的捕获列表为空,通常可以用函数来代替它;但是对于捕获局部变量的​​lambda​​​,替换不容易,因为谓词被严格限定了,这时候我们可以用​​bind​​来解决
  • 可以将​​bind​​函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
  • _1,_2,_n 等占位符分别表示新可调用对象的第 1,2,n 个参数,将 _n 放在 bind 中不同的参数位置,表示将新可调用对象的第 n 个参数和旧可调用对象在该位置的参数绑定在了一起。
  • _1,_2 等定义在命名空间 placeholders 中,placeholders 这个名字定义在 std 中。placeholders 的实现定义在 functional 头文件中。
  • ​bind​​的主要功能:减少参数数目,改变参数顺序
  • 假设被绑定的函数接受 ​​n​​​ 个参数,那么​​bind​​​ 接受​​n + 1​​个参数。
  • 默认情况下,​​bind​​​那些不是占位符的参数被拷贝到​​bind​​返回的可调用对象中
  • 如果要传递引用或参数类型不能拷贝(例如IO类)使用​​ref​​函数
  • ​ref​​​ 函数接受一个参数,返回一个可以拷贝的对象,该对象含有参数的引用。​​cref​​​生成保存 ​​const​​​ 引用的类。​​ref​​​ 和​​cref​​​ 也定义在头文件 ​​functional​​ 中。

10.4 再探迭代器

除了每个容器定义的迭代器之外,在头文件​​iterator​​中定义了额外集中迭代器

  • 插入迭代器:绑定到一个容器上,可用来向容器插入元素
  • 流迭代器:绑定到输入或输出流上,可以用来遍历所关联的​​IO​​流
  • 反向迭代器:向后而不是向前移动,除了​​forward_list​​之外的标准库容器都有反向迭代器
  • 移动迭代器:专门用来移动元素

10.4.1 插入迭代器

  • ​back_inserter​​​创建一个使用​​push_back​​的迭代器
  • ​front_inserter​​​创建一个使用​​push_front​​的迭代器器
  • ​inserter​​​创建一个使用​​insert​​的迭代器

10.4.2 iostream 迭代器

  • ​iostream​​​类型不是容器,但是标准库定义了可以用于这些 ​​IO​​类型对象的迭代器
  • ​istream_iterator​​读取输入流
  • ​ostream_iterator​​向一个输入流写数据
  • 这些迭代器将它们对应的流当作一个特定类型的元素序列来处理
  • ​istream_iterator​​操作
  • 创建一个流迭代器必须指定要读写的对象类型
  • 使用​​>>​​读取数据
  • 创建时可以绑定到一个流,或者默认初始化迭代器,这样创建一个可以当作尾后值使用的迭代器
  • 允许懒惰求值,标准库并不保证迭代器立即从流读取数据,可以使用时再读取
  • ​ostream_iterator​​操作
  • 使用​​<<​​写数据
  • 可以提供一个C风格字符串作为第二参数,在每个元素后都会打印此字符串
  • 必须将​​ostream_iterator​​​绑定到一个指定的流,不允许空的或表示尾后位置的​​ostream_iteratro​
  • 我们可以为任何定义了输入运算符(​​>>​​​)的类型创建​​istream_iterator​​​ 对象,​​ostream_iterator​​同理

10.4.3 反向迭代器

  • 从尾元素向首元素反向移动的迭代器,递增和递减操作含义会颠倒
  • 我们只能从既支持​​++​​​又支持​​--​​的迭代器来定义反向迭代器
  • 除了​​forward_list​​标准容器的迭代器都支持两者
  • 流迭代器不支持递减
  • 反向器的目的是表示元素范围,而这些范围是不对称的,这导致当我们从一个普通迭代器初始化一个反向迭代器或给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素
  • 反向迭代器不支持两个迭代器相减,但可以通过​​distance(it1,it2)​​来获得两个反向迭代器的距离

10.5 泛型算法结构

算法所要求的迭代器可分为下面5类

  • 输入迭代器:只读,不写;单遍扫描,只能递增
  • 输出迭代器:只写,不读;单遍扫描,只能递增
  • 前向迭代器:可读写;多遍扫描,只能递增
  • 双向迭代器:可读写;多遍扫描,可递增递减
  • 随机访问迭代器:可读写,多遍扫描,支持全部迭代器运算

10.5.1 5 类迭代器

  • 5类迭代器形成了层次,除了输出迭代器之外,高层类别的迭代器都支持低层类别迭代器的所有操作
  • 输入迭代器必须支持:​​==​​​,​​!=​​​,​​++​​​,​​*​​​,​​->​
  • 输出迭代器必须支持(​​ostream_iterator​​​类型也是输出迭代器):​​++​​​,​​*​
  • 前向迭代器
  • ​==​​​,​​!=​​​,​​++​​​,​​*​​​,​​->​
  • ​replace​​​算法要求前向迭代器,​​foward_list​​上的迭代器也是前向迭代器
  • 双向迭代器
  • ​==​​​,​​!=​​​,​​++​​​,​​--​​​,​​*​​​,​​->​
  • 算法​​reverser​​​要求双向迭代器,除了​​foward_list​​其他标准库都提供双向迭代器
  • 随机访问迭代器
  • ​==​​​,​​!=​​​,​​<​​​,​​<=​​​,​​>​​​,​​>=​​​,​​++​​​,​​--​​​,​​+​​​,​​+=​​​,​​-​​​,​​-=​​​,​​*​​​,​​->​​​,​​iter[n]​​​==​​*(iter[n])​
  • ​sort​​​要求随机访问运算符,​​array​​​、​​deque​​​、​​string​​​和​​vector​​迭代器都是随机访问迭代器,用于访问内置数组的指针也是

10.5.2 算法形参模式

  • 接受单个目标迭代器的算法假定目标空间足够容纳写入数据
  • 接受单独​​beg2​​​的算法假定从​​beg2​​​开始的序列与​​beg​​​和​​end​​所标示的范围至少一样大

10.5.3 算法命名规范

  • 一些算法使用重载形式传递一个谓词
  • 也有用_if 版本的算法标示接受谓词的版本
  • ​_copy​​区分拷贝元素的版本和不拷贝的版本

10.6 特定容器算法

  • 与其他容器不同链表类型的​​list​​​和​​forward_list​​​定义了几个成员函数形式的算法,它们定义了独特的​​sort​​​、​​merge​​​、​​remove​​​、​​reverse​​​和​​unique​
  • 通用版本的​​sort​​​要求随机访问存储器因此不能用于​​list​​​和​​foward_list​​,这两个类型分别提供双向迭代器和前向迭代器
  • 对于​​list​​​和​​forward_list​​应该优先使用成员函数版本的算法而不是通用算法
  • ​splice​​是链表数据结构特用的,不需要通用版本
  • 链表特有的操作会改变容器