博客主页: 主页
系列专栏: C++
❤️感谢大家点赞收藏⭐评论✍️
期待与大家一起进步!
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;
const int& ra4 = a;
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int a = 10;
int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);
优点:做参数和返回值都可以提高效率。
缺点:当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。
但这里传值返回又有一个问题,那就是效率很低,而且我进行的都是深拷贝,根据编译器的不同,有可能会进行两次拷贝构造。
在讲解移动拷贝之前我们先来引入几个概念:
内置类型的右值:纯右值
自定义类型的右值:将亡值(因为我们自定义类型,那上面的s=func()来说,过了这一行后,我返回的func就要被销毁了,一般生命周期只有一行)
在const左值引用与右值引用都存在的情况下,我们给右值起别名,编译器会走更匹配的
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
string(string&& s)
:_str(nullptr)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
//因为这里我们用的右值引用,
//我们传值返回的时候会将str识别为将亡值
//因为它满足将亡值的特性,过了return以后就被销毁了
//既然你反正都要销毁,不如和我交换一下
}
string& operator=(string&& s)
{
cout << "string& operator=(string && s) -- 移动拷贝" << endl;
swap(s);
return *this;
}
这里func函数返回的str,如果不做其他说明,编译器会把其强行当成右值,然后会去调用移动拷贝。
这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。
浅拷贝的类不需要实现移动构造,因为浅拷贝的拷贝构造消耗不是很大
右值引用的核心价值是进一步减少拷贝,解决左值引用没有解决的场景,如传值返回
这里为了方便看,所以删除了一些函数,详细可以到【C++】string类的模拟实现(增删查改,比大小,运算符重载)
namespace bit
{
class string
{
public:
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "string(const char* str )--构造函数" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string& operator=(string&& s)
{
cout << "string& operator=(string && s) -- 移动拷贝" << endl;
swap(s);
return *this;
}
string( string&& s)
:_str(nullptr)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
//因为这里我们用的右值引用,
//我们传值返回的时候会将str识别为将亡值
//因为它满足将亡值的特性,过了return以后就被销毁了
//既然你反正都要销毁,不如和我交换一下
}
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
private:
char* _str;
size_t _size;
size_t _capacity; // 不包含最后做标识的\0
};
}