目录
概念:
理解:
左值引用,右值引用
左值引用能否给右值取别名?
右值引用能否给左值取别名?
引用的意义是什么?
左值和右值对自定义类型有什么区别吗?
move的妙用!
没有优化的左值传输:
临时变量是必然产生的!
右值引用本身是左值!
主要原因:
右值引用的底层 和左值引用的底层:
右值能否被改变?
概念:
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋 值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。
定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。
左值引用就是给左值的引用,给左值取别名。
int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
return 0;
}
理解:
在以往较为浅薄的理解中,左值是可以进行修改的,右值是无法进行修改的
例如,图中的a是变量可以进行修改是左值,但10是常数不能进行修改是右值,但不完全是这样的结果:又如下图所示,a在第一行代码中是左值,在第二行代码中又充当了右值,所以左值和右值不能如此轻易的划分。
又如:加上了const的c是不允许被修改的,所以c是左值还是右值?答案是:c是左值!
因此,最后我们可以总结出一个结论, 可以取地址的是左值,不能取地址的是右值。
同时我们也需要注意,左值代表的不一定是一个数值,也可也是一个表达式。
例如:
*p
*p 是一个左值
vector<int> v(10,1);
v[1];
v[1] 是一个左值
凡是能取到地址的都算是左值!
而右值就是取不到地址的,同样右值也可以是一个表达式,且同时,临时变量、匿名对象、临时对象统统都是右值!
都是右值
左值引用,右值引用
引用就是取别名!左值引用就是给左值取别名,右值引用取别名!
int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
左值引用能否给右值取别名?
答案是不可以!但是加上const可以!
右值引用能否给左值取别名?
答案也是不可以!但是需要把左值加上move进行使用后才行!
所以左右值引用 可以对左右值进行取别名,但是要有条件!
引用的意义是什么?
- 减少拷贝!提高效率!传值、传参、传引用返回 都可以使用引用,这是左值引用的常见场景!
- 但是左值引用没有彻底解决返回值的问题,右值引用就是修补这一块内容。
- 因为如果是一个临时对象,或者局部对象,则不能使用引用返回!因为出了作用域地址就会被销毁,所以没有用!而这种问题也不能直接使用右值引用进行返回,右值引用返回不是这样用的!
例如:
在主函数调用的时候,会在返回的时候进行临时变量的构造然后再加上拷贝构造把数值从临时变量中进行拷贝导赋值变量中,然后C++11后面优化成了一个直接获取!
这里无论是加左值引用还是右值引用都没有效果且会报错,因为str是局部变量被销毁了!地址不存在了!因为出作用域了!
为了解决这种问题,增加一个右值引用的构造函数,而被右值引用修饰的构造叫做移动构造!
有了移动构造,编译器会进行选择,如果返回的时候是左值,那么编译器就会去选择拷贝构造,如果是右值,那么就会 选择移动构造
左值和右值对自定义类型有什么区别吗?
自定义类型的右值基本都是匿名对象、传值返回的临时对象。
然后右值又有细分,分为纯右值和将亡值,基本上内置类型的右值都是纯右值,而自定义类型的右值基本都是将亡值。
对于局部变量来说,它的左值只能是老实的开辟空间然后被利用完被拷贝完后销毁空间,
而对于局部变量来说,它的右值(将亡值),它会利用空间! 将拷贝数值的空间和临时变量的空间进行交换!
有了移动构造后,在传值时,会强行把左值变成右值,强行使用了move (可以不在返回时加上move 编译器会做处理!)/可以看出str虽然是左值,但是和将亡值没什么区别
然后会调用两次移动构造,但是编译器直接转移资源,一步到位!以前需要进行拷贝,但是右值的移动构造,将被赋值的数值强行和右值进行交换!
移动构造为什么叫移动构造,是因为它移动将亡值对象的资源!!!!把将亡值的资源转移到被赋值调用的数据上!
严格来说,移动构造是延长了将亡值 所占据的资源数据的生命周期,而不是延长了将亡值所处在空间地址的生命周期。因为是转移资源,所以移动构造的代价是极低的!
move的妙用!
可以看出s1的数据被移动到了s3 这就是move的功能!
没有优化的左值传输:
没有优化的传输其实就是仙拷贝构造在赋值拷贝,就如下图中的写法,导致编译器无法进行优化!
优化:进行优化的左值传输!一次构造和一次拷贝构造,最后被编译器优化成了一次拷贝构造!
临时变量是必然产生的!
临时对象是必然产生的,这个临时对象是在寄存器的,但是寄存器非常的小,所以当对象数据过大时,会把临时对象放到两个数据的栈帧中:如下图所示的绿色框框就是处在main和to_string两个栈帧之间!
这种编译器无法进行优化的,可以使用move进行转化成移动构造函数,将其变成一次移动构造,一次移动赋值!
str在传输时会给一个临时对象,然后把临时对象的数据(空)的数据和 传输的数据进行数据之间的交换,然后在临时赋值的时候,因为在赋值之前进行初始化,所以会把初始化的那个空间和临时对象内部的数据再一次进行交换,然后完成移动赋值!
右值引用本身是左值!
右值和右值引用,求s1是右值还是左值?左值!已知std::string(“11111111”)是右值
但是右值引用是左值!有地址!右值引用本身是左值!
主要原因:
右值有一个特点,本身是不能改变的,因为如果使用了别名,那如果别名也不能改变,那如何做资源转移?
可以看出右值的资源转移主要是因为swap如转移的过程中数值不能进行改变,也就是右值引用的数据不能改变,那么将无法进行资源的转移!所以也就可以得出,右值引用是左值!
右值引用的底层 和左值引用的底层:
右值引用和左值引用的底层都是指针,都是取匿名空间的地址!所以右值真的没有地址吗?那右值存哪里?总要存储吧!所以右值是有地址的,只是不能拿取这个地址罢了!取不到这个地址罢了!
右值能否被改变?
s5是可以左值引用s1的!但是s6是不能直接左值引用的!但是可以使用强制转化:
可以资源转移到s7!所以右值能否改变呢!可以的!如果不用强转呢?
先给一个右值引用,然后在左值引用,本质就是右值引用底层是指针,且底层还是有空间存储的!
和上面的s5和s1的操作一样!类似于交换!