左值与右值
在C语言中,左值和右值一般有两种区分的方法。可以出现在赋值符号“=”的两边的值为左值,只能出现在赋值符号“=”的右边的值为右值;还有一种说法是能取地址的为左值,不能取地址的为右值。但是这两种说法并非完全正确
void test()
{
int a = 10;
int b = a;//ok a为左值
10 = a; //error 10为右值
int* pa = &a;//ok
int* pi = &10;//error
}
而在C++中,右值有3种变量,分别为常量、临时变量/匿名变量、将亡值;其他的变量都为左值
将亡值就是声明即将结束的变量
int fun(int a)
{
return a;//将亡值
}
C++引入右值的原因
- 实现移动语义(移动构造和移动赋值)
- 给中间临时变量取名字
- 实现完美转发
左值引用和右值引用
左值引用就是我们平时用的引用,左值引用就是在类型后面加&
,则定义该变量为引用类型。左值引用及可以引用左值,也可以引用右值
void test()
{
int a = 10;
int& ra = a;//引用左值
const int& ri = 10;//引用右值
}
右值引用则需要在变量类型加上&&
,则为右值引用。右值引用不能引用左值,只能引用右值
void test()
{
int a = 10;
int&& rri = 10;//ok
int&& rra = a;//error;
}
右值引用的作用
在我们平时写的代码中,常常为有创建拷贝以及释放空间的开销,而C++是一门追求极致性能的语言,所以会尽可能在保持原有特性上增强性能。而引入右值引用,目的就是为了在某些特定场景下提高代码运行的效率—通过不进行深拷贝来提高代码拷贝效率
我们先来看看自己实现的string类
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class String {
public:
String(const 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);
cout << "String(const String&)" << endl;
}
String& operator=(const String& s) {
if (this != &s) {
char* pTemp = new char[strlen(s._str) + 1];
strcpy(pTemp, s._str);
delete[] _str; _str = pTemp;
cout << "String& operator=(const String&)" << endl;
} 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;
}
~String()
{
if (_str) delete[] _str;
}
private: char* _str;
};
void test() {
String s1("hello");
String s2("world");
String s3(s1 + s2);
}
在测试代码中,要创建s3会经历很多次的申请空间,第一是先要先将s1和s2进行+操作,在operator+函数中,最后要将空间赋值给strRet是时会进行拷贝,然后在返回strRet时也会进行一次拷贝,然后将拷贝的临时变量用来创建s3时又要调用拷贝构造。而拷贝构造是深拷贝,每次调用都需要开辟空间和花费时间
但是在vs中编译器会进行优化。将返回值那一步拷贝过程去掉
但是在C++11中,引入了 右值引用,还可以有优化的空间。也就是s3也不需要开辟新的空间,直接利用strRet的原有资源。将strRet中的资源移动到s3中
String(String&& s)
:_str(s._str)//直接指向strRet的资源
{
s._str = nullptr;//置其为空,防止二次释放
cout << "String(String&&)" << endl;
}
在右值引用的拷贝构造中并没有申请新的空间。这两个拷贝构造可以共存,大部分情况下是会调用左值引用的拷贝构造,只有以上情况,也就是临时变量,只有为右值时才会调用右值引用的拷贝构造。
同理,重载赋值运算符也可以使用右值引用
String& operator=(String&& s) {
if (this != &s) {
delete[] _str;
_str = s._str;
s._str = nullptr;
cout << "String& operator=(String&&)" << endl;
} return *this;
}
move
----将左值属性的变量修改为右值
void test()
{
int a = 10;
int&& rra = move(a);//ok
}
完美转发----在函数传递过程中保持变量的原有属性
void Fun(int& x) {
cout << "左值引用" << endl; }
void Fun(int&& x) {
cout << "右值引用" << endl; }
void Fun(const int& x) {
cout << "const类型的左值引用" << endl; }
void Fun(const int&& x) {
cout << "const类型的右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) //如果是左值则为左值引用
{
Fun(std::forward<T>(t)); //完美转发std::forward<T>(name)
}
void test() {
PerfectForward(10); // 右值引用
int a = 4;
PerfectForward(a); // 左值引用
PerfectForward(std::move(a)); // 右值引用
const int b = 8;
PerfectForward(b); // const类型的左值引用
PerfectForward(std::move(b)); // const类型的右值引用
}
转载:https://blog.csdn.net/qq_44443986/article/details/117405080