前言
本文对C++中的左值、右值、左值引用、右值引用进行对比区分
一、左/右值是什么?
左值:一般出现在赋值运算符左侧的,具名且可以取地址的变量;
右值:只能出现在赋值运算符右侧的表达式或函数返回值,常量,无名且不能取地址;
int a = 1;//a为左值,1为右值
int b = a+2;//b为左值,(a+2)是一个表达式,为右值
二、左/右值引用
1.左值引用
一般C++中的引用就是指的左值引用,它必须使用左值初始化(const左值引用除外)
如下:
int a = 1;
int &r_a1 = a;
int &r_a2 = 1;//error
const int &r_a3 = 1;//right
2.右值引用
右值引用是对临时变量,将要消亡的值的引用,也需要定义时初始化,是C++中引入的支持移动语义的新的特性。
移动语义:在资源转移时避免不必要的拷贝,从而提高内存使用性能;例如原先拷贝初始化时,需要给目标对象创建新的内存,并将原对象的值进行拷贝,再释放原对象的内存。移动构造函数或移动赋值运算符则可以在原对象的基础上直接构造新的对象而避免了内存申请的开销。
如下是一个使用右值引用的实例:
#include <iostream>
class MyClass {
private:
int* data;
public:
MyClass() {
data = new int[10];
std::cout << "Default constructor called" << std::endl;
}
MyClass(const MyClass& other) { // 拷贝构造函数
data = new int[10];
std::copy(other.data, other.data + 10, data);
std::cout << "Copy constructor called" << std::endl;
}
MyClass(MyClass&& other) { // 移动构造函数,使用右值引用
data = other.data;
other.data = nullptr;
std::cout << "Move constructor called" << std::endl;
}
~MyClass() {
delete[] data;
}
void printData() {
for (int i = 0; i < 10; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};
MyClass createObject() {
MyClass obj;
return obj;
}
int main() {
MyClass obj1;
obj1.printData();
MyClass obj2 = createObject(); // 这里会调用移动构造函数
obj2.printData();
return 0;
}
总结
这里对文章进行对比总结:
特性/概念 | 左值 (lvalue) | 右值 (rvalue) |
---|---|---|
定义 | 能够被取地址,代表一个对象的身份 | 不能被取地址,代表一个对象的值 |
用途 | 可以出现在赋值表达式的左侧,可以被多次访问 | 通常出现在赋值表达式的右侧,用于传递值 |
类型 | 变量、数组、对象成员等 | 临时对象、字面量、返回值等 |
返回类型 | 可以返回左值引用 | 可以返回右值引用或左值引用 |
修饰符 | 通常不需要 const | 可以被 const 修饰,形成 const rvalue reference |
常用场景 | 函数参数,需要修改对象时 | 移动语义,函数参数,需要获取对象所有权时 |
与引用的关系 | 左值引用可以绑定到左值 | 右值引用可以绑定到左值或右值 |
特性/概念 | 左值引用 (&) | 右值引用 (&&) |
---|---|---|
定义 | 引用一个对象,必须绑定到对象上 | 引用一个对象,但可以绑定到对象或对象的临时状态 |
用途 | 用于获取对象的别名 | 用于接受对象的所有权,或者引用临时对象 |
与对象的关系 | 必须引用一个已存在的对象 | 可以引用一个临时对象或已存在的对象 |
返回类型 | 返回左值引用 | 返回右值引用 |
修饰符 | const 修饰的左值引用为 const T& | const 修饰的右值引用为 const T&& |
常用场景 | 函数参数,需要修改对象时 | 移动语义,函数参数,需要获取对象所有权时 |
与 std::move | 无直接关系 | 使用 std::move 可以将左值转换为右值引用 |