C++ Prime 0x0A 学习笔记
Posted 鱼竿钓鱼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Prime 0x0A 学习笔记相关的知识,希望对你有一定的参考价值。
📔 C++ Prime 0x0A 学习笔记
推荐阅读 《C++ Primer 5th》知识点总结&练习题解
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
是链表数据结构特用的,不需要通用版本- 链表特有的操作会改变容器
以上是关于C++ Prime 0x0A 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章