?个人主页:秦jh_-CSDN博客
? 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482
目录
C++11简介
统一的列表初始化
{}初始化
std::initializer_list
声明
auto
decltype
nullptr
STL中一些变化
右值引用和移动语义
左值引用和右值引用
左值引用与右值引用比较
右值引用使用场景和意义
右值引用的属性编辑
右值引用引用左值及其一些更深入的使用场景分析
STL容器插入接口函数也增加了右值引用版本:
完美转发
模板中的&& 万能引用
std::forward 完美转发
前言
? hello! 各位铁子们大家好哇。
今日更新了C++11的相关内容
? 欢迎大家关注?点赞?收藏⭐️留言?
C++11简介
相比于 C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中 约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言, C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更 强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多。
统一的列表初始化
{}初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Point{ int _x; int _y;};int main(){ int array1[] = { 1, 2, 3, 4, 5 }; int array2[5] = { 0 }; Point p = { 1, 2 }; return 0;}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型,使用列表初始化时,可添加等号(=),也可不添加。
当没有写构造函数时,无法用{}初始化自定义类型。写出构造函数后就可以,这是C++11的语法特性。本质上是通过{}构造一个A对象,然后通过拷贝构造给aa1。但是编译器会优化,连续的构造和拷贝构造会被优化为直接构造。
实际上C++98就支持了单参数的构造函数的隐式类型转换,C++11支持了多参数的。
如果不想让隐式类型转换发生,可以在构造函数前加上explicit。
单参数构造时,也可使用{},并且{}可要可不要。 甚至连内置类型也可用列表初始化。
std::initializer_list
如果使用{}初始化,像vector这种容器,我们无法知道每次初始化的参数个数是几个, 所以C++11对STL中的不少容器就增加 std::initializer_list作为参数的构造函数。
initializer_list也支持迭代器遍历。
v5是构造,v3是由 initializer_list隐式类型转换成一个vector,然后再拷贝给v3得到的。
声明
auto
C++11中废弃auto原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
由上图可知,x不是i的引用。j和i的地址相同,但y和i的地址不同。如果想要y的地址与i相同,就得用auto&
decltype
typeid 可以拿到这个类型的字符串。
如果我们想用推出来的类型定义对象,不能用typeid。得用decltype。
nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示 整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
STL中一些变化
红色框中是C++11新增的容器,实际有用的是unordered_set和unordered_map。array就是静态的数组,只不过检查数组越界更严格,实际没什么用。forward_list就是一个单向链表,实际也没什么用。
右值引用和移动语义
左值引用和右值引用
无论左值引用还是右值引用,都是给对象取别名。
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)、匿名对象等等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。
左值引用与右值引用比较
左值引用总结:
左值引用只能引用左值,不能引用右值。但是const左值引用既可引用左值,也可引用右值。右值引用总结:
右值引用只能右值,不能引用左值。但是右值引用可以引用move以后的左值。右值引用使用场景和意义
下面的测试用VS2019,VS2022优化更大,测试结果会不同。
func1中左值引用可以减少拷贝。func2中,返回值是左值引用时也可以,但如果返回值是func2中的局部对象时,出了作用域就销毁了,所以无法用左值引用返回。
比如上方的to_string,str是局部对象,出了作用域就销毁了,就不能用左值引用返回。右值引用返回也是同理。
局部对象出作用域就会销毁,所以会拷贝构造一个临时对象,然后再拷贝构造给ret1。不过vs2022编译器会优化,连续的构造、拷贝构造会优化为直接构造。
如果把上面那句话拆成两句,编译器就无法优化。先构造ret1,然后构造str,接着拷贝构造临时对象,最后进行赋值拷贝。
右值还可以分为纯右值(内置类型)和将亡值(自定义类型)。
STL中增加了移动构造解决上述问题:
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
// 拷贝构造// 左值string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 移动构造// 右值(将亡值)string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}
没有移动构造时,传左值和右值都会调用拷贝构造。有了移动构造,如果传的是右值,会优先调用移动构造。
有了移动构造后
str是左值,要拷贝构造一个临时对象,有了移动构造,就调用移动构造给ret1。但是编译器还会继续优化,编译器将str强制识别为右值,使其调用移动构造,两次连续的移动构造又会被继续优化为一次移动构造。
因为s3不是已存在的对象,所以编译器会优化。所以把s1强转成右值时,就会调用移动构造,交换s3和s1的资源。
不仅仅有移动构造,还有移动赋值:
//移动赋值//s3 = 将亡值string& operator=(string&& s){cout << "string(string&& s) -- 移动赋值" << endl;swap(s);return *this;}
因为如果是用一个已经存在的对象接收,编译器就没办法优化了。 所以调用了移动构造和移动赋值。
右值引用的属性
右值引用的属性是左值。如上图,右值引用s被当作swap函数的左值引用的形参。
右值引用引用左值及其一些更深入的使用场景分析
当需要用右值引用引用一个左值时,可以通过move 函数将左值转化为右值。C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性, 它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用。
这里我们把s1 move处理以后, 会被当成右值,调用移动构造
STL容器插入接口函数也增加了右值引用版本:
push_back里面要new一个节点,所以会有拷贝构造。因为s1是左值,所以会调用拷贝构造。第二个push_back里是右值,就可以调用移动构造。
int类型,日期类等浅拷贝类型,没有移动系列函数。因为他们不需要拷贝构造和移动构造。浅拷贝的类不存在转移资源的说法。
完美转发
模板中的&& 万能引用
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。因为右值引用的属性是左值,所以上面Fun(t)里的t都是左值,因此会产生这个结果。如果希望能够在传递过程中保持它的左值或者右值的属性,需要用到完美转发。
std::forward 完美转发
完美转发保持了t的原生属性类型,即如果传的是左值,就不变。传的是右值,就推成右值引用。