左值引用与右值引用的区别?右值引用的意义?
- 1 区别
- 1.1 功能差异
- 1.2 左值引用
- 1.3 右值引用
- 1.3.1 实现移动语义
- 1.3.2 实现完美转发
- 2 引用的作用
- 3 区分左值和右值
- 3.1 左值
- 3.2 右值
1 区别
左值引用是对左值的引用;右值引用是对右值的引用。
(1)使左值指向右值
const 左值引用能指向右值,局限是不能修改这个值。
注意:使用const不能修改值时,可以使用引用,引用该值并对其进行修改。
(2)使右值指向左值
右值引用通过**std::move(v)**可以将左值转化为右值,此时右值就是将亡值。
(3)声明出来的左值引用或右值引用都是左值
int i=0;
//引用是必须初始化
//此时的j引用的是右值'100'
int && j=100; //右值引用
int & k=i;//左值引用
1.1 功能差异
1.2 左值引用
避免对象拷贝。
- 函数传参
- 函数返回值
1.3 右值引用
1.3.1 实现移动语义
解决对象赋值的问题,避免资源(堆上)的重新分配。有深拷贝的情况下。
以数据库举例。
以前:与数据库创建连接,不想把连接对象关闭,需要再创建另一个连接,再把连接资源拿过来。
现在:直接把连接对象的资源拿给另一个对象是用,就不用再创建并删除一个与数据库之间的连接对象。
以文件举例。
以前:一个文件描述fd,按照以前的深拷贝需要重新打开一个文件。
现在:直接把该文件资源fd移动到另外一个对象中就可以了。
拷贝赋值构造与拷贝构造:
//拷贝赋值构造 深拷贝(资源的重新分配)
A a1,b1;
a1=b1;
//拷贝构造 深拷贝
A a2;
A b2(a2);//当前a2是左值
cout<<"b,p="<<b.p<<endl;//输出结果:b,p=A(const A&)...
A b2(std::move(a2));//改为右值
cout<<"b,p="<<b.p<<endl;//输出结果:b,p=A(A&&)...
深拷贝与浅拷贝:
//深拷贝
A(const A&a)//拷贝构造
{
//拷贝构造中使用的是const,只能读,不能写,所以不能再拷贝构造中使用移动构造进行资源转移
p=new int(10);
memcpy(p,a.p,10*sizeof(int));//重新分配内存
cout<<"A(const A&):p="<<p<<endl;
}
//浅拷贝
A(A&& a) //移动构造
{
//直接把a上的资源赋值给自己,并把a中的资源置空
this->p=a.p;
a.p=nullptr;
cout<<"A(A&&)"<<endl;
}
stl中的应用:
list<A> alist;
alist.push_back(A());//当前A()为右值
auto &ele=alist.front();//取出第一个值
cout<<"ele.p="<<ele.p<<endl;//输出结果:ele.p=A(A&&)...
1.3.2 实现完美转发
定义:函数模板可以将自己的参数完美地转发给内部调用的其他函数。
完美指的是不仅能准确地转发参数的值,还能保证转发的参数的左右值属性不变。使用std::forward(v)实现。
//左值
void func(int &n)
{
cout<<"left value="<<n<<endl;
}
//右值
void func(int &&n)
{
cout<<"right value="<<n<<endl;
}
//调用函数进行转发
//c11以前
template<template T>
void revoke(T &t)
{
//此时的t唯一个具体的值
func(t);//只能转发左值,调用的函数为func(int &n)
}
//c11以前,如果一定要转发右值
void revoke(const T &t)
{
func(t);//但是不能对t进行修改
}
/**************分割线***************/
//c11 完美转发
template<template T>
void revoke(T &&t)//T &&t为万能引用
{
func(std::forward<T>(t));
}
万能引用:具有模板参数或者模板推导的叫做万能引用。
1)具有模板参数:
template<typename T>
void tempFun(T&& t) {} //模板类型的这种用法 T && 是万能引用最常见的使用场合
2)具有模板推导:
auto&& var2 = var1; //auto这种需要推断类型的地方
万能引用虽然跟右值引用的形式一样,但右值引用需要是确定的类型,如: int && ref = x;就是右值引用。
借用万能引用的方式接受左右属性的值。使用引用折叠规则实现。
template<template T>
void revoke(T &&t)//T &&t为万能引用
{
func(std::forward<T>(t));
}
int main()
{
int i=10;
revoke(10);//右值 万能引用中会转换成int && t
revoke(i);//左值 万能引用中会转换成int & t
return 0;
}
引用折叠规则:
1)参数为左值或左值引用,T&&将转化为int &
2)参数为右值或右值引用,T&&将转化为int &&
注意:万能引用只接收值,void revoke(T &&t)中的t仍然是左值!
std::forward(v):
1)T为左值引用,v将转化为T类型的左值。
2)T为右值引用,v将转化为T类型的右值。
int main()
{
int i=10;
int &m=i;
int &&n=100;
//不使用类型强制转换,会直接到调用左值void func(int &n)
remoke(m);
remoke(n);
//使用类型强制转换
remoke(static_cast<int&>(m));
remoke(static_cast<int&&>(n));
return 0;
}
2 引用的作用
(1)别名,没有地址
(2)声明时必须要初始化
(3)通过引用修改变量值
3 区分左值和右值
3.1 左值
可以在等号左边,能够取地址,具名
举例:
1)变量名
2)返回左值引用的函数调用
3)前置自增/自减
int i=0;
++i=10;//++i为左值
cout<<i<<endl; //输出结果:10
4)赋值运算/复合赋值运算
int i=0;
//赋值运算
(i=9)=100;
cout<<i<<endl;//输出结果:100
//复合赋值运算
(i+=10)=1000;
cout<<i<<endl;//输出结果:1000
5)解引用
A *a=new A;
*a=...
6)…
3.2 右值
只能在等号右边,不能取地址,不具名
-
纯右值
举例:
1)字面值
2)返回非引用类型和函数调用
3)后置自增/自减
int a=0; i++=100;//error 纯右值不能作为左值 i++;//正确写法
4)算术表达式:+*/
5)逻辑表达式:^&|~
6)比较表达式:><=
7)…
-
将亡值:c++11新引入的与右值引用(移动语义)相关的值类型
定义:将亡值用来触发移动构造或移动赋值构造,并进行资源转移,之后将调用析构函数