左值引用和右值引用的区别?右值引用的意义?
区别:
按照名字:左值引用就是对左值的引用,右值就是对右值的引用
按照功能:
左值引用是为了防止对象的拷贝,
函数传参
函数返回值(拷贝函数)
右值引用(C++11):
- 实现移动语义
- 实现完美转发
左右值
可以在等号的左边可以取地址为左值,只能在等号右边,不能取地址为右值
左值:
左值是一个数据的表达式(如变量名或引用的指针),我们可以获取到它的地址,正常情况下是可以能够对它赋值,定义const修饰后的左值,不能给它赋值,但是可以取出它的地址。
常见的左值类型如下:
- 变量名
- 返回左值的函数调用
- 前置自增/自减
- 赋值运算或者符合赋值运算
- 解引用
int a = 1 ;//a为左值,a具有地址&a
int i = 0;
++i = 10;//a为左值,因为i具有地址
A *a = new a; //*a,解引用依然是左值
右值:
右值也是一个数据表达式,右值是字面常量或者是求值过程种创建的临时对象,右值的生命周期是短暂的,右值不能出现在赋值符号的左边,右值也不能取出地址,更不能对它赋值。
右值的引用语法:
int&& rra = x +y; //正确
rra = 20; //正确,普通的右值可以被更改
int&& rrb = x; //错误,右值应用不能引用左值
const int&& rrc = 10;
rrc = 20 //错误,const引用不能更改
int&& rrd = rra; //错误,右值的引用不能被别的右值引用
标准库中的move
函数可以将一个左值强制转换为右值
纯右值
- 字面值
- 返回非引用类型的函数调用
- 后置自增自减
- 算数表达式
- 逻辑表达式
- 比较表达式
纯右值的概念等同于我们之前所理解的右值,指的是临时变量或字面量值;而将亡值是C++11新引入的概念,它依托于右值。
- 将亡值
i++的原理:可以看出因为j是临时对象所以是纯右值。
int j = 1
i = i+1
return i
将亡值
与右值引用的(移动语义)相关的值的类型。在C++中,使用左值去初始化对象或者为对象赋值时候,会调用拷贝构造函数或者赋值构造函数。而使用右值去初始化或者赋值时,会调用移动构造函数 或者移动赋值函数 来移动资源,从而避免拷贝,提高效率。将亡值可以理解为通过移动构造其他变量内存空间的方式获得的值。在确保其他变量不再被使用、或者即将被销毁时,来延长变量值的生命期。
- 返回右值引用的函数的调用表达式。
- 转换为右值引用的转换函数的调用表达式。
看代码:
1、先定义Time类,在类中定义了拷贝构造函数和移动构造函数;
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
class Time
{
int *hour;
int *minute;
int *second;
//构造函数
Time(int h, int m, int s)
{
hour = new int(h);
minute = new int(m);
second = new int(s);
}
//拷贝构造函数
Time(Time &t)
{
cout << "拷贝构造函数" << endl;
hour = new int(*t.hour);
minute = new int(*t.minute);
second = new int(*t.second);
}
//移动构造函数
Time(Time &&t) noexcept :hour(t.hour), minute(t.minute), second(t.second)
{
t.hour = nullptr;
t.minute = nullptr;
t.second = nullptr;
std::cout << "move contruct." << std::endl;
}
~Time()
{
std::cout << "call ~Time()" << std::endl;
delete hour;
delete minute;
delete second;
}
};
int main()
{
Time test(10,25,12);
Time test2(test);
return 0;
}
执行结果:
copy contruct.
call ~Time()
call ~Time()
通过这样的方式来减少不必要的内存操作。但是之后我们也无法再访问 test 对象的内容了,因为都在移动构造函数之中置为了空指针。将亡值通过移动构造函数“借尸还魂”,通过 test2 变量延续了自己的生命周期。
https://blog.csdn.net/itworld123/article/details/115470227
移动拷贝构造函数
有必要在这解释一下 移动拷贝构造函数
左值引用的缺陷:如果一个函数的返回值出了该函数作用域后就会被销毁掉,这个返回值的生命周期就结束了,因此就不能使用传引用返回,所以只能通过传值返回,而传值返回至少需要进行一次拷贝构造(如果编译器比较旧,就需要进行两次的拷贝构造),在旧的编译器下,如果一个函数是返回值给另一个函数并创建出新对象的时候,中间过程中需要先拷贝构造出一个临时对象,然后再通过这个临时对象在另一个函数内拷贝构造出一个新对象,然后这个临时对象就会被销毁掉。但是有的编译器为了提高拷贝效率,就将调用两次拷贝构造函数优化只调用一次拷贝构造函数
原文链接:https://blog.csdn.net/sjp11/article/details/122830135
移动拷贝构造函数跟构造函数一样,参数需要是一个本身类型的对象,但移动拷贝构造函数的参数是一个该类型的右值引用。移动拷贝函数创建出一个新对象,都将新对象中的值都设置为0,接下来与传进来的右值对象进行资源交换。
//移动构造
string(string&& s):_str(nullptr),_size(0),_capacity(0)
{
cout << '看看构造'<<endl;
swap(s);
}
int main()
{
sjp::string s1('詹姆斯是条狗');
sjp::string s2(s1);
sjp::string s3(std::move(s1));
}
使用移动拷贝构造函数后,源对象指向资源就被交换出去,这些资源的所有权都归属到了新对象,因此,如果源对象是一个长期存在的对象的时候,需要谨慎使用移动拷贝构造函数。调用移动拷贝构造函数创建出s3,s1的资源被转移到了s3,s1中没有指向任何资源,所以就不能通过s1去寻找之前的资源。
当函数的返回值进行返回的时候,它会调用拷贝出一个 临时对象,此时返回值就会去拷贝出一个临时对象,由于返回值是一个将亡值,属于一个右值,因此就会调用移动拷贝构造函数拷贝出临时对象,然后临时对象去拷贝出新对象,由于临时对象也属于一个右值,所以回去调用移动拷贝构造函数去拷贝出新对象。由于两个过程都是调用移动拷贝构造函数,则编译器会直接优化成只调用一次移动拷贝构造函数。
如下,str对象中包含一个指针指向堆上一个字符串,当我们调用移动拷贝构造函数创建出s的时候,可以将str对象中的资源和s的对象资源进行交换,所以指向字符串的指针就给了s,因此s对象就可以通过指针使用该字符串,这就不需要去堆上再创建一块新的字符串给s对象。