C++ 11
文章目录
- 一、列表初始化
- 二、右值引用和移动语义
- 三、可变参数模板
- 四、新的类功能
- 五、STL中的一些变化
- 六、lambda
- 七、包装器
一、列表初始化
1.C++98中传统的{}
C++98支持数组使用{}进行初始化,也支持结构体和类使用{}进行初始化
(注意:只有当结构体和类的成员变量全部都是public时才可以使用{}进行初始化)

2.C++11中的{}
2.2内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时
对象,最后优化了以后变成直接构造(自定义类型本质上是构造函数隐式类型转换)
也就是自定义类型想要实现{}进行初始化需要有对应的构造函数支持
2.3列表初始化的过程中可以省略=
2.4C++11列表初始化的本意是想实现⼀个大统一的初始化方式,其次他在有些场景下带来
的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便

3.C++11中的std::initializer_list
thetype of il is an initializer_list ,这个类的本质是底层开一个数组,将数据拷贝

3.3容器支持⼀个std::initializer_list的构造函数,也就支持任意多个值构成的
{x1,x2,x3...}进行初始化。STL中的容器支持任意多个值构成的 {x1,x2,x3...} 进行初始化,
就是通过std::initializer_list的构造函数支持的

3.4std::initializer_list的构造函数运行逻辑
首先编译器看见{x1,x2,x3...}就会推导出这是一个std::initializer_list类型,那么
就会直接去调用容器的std::initializer_list版本的构造函数,使用{}初始化这个
构造函数参数std::initializer_list类型的对象,此时会在栈区上开辟一块空间
用于存储这些数据,然后std::initializer_list中有两个指针指向这一块空间的起始
和结束在容器的构造函数中,再通过std::initializer_list的迭代器遍历这一块空间
将这个空间中的数据一个一个进行插入

二、右值引用和移动语义
1.左值和右值
1.1左值右值的核心区别:是否拥有明确的存储空间,是否持久存储,是否可以取地址
拥有明确的存储空间和持久存储就可以取地址
1.2左值:变量,const 变量,字符串常量指针解引用都是左值
左值的特点就是拥有明确的存储空间,持久存储(在当前作用域持久存储),可以取地址
1.3右值:常量,匿名对象,临时对象
右值的特点就是没有明确的存储空间,可能是编译器编译时直接嵌入进代码,可能存储
在寄存器(寄存器是不可以取地址的),可能存储在内存空间,但是生命周期只在当前
表达式中无法取地址,右值最显著的特点就是不可以取地址,只是短暂存储一下,
生命周期只在当前这一行,右值都不可以进行修改,但是不能修改的不一定是右值
比如const 变量不可以进行修改,但是const 变量右明确的存储空间并且持久存储
可以取地址,是左值
1.4知识补充:
对于整型,浮点型,字符型的字面量常量,编译器在编译时一般都是直接嵌入到代码
当中,并不会开辟空间去存储这些字面量常量,对于整型、浮点型、字符型的数组
需要大量的字面量常量去初始化时,那么编译器就会在常量区开辟一块空间存储这些
数据,当程序执行到数组时再去常量区进行拷贝,那么这些就是左值,因为有明确
的存储空间并且持久存储,字符串常量的逻辑和数组类似,编译器识别到字符串常量
就会在静态区开辟一块内存空间存储该字符串,当程序指向到字符串这里时,再去
常量区将该字符串拷贝过来,此时字符串常量拥有明确的存储空间并且持久存储可以
去地址是左值

2.左值引用和右值引用
2.1左值引用就是给左值取别名,右值引用就是给右值取别名
2.2左值引用不可以直接引用右值,但是const左值引用可以引用右值
右值引用不可以直接引用左值,但是右值引用可以引用move过后的左值
(move使得左值的左值属性在当前表达式中的属性转换为右值)
2.3注意:变量表达式的属性都是左值属性,也就是说当一个右值被右值引用引用
过后,该右值引用的属性是左值,也就是当右值被右值引用引用过后,那么该右值
引用是不可以赋值给其他右值引用的,因为此时右值引用的属性是左值,所以
可以赋值给其他左值引用
2.4从语法层面来看,右值引用和左值引用一样不开空间,但是从底层来看,编译器
会存储该被引用对象的地址,然后当使用该引用时,自动转变为*该地址

3.延生命周期
3.1右值的生命周期只在当前这一行的表达式当中,当执行完这一个表达式之后,临时
对象,匿名对象就会自动销毁,而当右值被右值引用引用之后,该右值的生命周期就会
随着这个右值引用去走,也就是只有当该右值引用的生命周期到了,该右值的生命周期
才会到,该右值才会销毁
3.2const左值引用也可以延长临时对象,匿名对象的生命周期,不过被const左值引用
引用的临时对象/匿名对象是不可以进行修改的(临时对象/匿名对象具有常性)
3.3普通右值引用引用的临时对象/匿名对象是可以进行修改的,因为当右值被普通右值
引用引用过后普通右值引用是一个左值属性,那么此时右值就可以通过右值引用进行修
改

4.左值和右值的参数匹配
4.1此时我们拥有左值引用和右值引用两个引用版本,那么在编写函数时,函数的参数
我们既可以写左值引用,可以写const 左值引用,可以写右值引用,那么在调用函数时
是如何匹配这些不同参数的函数的
4.2原理
4.2.1当左值版本,const左值版本,右值版本同时存在
普通左值匹配左值版本,const左值匹配const左值版本,右值匹配右值版本

4.2.2当只拥有左值版本,右值版本
普通左值匹配左值版本,const左值没有可以匹配版本会报错,右值匹配右值版本

4.2.3当拥有const左值版本,右值版本时
普通左值匹配const左值版本,const左值匹配const左值版本
右值匹配右值版本

4.2.4当只拥有const左值版本时
普通左值、左值、右值均匹配const左值版本

5.右值引用移动语义使用场景
5.1左值引用使用场景
左值引用主要使用场景:在函数传参传返回值时左值引用可以减少拷贝,并且可以
在函数体内部通过左值引用对左值进行修改
5.2引入右值和右值引用的目的
5.2.1观察一个场景

此时我们发现,左值引用并不能解决这种场景下的拷贝问题
但是回想一下右值的概念,没有明确的存储空间,并不是持久存储,最重要的是
生命周期只在当前这一行,也就是出了当前这一行表达式它就要死了,它就要进行
销毁,在当前场景下的string,vector也是相同的道理,函数执行完毕过后,它就要
死了就要销毁了,而我们又去拷贝一个快要死的对象,然后再让改对象进行销毁析构
那么为何不直接将该对象的资源进行转移,将该对象资源转移后,使得该对象为空
那么我们不就不需要对这种快要死的对象快要销毁的对象进行拷贝了,并且该对象的
资源移动过后,该对象资源为空不需要再去释放什么
右值和右值引用产生的目的就是为了这种场景,右值是快要死的值,是认为无用的值
那么对于这种对象我们就不要去拷贝,直接移动该对象的资源,然后再使得该对象的
资源为空,那么就可以避免拷贝以及该对象在进行析构时不需要再析构什么资源
这就是右值引用的移动语义,在这种场景下的string,vector也会被认为是右值
此时就不需要再进行拷贝以及析构大量资源,直接将该对象的资源进行转移就可以
5.3移动构造和移动赋值
5.3.1上面的场景我们揭示了引入右值和右值引用的本质目的:
面对快要死的值快要销毁的值需要进行拷贝时,不直接去拷贝快要销毁的对象
而是将快要销毁的对象的资源进行转移,那么就可以减少拷贝提升小笼包,并且
快要销毁的值的资源被移动走了,那么该快要销毁的对象销毁析构时也不需要析构
什么资源,但是如果进行资源的转移呢? --- 通过移动构造和移动赋值
5.3.2移动构造和移动赋值
移动构造和移动赋值是c++11新增的两个默认成员函数,当没有显示编写析构函数
拷贝构造函数和赋值运算符重载函数时,那么编译器会自动在类中添加一个移动
构造函数和移动赋值(当没有显示写析构函数时,认为没有什么资源需要进行释放
如果有资源需要进行析构那么就应该去显示定义移动构造函数和移动赋值函数)
编译器自己编写的移动构造函数对于内置类型的成员变量进行浅拷贝/值拷贝,对于
自定义类型的成员变量使用该自定义类型的移动构造,对于基类的那一部分调用
基类的移动构造函数(移动赋值与移动构造保持一致)
移动构造函数和拷贝构造函数要求一致第一个参数必须为该类型对象的右值引用
可以有其他参数,但是必须拥有缺省值
移动赋值函数和拷贝赋值函数要求一致第一个参数必须为该类型对象的右值引用
可以有其他参数,但是必须拥有缺省值
拷贝/移动要求第一个参数必须为该类型对象的引用,其余参数必须拥有缺省值是为
了在一些情况下编译器使用拷贝/移动可以直接使用该类型对象去调用,其他参数不
需要去继续考虑
对于string,vector这种需要深拷贝的类就需要自己去实现移动构造/赋值,可以
直接对快要销毁的值进行移动资源,但是对于Date这种类没有任何资源只需要浅
拷贝,那么就不需要显示写移动构造/移动赋值,因为对于Date的快要销毁的对象
移动和拷贝的行为是一致的,只需要将Date中的成员中的值拷贝过来就行,并没有
资源需要去进行拷贝
因为移动构造和移动赋值的第一个参数都是右值引用的类型,他的本质是要
“窃取转移”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,
从提高效率
例子:string实现移动构造和移动赋值的差别
在没有右值引用移动语义之前,我们都是实现一个const左值引用版本的构造和赋值
这这样改函数既可以接收左值参数,const左值参数,也可以接收右值参数,但是
无论左值还是右值它都会进行拷贝
没有移动构造和移动赋值前:


有移动构造和移动赋值之后


5.4右值引用和移动语义解决传值返回问题

结论:移动构造/赋值使得我们在函数传参做返回值时可以减少拷贝提升效率
5.5右值引用和移动语义在传参中的提效
查看STL文档我们发现C++11以后容器的push和insert系列的接口都增加的右值引用
版本当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器
空间中的对象当实参是⼀个右值,容器内部则调用移动构造,右值对象的资源到容器空
间的对象上
例子:将之前实现的list增添右值版本的push和insert

6.类型分类
6.1C++11以后,进⼀步对类型进行了划分,右值被划分纯右值和将亡值
6.2纯右值:C++11中的纯右值 == C++98中的右值
6.3将亡值:move之后的左值,以及上述中函数的返回值
6.4泛左值:将亡值和左值的统称
6.5左值:C++11左值 == C++98左值

7.引用折叠
7.1理解引用折叠的两个必要点:
7.1.1引用折叠是可能会发生的状况,那么面对这一种状况C++标准委员会需要设计
出一套解决方案
7.1.2C++标准委员会设计的解决方案的目的是为了引出接下来的万能引用
7.2C++中不可以直接定义引用的引用,例如:int& && x = move(i);
但是在typedef和模板中就可能会出现引用的引用,那么面对这种状况,C++标准
委员会提供的处理方案是引用折叠

7.3引用折叠的规则:
7.3.1左值引用碰右值引用结果是左值引用
7.3.2左值引用碰左值引用结果是左值引用
7.3.3右值引用碰左值引用结果是左值引用
7.3.4右值引用碰右值引用结果是右值引用
7.3.5总结:当两个引用发生引用折叠时,两个引用只有全部都是右值引用时结果
为右值引用,只要两个引用中有一个左值引用,结果就为左值引用
7.3.6通过下面代码可以发现:当函数模板的参数是左值引用时,结果必然是左值
引用,当函数模板的参数是右值引用时,结果可能是左值引用,也有可能是右值
引用

7.4万能引用
7.4.1万能引用要求:必须是函数模板使得类型是通过编译器自行推导
函数模板参数必须为右值引用&&
(因为当函数模板参数为右值引用时,结果可能是左值引用也可能是右值引用)
7.4.2使用万能引用时,函数参数传右值编译器推出来的就是原本类型,
函数参数传左值编译器推出来的就是左值&

7.4.3注意类模板的成员函数中的右值引用只是单纯的右值引用并不是万能引用
因为类模板是显示实例化,也就是在使用类模板时就将类显示实例化了,那么类
模板中的成员函数也显示实例化了,参数的类型是确定的,只可能发生引用折叠
不可能是万能引用,万能引用的第一个要求就是要求是函数模板,必须是通过
编译器去推导类型

7.4.4万能引用的本质是为了实现编写代码时更高一层维度的泛型
8.完美转发
8.1在上面实现list的push和insert时,需要实现一个左值版本还需要实现一个右值版本
代码很多余,但是两者之间只有类型的不同只有左值和右值的区别,那么此时我们就可
以通过万能引用将左值版本和右值版本统一起来,万能引用版本传左值就实例化出左值
的版本传右值就实例化出右值的版本,本质上是使得我们在编写代码时实现了更高一层
度的泛型
8.2那么将push和insert的左值引用版本和右值引用版本统一起来实现万能引用版本之后
那么此时push底层调用的insert,那么对于push函数的参数,无论最后是左值引用还是
右值引用属性都是左值,那么要将其参数传递给insert时,insert函数模板参数推导出来
的都是左值引用,因为push的参数就是左值属性,那么此时就需要在将push参数传递
给insert时可以分辨出此时push参数所引用的是一个左值还是一个右值,如果是左值就
保留其左值属性,如果是右值就保留其右值属性,然后再传递给insert,不能使用move
因为move只会将其转变为右值属性,并不能保持其原有的属性,此时需要完美转发
8.3完美转发的作用:如果此时是左值引用就保留其左值属性
如果此时是右值引用就保留其右值属性
8.4完美转发声明:

8.4.1模板参数T:需显示指定(无法自动推导),用于标记目标值的类别
(如果T为左值引用则转发为左值,非引用则转发为右值)
8.4.2左值调用第一个重载版本,右值调用第二个重载版本
8.5完美转发使用

三、可变参数模板
1.前提引入
C语言中有一个函数printf,该函数的参数是可变的,也就是可以传任意多个参数
如果此时完美使用C++去仿照这个函数的实现,那么需要使用函数模板,因为传参时
可能是任意类型的参数,并且此时我们需要实现0个参数的函数,1个参数的函数模板
两个参数的函数模板,三个参数的函数模板...N个参数的函数模板

仔细一想可以发现这是不可能实现的啊,怎么可能显示编写到无穷无尽呢?
所以C++就提供了一种语法可变参数模板
2.可变参数模板基础语法
2.1C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,
可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;
函数参数包:表示零或多个函数参数
2.2语法格式

2.3语法解释:
2.3.1用省略号来指出一个模板参数或函数参数的表示一个包
2.3.2在模板参数列表中,class...或 typename...指出接下来的参数
表示零或多个类型列表
2.3.3在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表
2.3.4函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,
每个参数实例化时遵循引用折叠规则
2.4可以使用sizeof...运算符去计算参数包中参数的个数
2.5此时Printf就可以这样实现

3.可变参数模板的原理
3.1可变参数模板的原理和普通模板的原理类似:
本质上都是去实例化出对应参数个数和类型的多个函数/类
3.2原理展开

3.3可变参数模板本质上是对模板的进一步泛型化(针对于模板参数个数)
4. 包扩展
4.1对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它
4.2包扩展起始就是解析包中的内容
4.3扩展方式:提供用于每个扩展元素的函数模板,扩展⼀个包就是将它分解为构成的
元素,也就是提供一个函数模板用来解析包中的内容
4.4可变参数模板出来模板参数包还可以有其他参数
理解:虽然可变参数模板可以有0 --- N个参数,但是这是根据所使用函数是使用的不同
方式所决定的,但是有一些情况,可变参数函数模板中有些参数是确定的,也就是确定
会有这几个参数,那么就可以显示的将这些可确定的参数显示出来,其余不确定的参数
使用可变参数性质进行推导
4.5例子:

4.6原理


往下传递参数包时,函数参数包后面需要+...
4.7包扩展方式2及其原理:

5.emplace接口
5.1C++11过后在容器中增添了emplace接口,emplace是一个可变参数万能引用模板
emplace接口的作用和insert相同都是在容器进行插入
5.2emplace系列接口支持传参时传构成容器中存储对象的参数,可以使用这些参数直接
去构造容器中存储的对象,仔细想一下,如果容器插入时传对象的左值,那么就需要先
构造一个左值,然后再将该左值拷贝构造给容器中存储的对象,如果让其插入时传对象
的右值,那么还是需要先构造一个右值对象,然后再将右值对象中的资源移动构造给
容器中存储的对象,那么我们发现无论是传左值进行插入还是传右值进行插入都必须
先构造一个对象,而emplace接口可以使得我们直接传构造容器中存储的对象的参数
那么就可以直接使用这些参数对容器中存储的对象直接进行构造,不仅仅需要先去构造
一个对象并且连拷贝/移动都省了这样就提升了效率 
5.3例子:此时我们自己实现string,在list中存储string

5.4 emplace系列总体而言是更高效,推荐以后使用emplace系列替代insert和push系列
5.5在传构造容器中存储的对象的参数时的场景下emplace系列才会提高效率,
传左值和右值的场景下,emplace和insert没有差距
5.6emplace系列提升的效率:不需要再先去定义一个对象并且不再需要拷贝/移动
5.7list模拟实现emplace 
四、新的类功能
1.默认移动构造和移动赋值
1.1原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/
拷贝赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个用处不大,
默认成员函数就是我们不写编译器会生成⼀个默认的。C++11 新增了两个默认成员
函数,移动构造函数和移动赋值运算符重载。
1.2如果没有实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载
中的任意一个(全部都没有实现)。那么编译器会自动生成⼀个默认移动构造。
默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型
成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就
调用拷贝构造。
1.3如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、
拷贝赋值重载中的任意一个,那么编译器会自动生成⼀个默认移动赋值。默认生成的
移动赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要
看这个成员是否实现移动赋值,如果实现了就调用移动赋值没有实现就调用拷贝赋值
(移动赋值和移动构造的逻辑一致)
1.4这里的逻辑就是析构函数和拷贝构造、拷贝赋值是绑定在一起的,如果显示写了
其中一个,那么就表明该类型需要申请资源需要释放资源需要深拷贝,那么对于这种
类型我们自己就应该显示去定义移动构造/移动赋值
1.5如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
2.成员变量声明时给缺省值
成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会
在初始化列表用这个缺省值初始化
3.defult和delete
3.1defult强制生成默认成员函数
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因
为一些原因这个函数没有默认生成,比如:我们提供了拷贝构造,就不会生成移动构造
那么我们可以使用default关键字显示指定移动构造生成
3.2delete强制删除默认成员函数
如果能想要限制某些默认函数的生成,只需在该函数声明加上=delete即可
该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数
五、STL中的一些变化
1.C++11新增容器

实际最有用的是unordered_map和unordered_set
2.STL中容器的新接口
最重要的就是右值引用和移动语义相关的
push/insert/emplace系列接口和移动构造和移动赋值,还有initializer_list版本的构造等
3.容器的范围for遍历
范围for的底层就是迭代器,编译器识别到范围for就会自动转换为对应的迭代器
六、lambda
1.lambda表达式语法
1.1lambda表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部
之所以称作匿名函数对象是因为lambda底层是创建一个仿函数类型,然后再实例化出一
个仿函数对象,然后通过该对象去调用lambda,匿名的原因是这都是编译器自动生成的
所以我们也不知道编译器生成的类型名叫什么也不知道实例化出的对象叫什么
1.2lambda表达式语法使用层而言没有类型,所以我们⼀般是用auto或者模板参数定义的
对象去接收lambda对象,如果有接收lambda表达式的对象那么lambda表达式就会直接
去创建这个接收lambda表达式的对象,如果没有接收lambda表达式的对象那么lambda
就会自动取创建一个临时对象(右值)
1.3lambda表达式生成的匿名函数对象默认是临时对象(右值),仅当用具名对象(auto/模板)
接收时才会转为具名对象(左值)
1.4lambda表达式语法格式:
[capture-list] (parameters)-> return type { function boby }
[capture-list]:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
判断接下来的代码是否为lambda函数,捕捉列表能够捕捉当前作用域及外层作用域中
“上文已声明”的变量供lambda函数使用,捕捉列表可以传值和传引用捕捉
(parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,
则可以连同()⼀起省略
->return type :返回值类型,没有返回值时此部分可省略,⼀般返回值类型明确情况
下,也可省略,由编译器对返回类型进行推导
{function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,
除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略

2.捕捉列表
2.1lambda表达式中默认只能用lambda函数体和参数中的变量,如果想用外层作用域中
的变量就需要进行捕捉,全局变量和静态成员变量不需要进行捕捉,可以直接使用
全局变量和静态成员成员之所以不需要进行捕捉是因为全局变量和静态成员变量在编译
期间就确定好了地址,而函数中的变量是存储在栈区的,只有当程序运行时才会分配
内存,那么就无法准确得知函数中变量的地址,而全局变量和静态成员变量的地址可以
确定那么就可以直接使用全局变量和静态成员变量
2.2第一种捕捉方式:显示捕捉
显示捕捉可以是传值捕捉和传引用捕捉
捕捉的多个变量用逗号进行分割,[x, y,&z]表示x和y值捕捉,z引用捕捉
传值捕捉的变量默认添加了const修饰不可以进行修改

此时是传值捕捉,注意此时lambda中的c和外层的c是两个c此时lambda中的c是外层c的
拷贝并且lambda传值捕捉默认是添加const的修饰的所以lambda中的c不可以进行修改

全局变量和静态成员变量不需要捕捉也可以进行使用

全局的lambda捕捉列表必须为空,因为全局变量和讲台成员变量不需要进行捕捉
没有需要捕捉的变量,所以全局的lambda的捕捉列表必须为空

此时为传引用捕捉,&c就表示外层的c以引用的方式捕捉,此时不会默认添加const
所以可以在lambda中修改c,此时在lambda中修改c就会影响外层的c,lambda中的c
就是外层c的引用/别名
2.3第二种捕捉方式,隐式捕捉
如果此时外层中有n个变量我们需要全部使用,那么一个一个变量进行捕捉太麻烦,此时
就可以使用隐式捕捉,隐式捕捉可以将外层中的所有变量一起捕捉过来
在捕捉列表写一个=表示隐式值捕捉,在捕捉列表写一个&表示式引用捕捉,这样lambda
表达式中用了那些变量,编译器就会自动捕捉那些变量
(注意隐式捕捉可以将外层中的所有变量一起捕捉过来,但是不是傻傻的全部捕捉,
而是我们在lambda中使用了哪个变量lambda就去捕捉哪个变量)

此时是隐式值捕捉,我们在lambda中使用了哪个变量编译器就会将哪些变量捕捉过来
隐式值捕捉和显示值捕捉的性质是一样的,lambda中的a,b,c都是外层a,b,c的拷贝
并且自动添加了const进行修饰不可以进行修改

此时是隐式引用捕捉,当我们在lambda中使用什么变量,编译器就会自动捕捉什么变量
隐式引用捕捉和显示引用捕捉的性质是一样的,lambda中的a,b,c是外层a,b,c的
引用,没有添加const,可以进行修改,并且修改了lambda中的abc外层的abc也会被影
响因为是引用
2.4混合捕捉
如果我们此时对于外层的大量变量需要进行捕捉,那么就需要使用隐式捕捉
但是隐式捕捉有一个问题,外层的全部变量都是统一的值捕捉或者是引用捕捉
如果此时大多数需要使用值捕捉一小部分需要使用引用捕捉,那么就可以使用混合捕捉
混合捕捉 == 隐式捕捉 + 显示捕捉
混合捕捉的要求:隐式捕捉必须在第一个,隐式捕捉的后面紧跟显示捕捉

此时是隐式值捕捉+显示引用捕捉,此时隐式值捕捉的变量的性质还是lambda中的ab
是外层ab的拷贝并且添加const修饰不可以进行修改,显示引用捕捉lambda中的c是
外层c的引用/别名,没有const修饰可以进行修改

此时是隐式引用捕捉+显示值捕捉,此时隐式引用捕捉的变量的性质lambda中的c
是外层c的引用没有添加const修饰可以进行修改,显示值捕捉lambda中的ab是
外层ab的引用/别名,有const修饰不可以进行修改
额外注意一点:混合捕捉中如果第一个是隐式的值捕捉,那么后面就不能再使用值捕捉
如果混合捕捉中第二个是隐式引用捕捉,那么后面就不能再使用引用捕捉
因为隐式捕捉的方式就可以将所使用的全部捕捉上,何必在后面再使用相同的方式进行
单独捕捉呢?
2.5默认情况下, lambda 捕捉列表是被const修饰的也就是说传值捕捉的过来的对象不
能修改, mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传
值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰后,
参数列表不可省略(即使参数为空)

3.lambda原理
3.1lambda的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有lambda和
范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数lambda底层就是仿
函数,当编译器识别到lambda时就会自动创建一个仿函数类,并且构建一个该仿函数类
的匿名对象,如果有auto/模板接收,那么就会直接使用这个仿函数类去构造该仿函数类
的具名对象
3.2仿函数的类名是编译按一定规则生成的,保证不同的lambda生成的类名不同
比如使用uuid
3.3lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体
3.4lambda的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是
lambda类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传哪些对象
七、包装器
1.function
1.1function作用:
包装一切可调用对象,统一可调用对象的类型
(可调用对象:函数指针,仿函数,lambda,bind)
function在<function>头文件当中
1.2为什么需要function?
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同,std::function的优势就是统
一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型
例如此时容器中需要存储返回值类型参数个数和类型相同的可调用对象,但是对于可调用
对象可能是函数指针可能是仿函数甚至可能是lambda我们都不知道他的类型,但是容器
中只能存储同一类型的对象,那么此时我们就需要统一这些可调用对象的类型,那么此时
就可以使用到function
1.3function的声明
function是一个类模板

1.4 function之所以可以包装一切可调用对象源自于它的构造函数
![]()
这是一个函数模板,可以自动推导出可调用对象的类型,然后形成对应的左值/右值引用
然后将该可调用对象的引用存储起来
1.5function本质上也是一个仿函数,它只是将可调用对象重新包装了一层,统一了可调
用对象的类型,使用function对象调用里面存储的仿函数对象时,是通过function的oper
ator()去调用被包装的可调用对象,在function的operator()中使用可调用对象(参数)进行
回调

function的operator()的参数是可变参数起始就是在显示实例化function时传递的可调用
对象的参数类型包,此时再通过参数类型包在operator()参数中实例化出对应的参数,我
们在使用function的operator()时传的参数其实最终传递给原本的可调用对象
如果function中没有可调用对象,但是还是去使用function去调用那么会抛异常
1.5function包装可调用对象
1.1包装普通函数,仿函数,lambda

1.2包装类的成员函数
1.2.1包装类的静态成员函数

1.2.2包装类的非静态成员函数

因为此时包装成员函数时,最重要的是除了this指针其余的参数类型,而对于this部
分类型参数只是为了可以使得function可以通过该类型的对象取调用该成员函数,此
时也会将this隐式传递过去,不然成员函数中所使用的成员变量从何而来如果此时封
装成员函数时第一个类型是this指针,那么function底层就会使用this->*成员函数指
针去调用,如果此时封装成员函数第一个类型是该类型的对象/引用,那么function
底层就会使用对象.*成员函数指针去调用
当拥有成员函数的指针,需要通过成员函数的指针去回调成员函数时如果是使用该类
型对象去回调就是对象.*成员函数指针如果是使用对象的指针去回调那么就是对象指
针->*成员函数指针进行回调
所以对于包装普通成员函数第一个参数的类型可以是类型*,也可以是该类型
1.3包装器的使用
使用map映射string和function的方式实现
在没有function时,我们无法统一可调用对象的类型,那么就无法在容器中存储可调用
对象,就发实现下面这种一个符号对于一个可调用对象,有了function我们就可以统一
可调用对象的类型就可以存储可调用对象

2.bind

2.1bind是一个函数模板,它也是一个可调用对象的包装器可以把他看做一个函数适配
器,对接收的fn可调用对象进行处理后返回一个可调用对象
2.2bind用来调整可调用对象的参数顺序和参数个数,bind也在<functional>头文件
当中
2.3调用bind的一般形式:auto newFunc = bind(func,arg_list); 其中newFunc本身是
一个可调用对象,arg_list是一个逗号分隔的参数列表,对应func的参数
bind返回的可调用对象本质上也是一个仿函数内部再调用func
当我们调用newFunc时,newFunc会调用func,并传给它arg_list中的参数

2.4arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,
表示newFunc的参数,它们占据了传递给newFunc的参数的位置
2.5数值n表示生成的可调用对象中参数的位置:_1为newFunc的第一个参数,_2为第
二个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的一个命名空间中

2.6调整可调用对象的顺序

牢记:bind的args_list的编写顺序与func的参数顺序一致,根据需求填写_n的位置
bind返回的可调用对象进行调用时,第一个参数传递给bind_1的位置,第二个参数
传递给bind_2的位置,此时这种方式就可以实现调整参数顺序
2.7调整可调用对象的参数个数(本质上就是提前将一些参数绑死)

