左值与右值
概念
左值与右值是C语言中的概念,但是C标准并没有给出严格的区分方式,一般认为:可以放在 = 左边的(一般是可以修改的值) 或者 能取地址的称为左值, 只能放在 = 右边(一般是不可修改的一些值),或者不能取地址的值称为右值。(如:常量、临时变量、表达式的返回值)
具体解释:
- 普通类型的变量,因为有名字,可以取地址,都认为是左值。
- const修饰的常量,不可修改,只读类型的,理论上应该按照右值对待,但因为其是可以取地址的,C++1认为其是左值。
- 如果表达式的运行结果是一个临时变量或对象,认为是右值。
- 如果表达式运行结果或单个变量是一个引用则认为是左值。
C++11对右值进行了严格的区分:
- C语言中的纯右值,比如:a+b,100;
- 将忘值。比如:表达式的中间结果,函数按照值返回的方式进行返回。
左值引用与右值引用
- 他俩都是给对象取别名;
- 左值引用一般是给左值取别名,右值引用一般给右值取别名;
左值引用(&) 右值引用(&&)
那么有没有将左值处理为右值的情况呢??当然是有的!!
move告诉编译器我们有一个左值,但我们希望像一个右值一样处理它。注意:调用move意味着承诺:我们要放弃对move对象的资源管理。
为什么要提出右值引用呢??它的价值何在??
值的形式返回对象的缺陷
如果一个类中涉及到资源管理,用户必须显式提供拷贝构造、赋值运算符重载以及析构函数,否则编译器将会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错,比如:
class String
{
public:
String(char* str = "")
{
//cout << "String()" << endl;
if (str == nullptr)
{
str = "";
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
// 拷贝构造----深拷贝
String(const String& s)
:_str(new char[strlen(s.c_str())+1])
{
cout << "String(String&)" << endl;
strcpy(_str, s._str);
}
// 移动构造-----移动拷贝
/*String(String&& s)
:_str(nullptr)
{
cout << "String (String&& s)" << endl;
std::swap(_str, s._str);
}*/
//拷贝赋值----深拷贝
String& operator=(const String& s)
{
cout << "String& operator=(const String& s)" << endl;
if (this != &s)
{
char* tmp = new char[strlen(s.c_str()) + 1];
strcpy(tmp, s.c_str());
delete[] _str;
_str = tmp;
}
return *this;
}
// 移动赋值----移动拷贝
/*String& operator=(String&& s)
{
cout << "String& operator=(String&& s)" << endl;
std::swap(_str, s._str);
return *this;
}*/
String& operator+=(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
std::swap(_str, pTemp);
return *this;
}
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
const char* c_str()const
{
return _str;
}
~String()
{
if (_str)
delete[] _str;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
String s3 = s1 + s2; // s1+s2,是传值返回,当没有
String s4 = s1 += s2;
//cout << endl;
String s5;
s5 = s1 + s2;
cout << endl;
String s6;
s6= s1 += s4;
system("pause");
return 0;
}
当然经过编译器优化之后,看起来效率并没有降低,但是接下来这种情况呢??
那么能否对这种情况进行优化呢????
移动语义
C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。
要实现移动语义就必须使用右值引用。
那上面的例子来说:
注意:
- 移动构造的函数参数千万不能设置程const类型的右值引用,因为资源无法转移而导致移动语义失效。
- 再C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显示定义自己的移动构造。