1.{}的初始化方式总结
(1)C++98支持的方式
cpp
//数组的初始化
int arr[] = {1,2,3,4,5};
//结构体且成员都为public的初始化(c语言的初始化方式)
struct A
{
int a;
int b;
};
A a = {1,2};
(2) C++11支持的初始化
C++11的{}有一个目的就是要将所有的类型都能用{}初始化
cpp
#include<iostream>
using namespace std;
#include<string>
#include<vector>
struct A
{
A(int _a = 10,int _b = 20,int _c = 30)
:a(_a)
,b(_b)
,c(_c)
{ }
int a;
int b;
int c;
};
int main()
{
//内置类型的初始化
int d = { 0 };
//自定义类型的隐式类型转换
string s = { "galgame" };
A a = { 1,2,3 };
//C++11还支持当该自定义类型支持单参数类型转换时,可以不写{}
A b = { 1 };
A c = 1;
//C++11还支持省略=(这里是调用构造函数的隐式类型转换)
A e{ 2,3,4 };
//这种不叫构造函数的隐式类型转换,这里是一下子插入很多数据
//本质时调用了形参为initializer_list的构造函数
//initializer_list和c语言的数组类似,只是多了两个分别指向头和尾的指针
//然后就将里面的值都插入到vector中了
vector<int> v({ 1,2,3,4,5 });
vector<int> vv = { 1,2,3,4,5 };
//运用上隐式类型转换和initailzer_list的一种情况
//里面那层为隐式类型转换的构造函数,外面那层为initailzer_list
vector<A> o = { {1,2,3},{4,5,6} };
return 0;
}
2.右值引用与移动语义
//左值引用
type& a;
//右值引用
type&& b;
1.定义
左值是一个数据自身如:*p= 10中的*p,a[0]中的a[0].
右值是常量或表达式产生的临时对象.例:x+y返回值时产生的临时对象。
2.不同
二者的最大区别在于左值可以取地址,但右值不可以。且左值可以出现在=两边,但右值只能出现在=右边。
(在打印非void*的类型的地址然后用cout打印时最好用(void*)强转后再打印,否则结果可能出现问题)
3.特点
左值引用就是给左值使用的引用,右值引用就是给右值使用的引用。
左值引用无法引用右值,但const左值引用可以引用右值。
右值引用无法引用左值,但左值在move(左值)后即可被引用。(move是以个函数模拟,功能类似于强转一个左值为右值)
右值引用只能引用右值,但右值引用的自身属性是左值。(一个意义就是能通过右值引用来修改其指向的右值(是修改右值本身,而非修改指向的对象))
左右引用都可以延长临时对象的声明周期。
左值引用/const左值引用/右值引用/const右值引用是可以构成函数重载的,编译器会优先匹配与实参类型最匹配的,没有再找其他的。
4.意义
左值引用的意义在于函数传参和传返回值时减少拷贝同时还可以对他们进行修改。
但是当返回值为函数的局部变量时,左值引用就会变成野引用。
3.移动构造函数和移动赋值
移动构造函数和移动赋值
两个函数的内部本质就只有一个swap全部成员变量的swap(本质都是从临时/匿名对象中掠夺资源).例:
//在string类内部
string(string&& s)
{
//此处s的数据要发生变化,也能发现右值引用为左值的意义
swap(s);
}
4.编译器的优化(一下情况都为返回值的情况)
1.只有拷贝构造的情况
1.无优化
返回值调用拷贝构造生成临时变量,临时变量调用拷贝构造给外部变量赋值。
2.优化一点
返回值直接调用拷贝构造给外部变量赋值.
3.完全优化
外部变量的引用就是返回值,也就是没有生成别的变量,相当与直接构造.
//二者的地址一样
string fun()
{
string show = "2121";
cout << &show << endl;
return show;
}
int main()
{
string b = fun();
cout << &b << endl;
return 0;
}
还发现了,如果返回值是内置类型,那么二者的地址不同,也就是内置类型编译器是认为不用优化的很厉害的。
2.既有拷贝构造也有移动构造
其实在有了移动构造后编译器就不会在调用拷贝构造,只会调用移动构造了。优化的方式与拷贝构造的方式完全一样。但是移动构造由于只涉及了变量的转移,因此效率极高。
3.为赋值的情况时
string ret;
ret = "djada";
此处的优化也与类和对象处的完全没有区别(就是有了移动赋值后就优先调用移动赋值),依旧是只能将拷贝构造全优化调,但赋值是不能优化的,但是使用移动赋值的效率很高。
左右图分别为没优化和右优化


5.总结应对左值引用无法传函数局部变量但值拷贝效率低的解决方式
1.通过输出型参数得到:
string ret;
func(//...,ret);//本来ret是应该在外部接收的,但为了效率只能从外面传进去然后修改它.
2.使用编译器的优化
从两次拷贝->一次拷贝->直接构造(就是类似于输出型参数的方式直接用外部接收数据的变量的引用来运行函数)
3.右值引用
因为有了右值引用才有的移动构造和移动赋值,这两个函数即使不优化的效率也很高。(本质就是将返回变量中的值swap几次直到传到外面的变量中)
(不同类型就是对一块空间的不同解释)
在有深拷贝的自定义类型中的移动构造和移动赋值更有含金量。
值得注意的是上述所有函数的调用都是调用的返回变量中的成员函数。