左值引用和右值引用

左值和右值

左值(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之前,当传递参数给函数或者从一个函数传递参数到另一个函数时,参数类型会被隐式地转换为目标函数接受的类型。但是这种转发方式有时会导致参数类型丢失或额外的拷贝。
    • 完美转发通过使用模板和右值引用,能够在不失去参数类型信息的情况下将参数转发给其他函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值