左值和右值
左值(lvalue)和右值(rvalue)是表达式的两种基本分类方式,这些术语主要用于解释赋值语句和函数调用的操作对象。
左值(lvalue):
- 左值是具有标识符(变量名)的表达式,表示在内存中有一个确定的地址,可以对其进行赋值操作。
- 具体来说,左值是指在赋值语句中位于赋值符号左边的对象,通常是变量或者对象的引用,能够取地址,具名。
- 除了变量和对象之外,也可以是数组元素、返回引用的函数等。
int x = 10; // x是左值
int& ref = x; // ref是左值
//前置自增,前置自减
int i = 0;
(++i) = 10;
//赋值运算和复合赋值运算表达式
(i = 9) = 100;
(i += 10) = 1000;
//解引用表达式
A *a = new A();//A为一个对象
*a = xxx;
右值(rvalue):
- 右值是指在赋值语句中位于赋值符号右边的对象,通常是一个临时的、不具有名称的值,或者是一个常量。
- 右值表示的是一个临时的、无法对其取地址的值,不能直接对其进行赋值操作。
- 具体来说,右值是指在赋值语句中产生值的表达式,例如字面常量、临时对象、函数返回的临时对象等。
int y = 20; // 20是右值
int z = x + y; // x + y是右值
//返回引用类型的函数调用
//后置自增、后置自减
i++;
i--;
//算数表达式
int sum = a + b;
//逻辑表达式
bool result1 = x && y;
//比较表达式
bool result5 = (m < n);
左值引用和右值引用
功能差异:
- 左值引用是避免对象的拷贝:传参,函数返回值
- 右值引用是实现移动语义和完美转发
左值引用
- 左值引用使用 & 符号声明,用于引用左值,即具有内存地址的表达式。它们通常绑定到可修改的对象上。
- 左值引用主要用于实现函数参数的传递、函数返回值的修改以及类成员变量的初始化。
- 在函数参数中,左值引用允许修改传递的参数的值,因为它们指向对象的内存地址
int x = 10;
int& ref = x; // ref是对x的左值引用
右值引用
- 右值引用使用 && 符号声明,用于引用右值,即临时的、将要销毁的值。它们通常绑定到临时对象或即将被移动的对象上。
- 右值引用主要用于实现移动语义和完美转发,以提高性能和资源利用率。
- 右值引用在移动语义中发挥重要作用,允许将资源(如内存或文件句柄)的所有权从一个对象转移给另一个对象,而无需执行深层复制。
#include <iostream>
#include <utility> // for std::move
// 简单的类,模拟需要资源管理的对象
class MyString {
private:
char* data; // 指向动态分配的内存
public:
// 构造函数
MyString(const char* str) {
std::cout << "Constructor is called!" << std::endl;
int length = std::strlen(str);
data = new char[length + 1];
std::strcpy(data, str);
}
// 移动构造函数,使用右值引用
MyString(MyString&& other) noexcept : data(nullptr) {
std::cout << "Move constructor is called!" << std::endl;
data = other.data;
other.data = nullptr; // 将原对象的指针置为空,避免在析构时重复释放
}
// 移动赋值运算符,使用右值引用
MyString& operator=(MyString&& other) noexcept {
std::cout << "Move assignment operator is called!" << std::endl;
if (this != &other) { // 避免自我赋值
delete[] data; // 释放原有资源
data = other.data;
other.data = nullptr; // 将原对象的指针置为空,避免在析构时重复释放
}
return *this;
}
// 析构函数
~MyString() {
std::cout << "Destructor is called!" << std::endl;
delete[] data; // 释放动态分配的内存
}
// 输出字符串
void print() const {
std::cout << data << std::endl;
}
};
int main() {
// 创建一个临时的MyString对象
MyString temp("Hello, World!");
// 使用移动构造函数将临时对象的资源转移到新对象
MyString str = std::move(temp);
// 输出新对象的内容
str.print();
// 使用移动赋值运算符将临时对象的资源转移到另一个新对象
MyString anotherStr("Another string");
anotherStr = std::move(temp);
// 输出另一个新对象的内容
anotherStr.print();
return 0;
}
总的来说,左值引用用于引用左值(可修改的对象)
右值引用用于引用右值(临时的、将要销毁的值),并且在语义和用法上有着不同的目的和影响。
移动语义和完美转发
移动语义和完美转发是C++11引入的两个重要概念,它们分别解决了对象所有权转移和泛型编程中参数转发的问题。
-
移动语义(Move Semantics):
- 移动语义是一种语言特性,它允许对象的资源(如堆上的内存)在转移所有权时不进行深拷贝,而是通过移动资源的指针(或其他资源管理对象)来提高效率。
- 通常,移动语义通过右值引用来实现。
- 当对象处于临时状态(例如临时创建的对象或将要被销毁的对象)时,可以使用移动语义来将其资源转移到另一个对象,而无需进行昂贵的深拷贝操作。
-
完美转发(Perfect Forwarding):
- 完美转发是一种能够在泛型编程中准确地保持参数类型的转发机制。
- 在C++11之前,当传递参数给函数或者从一个函数传递参数到另一个函数时,参数类型会被隐式地转换为目标函数接受的类型。但是这种转发方式有时会导致参数类型丢失或额外的拷贝。
- 完美转发通过使用模板和右值引用,能够在不失去参数类型信息的情况下将参数转发给其他函数。