21.C++11

1.列表初始化

1.1C++11中的{}

复制代码
•C++11以后想统⼀初始化⽅式,试图实现⼀切对象皆可⽤{}初始化,{}初始化也叫做列表初始化。
• 内置类型⽀持,⾃定义类型也⽀持,⾃定义类型本质是类型转换,中间会产⽣临时对象,最后优化
了以后变成直接构造。
• {}初始化的过程中,可以省略掉=
• C++11列表初始化的本意是想实现⼀个⼤统⼀的初始化⽅式,其次他在有些场景下带来的不少便
利,如容器push/inset多参数构造的对象时,{}初始化会很⽅便

############################################################################

调试时发现一个问题ovo

cpp 复制代码
初始状态:
vector 容量为 0。
​第一次 push_back:vector 分配容量 1,构造函数生成临时对象,
拷贝构造函数将临时对象拷贝到 vector 内存中;
​第二次 push_back:
构造函数生成新临时对象
vector 容量不足(当前容量 1,需要容量 2)。
分配新内存(容量变为 2)
​拷贝已有元素到新内存,触发拷贝构造
拷贝构造函数将新临时对象拷贝到新内存

1.2C++11中的std::initializer_list

cpp 复制代码
•上⾯的初始化已经很⽅便,但是对象容器初始化还是不太⽅便,⽐如⼀个vector对象,我想⽤N个
值去构造初始化,那么我们得实现很多个构造函数才能⽀持, vector<int> v1 =
{1,2,3};vector<int> v2 = {1,2,3,4,5};

• C++11库中提出了⼀个std::initializer_list的类, auto il = { 10, 20, 30 }; // the
type of il is an initializer_list ,这个类的本质是底层开⼀个数组,将数据拷⻉
过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。

• 这是他的文档https://legacy.cplusplus.com/reference/initializer_list/initializer_list/
  //std::initializer_list⽀持迭代器遍历。

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

2.右值引用和移动语义

C++11之后我们之前学习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤,都是给对象取别名。

复制代码
左值引用的意义:
1.函数传参和传值返回值过程减少拷贝,提高效率
2.函数传参和传值返回值用引用传值,修改被引用值

那要右值引用干啥?

2.1左值和右值

1.左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针),⼀般是有持久状态 ,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

2.右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的临时对象 等 ,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址

3.现代C++中,左值(lvalue) 被解释为loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,⽽ 右值(rvalue) 被解释为 read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字⾯量常量,存储于寄存器中的变量等,也就是说左值和右值的核⼼区别就是能否取地址。

2.2左值引用和右值引用

1.Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引⽤,左值引⽤就是给左值取别名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。

2.左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值

3.右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)

复制代码
template <class T> typename remove_reference<T>::type&& move (T&&
arg);

4.move是库⾥⾯的⼀个函数模板,本质内部是进⾏强制类型转换,他还涉及⼀些引⽤折叠的知识。

5.*需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值(重点)(重点)(重点)

6.语法层⾯看,左值引⽤和右值引⽤都是取别名,不开空间。从汇编底层的⻆度看下⾯代码中r1和rr1汇编层实现,底层都是⽤指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要然到⼀起去理解,互相佐证,这样反⽽是陷⼊迷途

2.3引用延长生命周期

右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生命周期,但这些对象无法被需改。

2.4左值和右值的参数匹配

1.C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配

2.C++11后,分别重载左值引用,const左值引用,右值引用作为形参的f函数,那么实参是左值会

匹配f(左值引⽤),实参是const左值会匹配f(const 左值引⽤),实参是右值会匹配f(右值引⽤)。

3.右值引⽤变量在⽤于表达式时属性是左值,这个设计这⾥会感觉很怪,注意体会这样设计的价值。

2.5右值引用和移动语义的使用场景

2.5.1左值引用和移动语义的使用场景回顾

左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉,同时还可以修改实参和修改返回对象的价值。左值引⽤已经解决⼤多数场景的拷⻉效率问题,但是有些场景不能使⽤传左值引⽤返回,如generate函数,C++98中的解决⽅案只能是被迫使⽤输出型参数解决那么C++11以后这⾥可以使⽤右值引⽤做返回值解决吗?显然是不可能的,因为这⾥的本质是返回对象是⼀个局部对象,函数结束这个对象就析构销毁了,右值引⽤返回也⽆法概念对象已经析构销毁的事实。

复制代码
// 这⾥的传值返回拷⻉代价就太⼤了
vector<vector<int>> generate(int numRows)

2.5.2移动构造和移动赋值

复制代码
1.编译器会按照最优原则,选择拷贝构造还是移动构造(他俩构成重载)

2.移动构造:将要死的资源转移走,而不再拷贝

1.移动构造函数是⼀种构造函数,类似拷⻉构造函数,移动构造函数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值。

  1. 移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。

  2. 对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,他的本质是要"窃取"引⽤的 右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率(理解:移动构造是要把即将销毁的资源直接 转移 到新的对象中)

2.5.3右值移动和移动语义解决传值返回问题

复制代码
*体会移动构造的意义:

1.编译器的优化会把移动构造给优化掉,三合一优化后只剩拷贝构造了

2.虽然编译器会进行优化,并且优化的很好,但是要将武器掌握在自己手中
我们可以主动自由控制右值传值返回的效率

下图中#1,#2均可以拷贝构造或移动构造,但#3就会把移动构造给优化掉。

2.5.4右值引用和移动语义在参数中的提效

当实参是⼀个左值时,容器内部继续调⽤拷⻉构造进⾏拷⻉,将对象拷⻉到容器空间中的对象

当实参是⼀个右值,容器内部则调⽤移动构造,右值对象的资源到容器空间的对象上

2.6类型分类(了解)

复制代码
1.右值被划分纯右值(pure value,简称prvalue)和将亡值
(expiring value,简称xvalue)。

2.纯右值是指那些字⾯值常量或求值结果相当于字⾯值或是⼀个不具名的临时对象。如: 42、
true、nullptr 或者类似 str.substr(1, 2)、str1 + str2 传值返回函数调⽤,或者整
形 a、b,a++,a+b 等。纯右值和将亡值C++11中提出的,C++11中的纯右值概念划分等价于
C++98中的右值。

3.将亡值是指返回右值引⽤的函数的调⽤表达式和转换为右值引⽤的转换函数的调⽤表达,如
move(x)、static_cast<X&&>(x)

2.7引用折叠

1.C++中不能直接定义引⽤的引⽤如 int& && r = i; ,这样写会直接报错,通过模板或 typedef中的类型操作可以构成引⽤的引⽤。

2.通过模板或 typedef 中的类型操作可以构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规 则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用

3.像f2这样的函数模板中,T&& x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。

2.8完美转发

1.Function(T&& t)函数模板程序中,传左值实例化以后是左值引⽤的Function函数,传右值实例化 以后是右值引⽤的Function函数

2.但变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性,就需要使⽤完美转发实现。

cpp 复制代码
template <class T> T&& forward (typename remove_reference<T>::type&
arg);
• template <class T> T&& forward (typename
remove_reference<T>::type&& arg);

3.完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现

3.可变参数模板

3.1基本语法及原理

1.C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函 数参数。

复制代码
•template <class ...Args> void Func(Args... args) {}
• template <class ...Args> void Func(Args&... args) {}
• template <class ...Args> void Func(Args&&... args) {}
  1. ⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则

3.可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。(可以理解为模板的模板)

复制代码
可以使⽤sizeof...运算符去计算参数包中参数的个数

3.2包扩展

对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个

包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元

素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。

C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。

复制代码
1.运行时递归-》自己调自己
2.编译时递归-》由一个函数推出另一个函数

3.3empalce系列接口

复制代码
• template <class... Args> void emplace_back (Args&&... args);
• template <class... Args> iterator emplace (const_iterator position,
Args&&... args);

1.C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container<T>,empalce还⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。

  1. emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列

3.们模拟实现了list的emplace和emplace_back接⼝,这⾥把参数包不段往下传递,最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前⾯说的empalce⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。

4.传递参数包过程中,如果是 Args&&... args 的参数包,要⽤完美转发参数包,⽅式如下 std::forward<Args>(args)... ,否则编译时包扩展后右值引⽤变量表达式就变成了左值。

4.新的类功能

4.1默认的移动构造和移动赋值

复制代码
• 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重
载/const 取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器
会⽣成⼀个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。

• 如果你没有⾃⼰实现移动构造函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意⼀
个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执
⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤
移动构造,没有实现就调⽤拷⻉构造。

• 如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数 、拷⻉构造、拷⻉赋值重载中的任意
⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动构造函数,对于内置类型成员会
执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调
⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)

• 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。

4.2成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表⽤的,如果没有显⽰在初始化列表初始化,就会在初始化列表⽤这个缺省值初始化。(在类和对象部分)

4.3 defult和delete

1.C++11可以让你更好的控制要使用的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤default关键字显⽰指定移动构造⽣成。
2. 如果能想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明补丁已, 这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语 法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。

4.4 final与override

在继承和多态那章

5.STL中一些变化

STL中容器的新接⼝也不少,最重要的就是右值引⽤和移动语义相关的push/insert/emplace系列
接⼝和移动构造和移动赋值,还有initializer_list版本的构造等。

6.lambda

6.1 lambda表达式语法

1.lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。 lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。

cpp 复制代码
lambda表达式的格式: [capture-list] (parameters)-> return type {
function boby }

2.[capture-list]捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使⽤,捕捉列表可以传值和传引⽤捕捉。捕捉列表为空也不能省略。

3.(parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略

4.->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。

5.{function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。

6.2 捕捉列表

1.lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想⽤外层作⽤域中的变量就
需要进⾏捕捉。
2.第 ⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x, y, &z] 表⽰x和y值捕捉,z引⽤捕捉。
3. 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表 写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些 变量。
4. 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=, &x]表⽰其他变量隐式值捕捉, x引⽤捕捉;[&, x, y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是 &或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必 须是引⽤捕捉。
5. lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态
局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使
⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
6. 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,
mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以
修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。

6.3 lambda的应用

1.在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的 类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对 象,既简单⼜⽅便。

2.lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定 制删除器等

6.4 lambda的原理

cpp 复制代码
•lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for
这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个
lambda 以后,编译器会⽣成⼀个对应的仿函数的类。

• 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返
回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成
的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕
捉,编译器要看使⽤哪些就传那些对象。

7.包装器

7.1 function

8.1 bind

cpp 复制代码
simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

1.bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。 bind 也在<functional>这个头⽂件中。

2.调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。

3.arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰ newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象 中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占 位符放到placeholders的⼀个命名空间中。

8.智能指针

分到下一篇文章

相关推荐
emplace_back4 小时前
C# 集合表达式和展开运算符 (..) 详解
开发语言·windows·c#
一禅(OneZen)8 小时前
「Windows/Mac OS」AIGC图片生成视频 ,webui + stable-diffusion环境部署教程
windows·stable diffusion
AirDroid_cn8 小时前
OPPO手机怎样被其他手机远程控制?两台OPPO手机如何相互远程控制?
android·windows·ios·智能手机·iphone·远程工作·远程控制
小龙在山东11 小时前
Python 包管理工具 uv
windows·python·uv
昏睡红猹11 小时前
我在厂里搞wine的日子
windows·wine
love530love14 小时前
Docker 稳定运行与存储优化全攻略(含可视化指南)
运维·人工智能·windows·docker·容器
1024小神19 小时前
tauri项目在windows上的c盘没有权限写入文件
c语言·开发语言·windows
程序视点1 天前
Window 10文件拷贝总是卡很久?快来试试这款小工具,榨干硬盘速度!
windows
wuk9981 天前
基于MATLAB编制的锂离子电池伪二维模型
linux·windows·github
lzb_kkk1 天前
【C++】C++四种类型转换操作符详解
开发语言·c++·windows·1024程序员节