15.左值、右值与移动构造

左值、右值与移动构造

概念:

左值(Lvalue):

  1. 定义: 左值是指可识别并具有标识符(有名称)的表达式,其值具有持久性,可以取地址。

  2. 示例: 变量、对象、数组元素、引用等都是左值。例如:

    int x = 5;     // x 是左值
    int* ptr = &x; // &x 取地址,合法
    
  3. 性质: 左值有持久性,可以被取地址,可以在程序中多次引用。

右值(Rvalue):

  1. 定义: 右值是指临时的、不具有标识符(通常即将销毁)的表达式,其值可能是计算的结果、字面常量等。

  2. 示例: 字面常量、临时对象、表达式的计算结果等都是右值。例如:

    cppCopy codeint result = 2 + 3;   // 2 + 3 是右值
    int&& rvalueRef = 42; // 42 是右值
    
  3. 性质: 右值通常是临时的,其值在表达式求值后可能会被销毁,不能被取地址。

左值引用(Lvalue Reference):

  1. 定义: 左值引用通过 & 表示,用于绑定到左值。它可以绑定到具有标识符(有名字)的对象。

  2. 示例:

    cppCopy codeint x = 5;
    int& lvalueRef = x; // lvalueRef 是左值引用,绑定到左值 x
    
  3. 性质: 左值引用可以修改绑定对象的值,可以绑定到左值,但不能绑定到右值。

右值引用(Rvalue Reference):

  1. 定义: 右值引用通过 && 表示,用于绑定到右值。它可以绑定到临时对象或表达式的计算结果。

  2. 示例:

    cppCopy code
    int&& rvalueRef = 42; // rvalueRef 是右值引用,绑定到右值 42
    
  3. 性质: 右值引用通常用于实现移动语义,可以绑定到右值,但不能绑定到左值。

重点:左值将优先绑定绑定左值引用,右值将优先绑定右值引用

下面我们将通过样例来进行理解:

样例:

/*************************************************************************
        > File Name: test.cpp
        > Author:Xiao Yuheng
        > Mail:3312638794@qq.com
        > Created Time: Sun Nov  5 13:25:02 2023
 ************************************************************************/

#include <iostream>

using namespace std;

void judge(int &a) {
    cout << "left_value" << endl;
}

void judge(int &&a) {
    cout << "right_value" << endl;
}

#define TEST(a) { \
    cout << "judge " << #a << " : "; \
    judge(a); \
}

int main() {
    int n = 123;
    TEST(n);
    TEST(n + 1);
    TEST(n++);
    TEST(++n);
    TEST(1 + 2);
    TEST(n += 2);
    return 0;
}

​ 由于左值将优先绑定绑定左值引用,右值将优先绑定右值引用,我们可以通过这个示例来进行判断什么情况下是左值,什么情况下是右值,我们先来运行一下看看结果:

在这里插入图片描述

​ 相信大家都听过这样一段话:左值是常态,右值是临时。意思就是说左值是一个常用变量,右值是一个临时变量。大家先记住这句话。

  • 我们先来分析一下TEST(n)这段代码传过去的n值在这个作用域中是一个可以长时间存在的一个值,所以这里是左值。

  • TEST(n + 1)这段代码中的n + 1的返回值是一个临时变量,所以这里是右值

  • TEST(n++)这段代码中的n++,写过函数重载的应该知道这里返回的应该是一个临时变量,所以是右值。

  • TEST(++n)这段代码中的++n,写过函数重载的应该知道这里返回的是n对象本身,所以是左值

  • TEST(1 + 2)这段代码中的1 + 2都是一个常数,所以返回值是一个临时变量,所以是右值

  • TEST(n += 2)这段代码中的n += 2的返回值应该也是n对象本身,所以是左值

    总结:看表达式的返回值是不是一个临时变量

移动构造:

​ 移动构造函数是C++中用于实现移动语义的特殊成员函数之一。它允许有效地将资源的所有权从一个对象移动到另一个对象,而不是进行深复制。移动构造函数通常采用右值引用作为参数,用于绑定到右值。

​ 我们来通过一个例子来理解为什么要有移动构造:

/*************************************************************************
        > File Name: test.cpp
        > Author:Xiao Yuheng
        > Mail:3312638794@qq.com
        > Created Time: Sun Nov 12 20:23:17 2023
 ************************************************************************/

#include <iostream>

using namespace std;

class A {
public:
    A(int size = 10) : __size(size), data(new int[__size]) {
        cout << this << "  A()" << endl;
    }
    A(const A &a) : __size(a.__size), data(new int[__size]) {
        cout << this << "  copy " << endl;
        for (int i = 0; i < __size; i++) {
            data[i] = a[i];
        }
    }
    int &operator[](int ind) const { return data[ind]; }
    A operator+(A &a) {
        A p(__size + a.size());
        for (int i = 0; i < __size; i++) {
            p[i] = data[i];
        }
        for (int i = __size; i < size(); i++) {
            p[i] = a[i - __size];
        }
        return p;
    }
    int size() { return __size; }
    ~A() {
        cout << this << "  ~A() " << endl;
    }

private:
    int __size;
    int *data;
};

int main() {
    A a, b;
    A c = a + b;
    return 0;
}

​ 大家先分析一下上述代码会输出什么东西呢?我们来运行一下看看(记得把返回值优化关了哟,不然看不到拷贝构造)

在这里插入图片描述

​ 我们可以发现这里我们进行了两次拷贝构造:

  • 重载加法运算符中的对象p拷贝给临时对象
  • 临时对象拷贝给对象c

​ 这里进行了两次拷贝构造,而我们知道拷贝构造的时间复杂度是O(n)的,如果多次进行拷贝构造的话,效率很低,这时我们将引用移动构造提高效率。

/*************************************************************************
        > File Name: test.cpp
        > Author:Xiao Yuheng
        > Mail:3312638794@qq.com
        > Created Time: Sun Nov 12 20:23:17 2023
 ************************************************************************/

#include <iostream>

using namespace std;

class A {
public:
    A(int size = 10) : __size(size), data(new int[__size]) {
        cout << this << "  A()" << endl;
    }
    A(const A &a) : __size(a.__size), data(new int[__size]) {
        cout << this << "  copy " << endl;
        for (int i = 0; i < __size; i++) {
            data[i] = a[i];
        }
    }
    A(A &&a) : __size(a.__size), data(a.data) {
        a.data = nullptr;
        cout << this << "  A(A &&a)" << endl;
    }
    int &operator[](int ind) const { return data[ind]; }
    A operator+(A &a) {
        A p(__size + a.size());
        for (int i = 0; i < __size; i++) {
            p[i] = data[i];
        }
        for (int i = __size; i < size(); i++) {
            p[i] = a[i - __size];
        }
        return p;
    }
    int size() { return __size; }
    ~A() {
        cout << this << "  ~A() " << endl;
    }

private:
    int __size;
    int *data;
};

int main() {
    A a, b;
    A c = a + b;
    return 0;
}

​ 这里我们加上了一个拷贝构造的重载。我们来运行一下:

在这里插入图片描述

​ 可以发现我们这里的拷贝构造变成了右值版本。我们来分析一下原因:

  • 由于临时变量是右值,所以在调用拷贝构造函数的时候会调用右值版本
  • 而我们右值版本中由于临时变量不会在后续继续使用,所以我们可以直接把成员属性中的所有东西给拿过来,然后在初始化一下临时变量
  • 这样我们的时间复杂的就变成了O(1),提升了效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值