1.左值引用
在C 中,左值引用和右值引用是用来声明变量的引用类型的两种方式。传统的C 语法中就有引用的语法,而C 11中新增了的右值引用语法特性,所以我们将C 11之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
例如:
代码语言:javascript复制int a = 10;
int& b = a; //左值引用
func(int& x)//左值引用
{
//...
}
我们之前学过对变量取别名和函数的传引用传参都是左值引用
- 什么是左值?
左值是一个表示数据的表达式(如变量名或/解引用的指针),我们可以获取它的地址对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
例如,以下都是左值:
代码语言:javascript复制int* p = new int(0);
int b = 1;
const int c = 2;
简单来说,左值就是可以取地址的表达式,以上*p,b,c都可以取地址
- 什么是左值引用?
左值引用就是对左值的引用,就是给左值取别名。
例如:
代码语言:javascript复制// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
2.右值引用
右值引用使用&&
符号进行声明,它是对一个右值进行引用。
右值(Rvalue)是指可以放在赋值运算符右侧的表达式,因为它没有一个确定的内存地址,所以右值也不能进行取地址。右值通常是一个临时的值或者一个将要被移动的值。右值可以用于初始化一个变量、传递给函数或者返回给调用者。
右值具有如下特点:
- 右值可以是字面量、临时对象、表达式的结果或者一些函数的返回值。
- 右值没有持久性,它们不能出现在赋值运算符的左侧。
- 右值可以被移动、交换或者销毁。
例如:
代码语言:javascript复制double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;//字面常量
x y;//表达式的结果
fmin(x, y);//函数的返回值,也指临时对象
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x y = 1;
fmin(x, y) = 1;
结果如下:
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,例如:上面例子中,不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
✨✨我们不能简单通过在赋值符号的左右来判断左值还是右值,因为左值也可以放在赋值符号的右边,但是我们可以通过是否可以取地址来判断左值和右值,左值可以取地址,右值不可以取地址。
3.右值引用与左值引用的比较
- 对于左值引用:
代码语言:javascript复制左值引用不可以直接引用右值,但是可以通过const修饰左值引用来引用右值
// 左值引用只能引用左值,不能引用右值。
int a = 10;
int& ra1 = a; // ra1为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;//引用右值
const int& ra4 = a;//引用左值
上述例子中,10具有常性,不能直接被左值引用,但是加了const之后就可以;右值的字面量、临时对象、表达式的结果或者一些函数的返回值都具有常性,所有被const修饰后可以左值引用右值。
- 对于右值引用:
右值引用不可以直接引用左值,但可以通过move(左值)
来达到右值引用
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int a = 10;
int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);
简单来说move可以将左值转换为右值,这样就可以对左值进行间接地右值引用
✨左值引用总结:
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值。
✨右值引用总结:
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
4. 右值引用使用场景和意义
这里有一个string类,放在自己的命名空间内,和之前实现过的string类一样,用来当作辅助理解的例子:
代码语言:javascript复制#include<assert.h>
#include<iostream>
using namespace std;
namespace tutu
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str _size;
}
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "string(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(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
reserve(s._capacity);
for (auto ch : s)
push_back(ch);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
if (this != &s)
{
_str[0] = '