C++ ——左值和右值

1 左值和右值

左值(loactor value):存储在内存中、可寻址的数据

右值(read value):可以提供数据值的数据(不一定可寻址,例如存储在寄存器中的数据)

区分:

  • 可以位于=左侧的表达式就是左值;右值只能位于=右侧(c++中左值也能位于=右侧,当做右值使用)
  • 有名称、可寻址的是左值;反之是右值

2 右值引用

2.1 右值引用的引入

C++11之前的引用(&)只能操作左值,无法操作右值。

常量左值引用可以操作右值,但是无法对右值进行修改。

int a = 10;
int& b = a;     //左值引用
int& c = 10;    //错误,左值引用无法操作右值

const int& d = 10;    //常量左值引用可以操作右值

因此,C++11引入了右值引用 &&

2.2 右值引用的使用

  • 初始化:只能用右值初始化,且和左值一样,声明后必须立即初始化
  • 右值引用可以修改右值
  • 可定义常量右值引用,但没什么意义。右值引用主要用于实现移动语义和完美转发,移动语义需要有修改右值的权限;常量右值引用可有常量左值引用实现
int&& a = 10;            //右值引用
a = 11;                  //通过右值引用修改右值
const int&& b = 15;      //常量右值引用
        

2.3 总结

非常量左值引用              只能引用               非常量左值

常量左值引用                  可以引用              非常量左值、常量左值、右值

非常量右值引用              只能引用               非常量右值

常量右值引用                  可以引用               常量右值、非常量右值

3 move()函数将左值转换到右值

int num = 10;
int&& a = std::move(num);

4 右值引用的场景

4.1 拷贝构造函数

使用其它对象初始化一个同类的新对象,在C++11之前,只能使用拷贝构造函数。当类中有指针成员变量时,拷贝构造函数需要以深拷贝的方式复制该成员(开辟一片新的空间,复制原对象指针所指向的数据)。

#include <iostream>
using namespace std;

class demo{
public:
    //构造函数
    demo():num(new int(0)){
        cout<<"construct!"<<endl;
    }
    //拷贝构造函数(深拷贝)
    demo(const demo &d):num(new int(*d.num)){
        cout<<"copy construct!"<<endl;
    }
    ~demo(){
        cout<<"class destruct!"<<endl;
    }
private:
    int *num;
};

demo get_demo(){
    return demo();    //返回一个demo对象,是一个右值
}
int main(){
    //demo a = get_demo();     //拷贝赋值
    demo a(get_demo());        //拷贝构造
    return 0;
}

如上所示,demo 类自定义了一个拷贝构造函数。该函数在拷贝 d.num 指针成员时,必须采用深拷贝的方式,即拷贝该指针成员本身的同时,还要拷贝指针指向的内存资源。否则一旦多个对象中的指针成员指向同一块堆空间,这些对象析构时就会对该空间释放多次,这是不允许的。

dema a(get_demo()); 的流程:

  1. 执行 get_demo() 函数,demo()调用构造函数生成一个匿名对象
  2. 执行 return demo() ,调用拷贝构造函数拷贝匿名对象,作为get_demo()的返回值(get_demo()执行完毕,匿名对象会被销毁)
  3. 执行 a(get_demo()), 调用拷贝构造函数(此行代码执行完毕,get_demo()的返回值会被析构)
  4. 程序结束前,a被析构。

在这个过程中,底层执行了2次深拷贝。如果指针指向的堆空间较大,会大大降低执行的效率。通过移动构造函数可以解决这个问题。

 4.2 移动构造函数

移动语义:以移动而非深拷贝的方式初始化含有指针类成员的对象。将其他对象的内存资源移为己用,而非拷贝。这大大提高了初始化的执行效率。

#include <iostream>
using namespace std;
 
class demo{
public:
    demo():num(new int(0)){
        cout<<"construct!"<<endl;
    }
    //拷贝构造函数
    demo(const demo &d):num(new int(*d.num)){
        cout<<"copy construct!"<<endl;
    }
 
    //移动构造函数
    demo(demo &&d):num(d.num){
        d.num = NULL;
        cout<<"move construct!"<<endl;
    }
 
    ~demo(){
        cout<<"class destruct!"<<endl;
    }
private:
    int *num;
};
 
demo get_demo(){
    return demo();
}
 
int main(){
    //调用右值初始化当前对象,移动构造函数存在的情况下,优先调用移动构造函数
    demo a(get_demo());    
    return 0;
}

 移动构造函数:使用右值引用类型的参数,指针浅拷贝右值对象指针置为nullptr, 从而,避免拷贝堆空间,完成初始化。

当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。

std::move()可以将左值转换为右值,从而使用移动构造。

参考

【C++】左值和右值、左值引用(&)和右值引用(&&)_c++ arg&&_Jacky_Feng的博客-CSDN博客 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值