目录
统一的列表初始化
{}初始化
decltype
编辑 nullptr
STL中一些变化
右值引用和移动语义
左值引用和右值引用
总结
左值引用优缺点
右值引用(将亡值)
拷贝赋值和移动赋值
万能引用|完美转发
移动构造和移动赋值注意事项
delete
点击跳转了解C++11
统一的列表初始化
{}初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=
的参数,这样就可以用大括号赋值
把大括号里面的东西给对象,对象就会被认为是initializer_list类型
点击跳转了解
所有的容器都支持了这样的构造函数,所以我们可以用列表初始化。
调用支持list(initializer_list<value_type>il)类似这样的构造函数
我们自己写的vectoru不支持这种写法 ,因为我们没initializer_list,我们在之前写的vector中这样写,就可以实现C++11这种写法
有了上面的initializer_list,我们还可以这样写
v4是隐式类型转换,而且调用了构造函数
map也可以这样写,首先是map支持 initializer_list的构造,而每个数据都是pair,pair支持初始化,调用了pair的构造。
C++11以后一切对象都可以用列表初始化,但是普通对象最好用以前的方式初始化,容器之类的可以用列表初始化
decltype
关键字decltype将变量的类型声明为表达式指定的类型。
y1的类型是由x类型决定,y2的类型由等号右边的值决定,auto和decltype有差异,俩者不一样。
nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
STL中一些变化
新容器
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和
unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。
容器中的一些新方法
都支持initializer_list构造,用来支持列表初始化。
如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得
比较少的。
比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是
可以返回const迭代器的,这些都是属于锦上添花的操作。
array对越界很敏感。实际情况arrar用的很少,因为C语言使用关了。
右值引用和移动语义
左值引用和右值引用
什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左
值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引
用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址。右值引用就是对右值的引用,给右值取别名。
右值不能取地址
左值引用不能去引用右值,加上const就可以
所以在函数传参的时候我们如果使用了引用传参,就加const,这样该函数既能接收左值,又能接收右值,若不加const只能加收左值引用,不能引用右值
右值引用不能直接引用左值,对左值加上move函数,右值引用就可以引用左值了
此时不报错
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地
址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
这里rr1和rr2都是左值,因为他们可以进行取地址
总结
左值引用总结:
我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。
1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值
右值引用总结:
右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址。
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值。
左值引用优缺点
左值引用解决哪些问题:
1.做参数
a.减少拷贝,提高效率 b.做输出型参数
2.做返回值
a.减少拷贝,提高效率 b.引用返回,可以修改返回对象(比如:operator[]).
缺点:
以to_string为例 ,它是传值返回,简单实现to_string,返回的str是局部对象,出了作用域就会销毁,不能用左值引用返回,如果用左值引用返回此时程序就崩了,因为str已经析构了。
还有这道杨辉三角的返回值,返回的是vector<vector<int>>,C++98左值引用面对这种场景很难处理
我们可以这样修改,这种优化的缺点不太符合使用习惯
右值引用(将亡值)
C++11的右值引用就能解决上面的问题
这里有俩次string的拷贝构造,编译器优化后只有一次拷贝构造,这里出了作用域str就销毁,所以不能用左值引用返回,我们可以用右值返回
namespace myspace
{
myspace::string to_string(int value)
{
myspace::string str;
//
return str;
}
}
int main()
{
myspace::string ret = to_string(-3456);
return 0;
}
右值引用的提出,产生了移动构造
拷贝构造是左值引用,移动构造是右值引用,右值引用也可以走拷贝构造因为被const所修饰,C++每次在选择函数时,会选择最匹配的函数进行使用,既有左值引用又有右值引用,当使用右值引用时,就会走右值引用
右值:1.内置类型右值——纯右值(如x+y) 2.自定义类型右值——将亡值(声明周期一般只在那一行或函数结束)
这里s是将亡值,用swap保留s的资源。所以叫移动构造
str1的资源被转移,move(str1)这里吧str1转成了右值,str1就是将亡值,str1的资源就被转移了
这里之前是一次拷贝构造,现在是一次移动构造,选择最匹配的函数,拷贝构造的代价要大于移动构造的代价,因为拷贝了临时对象之后,还要释放临时对象,移动构造是直接转移资源,不需要拷贝。
注释掉移动构造变成了拷贝构造
左边是拷贝构造(此时没有写移动构造),右边是写了移动构造之后,编译器匹配到了移动构造
拷贝赋值和移动赋值
屏蔽掉移动构造之后运行程序。
取消移动构造的注释,加上移动赋值,把s和将亡值进行交换,将亡值在被销毁的时候,会带着之前s的值一并被销毁
右值引用增加了移动构造和移动赋值之后,提高了效率,减少了资源的拷贝和释放,直接把资源进行交换。
右值引用在延长对象的生命周期,这种说法是错的,因为他转移了资源,并没有延长对象的生命周期
这里s1+s2是一个表达式,表达式的值是一个临时值,临时对象是右值(将亡值)
C++11以后提供了移动构造和移动赋值
STL中所有的插入接口,都有右值引用,解决了用传值返回类型对象的拷贝问题
这里传匿名对象(匿名对象周期只在那一行),传的是右值(将亡值),这里转移俩次是跟内部实现有关系,这个不需要关注,在这里只需搞清楚移动构造即可
万能引用|完美转发
如果普通参数是这样则成为右值版本,但是在模板里这样使用就会被称作万能引用,引用折叠。
折叠的规则很复杂,简单来说就是右值,左值都能传过去,即T既能引用左值,又能引用右值
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
引用折叠以后,统统变成了左值
完美转发: std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
完美转发引用场景
应用在以前写的链表的push_back
main函数
理论而言第一个push_back走左值版本,第二个走右值版本,但是这里都是拷贝构造
这是因为万能引用进行了折叠,这是语法特性,我们进行完美转发
但结果还是不对,这是因为我们给节点没有提供右值引用版本
给节点提供右值引用版本
但结果还是拷贝构造
给insert进行完美转发
给insert相关的都进行完美转发
此时结果正常
移动构造和移动赋值注意事项
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个(如果实现了这里面的任意一种,编译器会调用拷贝构造)。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个(如果实现了这里面的任意一种,编译器会调用拷贝赋值),那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
当存在析构或拷贝构造或构造时,我们可以强制生成移动构造和移动赋值
强制生成默认函数的关键字default:
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用default关键字显示指定移动构造生成。
delete
禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁
已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即
可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
这里以不想让Person对象拷贝为例
继承和多态中的final与override关键字前面博客已经说过
将析构用delete修饰,在哪个区域都无法创建对象 ,因为创建的对象最后要调用析构
如果要创建对象只有一种方法, 使用new,new出来空间的被指针接收,指针不会调用析构函数
如果在构造的时候new一块空间,这回导致资源泄露,因为析构被禁了
此时手动delete会出错,因为要调析构,析构此时调不了
我们可以手动写一个释放空间的函数,调用这个函数即可
这里的空间有俩层,destory只释放了_str所指向的空间,而ptr指向的空间没有被释放
可用这种方法解决