什么是右值?什么是左值?
简单的来说左值就是能放在=左边的,可以被取地址的就是左值;能放在=右边的,不能取地址的就是右值,但这样来定义并不准确:
int main(){
int a=10;//a是左值,10是右值
int b=a;//a,b都是右值,可a放在=的左边
//说明:左值可以放在=的左边,也可以放在=的右边
const int c=30;
//c=a;error:c是const常量,不可以被修改
cout<<&c<,endl;//可是c可以取地址,因此c并不算左值
//b+1=20;error:b+1是一个临时变量,没有具体名称,不可以取地址,是一个右值
return 0;
}
准确的来说:
左值:就是可以被修改的对象,如:a,b
右值:通常是常量,表达式的返回值(临时对象),如100,a+b
更确切的来说:
- 普通类型的变量,有名字,可以取地址,都认为是左值。
- const修饰的变量,只能读不能修改,理论上来说应该当做右值对待,但是它可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为它是左值。
- 如果表达式的运行结果是一个临时变量或对象,认为是右值。
- 如果表达式运行结果或单个变量是是一个引用,则认为是左值。
C++中将右值分为两种:
- C语言中纯右值。比如:a+b, 100
- 将亡值。比如:表达式的中间结果、函数按照值的方式进行返回
左值引用与右值引用
- 普通类型引用只能引用左值,不能引用右值。
- const引用既可引用左值,也可引用右值.
int main(){
int a=10;
int &re1=a;//左值引用
int &re2=20;//error:10是右值
const int& re3=a;
const int& re4=10;
return 0;
}
C++11中的右值引用
- 只能引用右值,一般情况不能直接引用左值。
int main(){
//10是纯右值,本来只是一个符号,没有具体的空间,
//右值引用变量a在定义过程中,编译器产生了一个临时变量,a实际引用的是临时变量
int&& a=10;
a=20;
int b=30;
int&& re=b;//error:右值引用不能引用左值
return 0;
}
移动语义
- 资源通过浅拷贝方式从一个对象转移到另一个对象。通过此方式可以减少不必要的临时对象的创建、拷贝、销毁,从而提高程序性能。
class String{
public:
String(char* str=""){
if(nullptr==_str){
str="";
}
_str=new char[strlen(str)+1];
strcpy(_str,str);
}
String(const String& s)
:_str(new char[strlen(s._str)]+1)
{
strcpy(_str,s._str);
}
String& operator=(const String& s){
if(this!=&s){
char* temp=new char[strlen(s._str)+1];
strcpy(temp,s._str);
delete _str;
_str=temp;
}
return *this;
}
String operator+(const String& s){
char* temp=new char[strlen(_str)+strlen(s._str)+1];
strcpy(temp,_str);
strcpy(temp,s._str);
String strRet(temp);
return strRet;
}
~String(){
if(_str){
delete[] _str;
}
}
private:
char* _str;
};
int main(){
String s1("hello");
String s2("world");
String s3(s1+s2);
return 0;
}
上面的代码,看似没什么问题,但是在operator+ 中:
- strRet在按照返回值返回时,会创建一个临时对象
- 然后使用临时对象构造s3,s3构造好了以后临时对象就会销毁
- 这三部分有自己的独立空间,存储的内容是一样的,这对于空间是一种浪费,降低了程序的效率,临时对象的作用也并不大。
为了解决这个问题,C++11提出了移动语句的概念。实现移动语义,就必须使用右值引用。增加移动构造:
String(String&& s)//不能设置为const型参数,否则内容无法转移
:_str(s._str)
{
s._str=nullptr;
}
- strRet在创建好临时对象后就销毁了,即将亡值,C++认为它是右值;
- 在用strRet创建临时对象时,采用移动构造,也就是将strRet中的内容转移到临时对象中。
- 临时对象也是右值,因此在使用临时对象创建s3的时候,也采用移动构造,将临时对象中的内容移动到s3中。
- 整个过程只需要开辟一块堆内存即可,节省空间并提高了程序的运行效率。
注意: - 在C++11中,编译器会为类默认生成一个移动构造,这个移动构造为浅拷贝。当类涉及资源管理时,用户必须显示定义自己的移动构造。
std::move
- 有些场景下,可能也需要右值引用引用左值去实现移动语义。可以通过std::move函数将左值强制转化为右值。
int main(){
String s1("hello world");
String s2(move(s1));
String s3(s1);
}
以上代码是对move函数的误用:
- move函数将s1转换为右值后,再实现s2的拷贝构造就会使用移动构造,此时s1中的内容转移到s2中,s1就成了无效字符串。