java左值与右值问题_5分钟搞懂C++左值引用和右值引用!

int i = 42; //i是左值,可以对i取地址int &r = i; //r是左值引用,绑定左值i

int &&rr = i; //错误!i是左值,不能绑定到右值引用rrint &&rr3 = rr; //错误!!!!rr是一个右值引用类型的变量,是一个左值。

int &r2 = rr; //正确,rr是右值引用类型变量,变量都是左值。int &r3 = r; //正确,r是左值引用类型变量,变量都是左值。

接下来我们看一下表达式,产生临时变量或字面常量的表达式都是右值,反之则是左值。我们依旧拿代码作为示例。

int &&rr_result = Add(1, 2); //不能对表达式取地址,所以表达式结果是一个右值,可以绑定到右值引用int &&rr2 = i * 42; //不能对表达值取地址,所以表达式结果是右值,可以绑定到右值引用int &&rr1 = 42; //不能对字面常量取地址,所以字面常量是右值,可以绑定到右值引用

Add函数产生一个临时变量,所以是右值。i * 42产生一个临时变量,是右值。42则是字面常量,所以也是右值。

int result = 0;int *ptr = &(result = i * 12); //正确,可以对result取地址int& r3 = (result = i * 12); //正确,表达式的结果存储在变量result中,可以对表达式取地址

const int &r2 = i * 42; //不能对表达值取地址,所以表达式结果是右值,可以绑定到const左值引用

i * 42是一个右值,但是const左值引用类型可以绑定到右值上。

02

拷贝和移动的区别

C++有拷贝构造函数和移动构造函数,拷贝赋值运算符和移动赋值运算符。移动和拷贝两者最大的区别是:拷贝会产生新的内存,而移动不会。通过拷贝获得的对象状态改变,不会影响到源对象,而通过移动获得的对象状态改变,会影响到源对象,而且被移动的源对象失去所有资源的控制权!拷贝会增加内存申请和数据复制的开销,而移动不会。

要实现移动语义,就必须要有:移动构造函数和移动赋值运算符

下面我们举个例子来学习一下拷贝和移动的区别。

class Object{private:    enum{        NAME_LEN = 50,    };public:    Object(): Name(new char[NAME_LEN]){}    Object(const char *NameStr);    ~Object();    Object(const Object& B);    //拷贝构造函数    Object& operator=(const Object& B);   //拷贝赋值运算符    Object(Object&& B);    //移动构造函数    Object& operator=(Object&& B);    //移动赋值运算符    void Print();    bool NameIsEmpty();private:    char *Name;};

我们新建一个Object类,有char*类型Name成员变量,我们在构造函数中申请内存,并拷贝传入的字符串,在析构函数中释放内存。之所以这么做,主要是为了突出拷贝和移动的区别。拷贝构造函数和拷贝赋值运算符源码实现:

Object::Object(const Object& B){    if(this == &B){        return;    }Name = new char[NAME_LEN];    memcpy(Name, B.Name, strlen(B.Name));    std::cout << "Object copy constructor" << std::endl;}Object& Object::operator=(const Object& B){    if(this == &B){        return *this;    }memset(Name, 0, NAME_LEN);    memcpy(Name, B.Name, strlen(B.Name));    std::cout << "Object copy assignment" << std::endl;    return *this;}

移动构造函数和移动赋值运算符源码的实现:

Object::Object(Object&& B){    //防止自我移动    if(this == &B){        return;    }        Name = B.Name;    B.Name = nullptr;    std::cout << "Object move constructor" << std::endl;}Object& Object::operator=(Object&& B){    //防止自我移动赋值    if(this == &B){        return *this;    }    if(!Name){        delete[] Name;    }    Name = B.Name;    B.Name = nullptr;    std::cout << "Object move assignment" << std::endl;    return *this;}

实现了移动构造函数和移动赋值运算符,那么就可以使用std::move()标准库函数将左值变为右值,并调用他们。我们用代码示例来具体看一下如何使用std::move()库函数,并调用我们实现的移动构造函数和移动赋值运算符。

Object B1("Tangmeimei");Object B2(B1); //调用拷贝构造函数B2 = B1;B1.Print();B2.Print();Object B4(std::move(B2));if(B2.NameIsEmpty()){    std::cout << "B2 Object name is empty" << std::endl;}B4.Print();Object B3;B3 = std::move(B1);if(B1.NameIsEmpty()){    std::cout << "B1 Object name is empty" << std::endl;}B3.Print();

03

运行结果和总结

下面我们实际运行一下,看一下效果。

de426788306bb2ab4a88e22ecf747b8e.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值