C++11(二)---右值引用和移动语义

server/2024/11/17 23:16:35/

文章目录

    • 右值引用和移动语义
      • 左值和右值
      • 左值引用和右值引用
      • 完美转发

右值引用和移动语义

左值和右值

  • 左值

    左值是一个表示数据的表达式(如变量名或解引用的指针)。我们可以获取左值的地址,一般情况下可以对它赋值(对const修饰的类型的变量无法后续赋值,但是仍为左值,如const int a = 10;此处的a是左值)。左值不一定在左边(如int c = a;)此处的c为左值**

    //以下的p、b、c、*p、s、s[0]都是左值
    //左值:可以取地址
    int* p = new int(0);
    int b = 1;
    const int c = b;
    *p = 10;
    string s("111111");
    s[0];
    
  • 右值

    右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值**(这个不能是左值引用返回)**等等。右值不能取地址。右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边。 右值常常是字面常量或者临时变量

    //右值:不能取地址
    double x = 1.1, y = 2.2;
    //以下几个都是常见的右值,常量临时对象,匿名对象
    10;
    x + y;
    fmin(x, y);
    string("11111");
    

    右值又分为 纯右值 和 将亡值
    纯右值: 内置类型右值

    将亡值: 类类型的右值引用 (匿名对象等等)

左值引用和右值引用

左值引用是对左值进行引用;右值引用是对右值进行引用。
即左值引用是对左值取别名;右值引用是对右值取别名

  • 左值引用

    实际上人们常说的引用就是左值引用

    • 语法:类型&
      //以下的p、b、c、*p、s、s[0]都是左值
      //左值:可以取地址
      int* p = new int(0);
      int b = 1;
      const int c = b;
      *p = 10;
      string s("111111");
      s[0];
      //左值引用给左值取别名
      int& r1 = b;
      int*& r2 = p;
      int& r3 = *p;
      string& r4 = s;
      
  • 右值引用

    • 语法:类型&&

      //右值:不能取地址
      double x = 1.1, y = 2.2;
      //以下几个都是常见的右值,常量临时对象,匿名对象
      10;
      x + y;
      fmin(x, y);
      string("11111");
      //右值引用给右值取别名
      int&& rr1 = 10;
      double&& rr2 = x+y;
      double&& rr3 = fmin(x,y);
      

      注意rr1,rr2,rr3是右值引用,但是右值引用本身是左值。即右值被右值引用后,右值引用本身是左值。这才使得后续在移动构造和移动赋值中可以转移对应右值引用的资源。

  • 左值引用和右值引用的交叉

    • 左值引用可以给左值取别名,那么左值引用可以给右值取别名吗?
      答案是肯定的。因为在右值引用出现前,已经可以用左值引用引用右值了,即用const修饰的引用。
      如下:

      //右值:不能取地址
      double x = 1.1, y = 2.2;
      //以下几个都是常见的右值,常量临时对象,匿名对象
      10;
      x + y;
      fmin(x, y);
      string("11111");
      //左值引用引用给右值取别名:不能直接引用,但是const 左值引用可以
      const int& rx1 = 10;
      const double& rx2 = x+y;
      const double& rx3 = fmin(x, y);
      const string& rx4 = string("11111");
      
    • 右值引用可以给右值取别名,那么右值引用可以给左值取别名吗?
      右值引用给左值取别名,不能直接引用,需要调用一下move函数

      • move函数如下
        在这里插入图片描述

        该函数参数是需要一个左值,返回值为一个右值。
        该函数的本质就是强制类型转换

      • 右值引用给左值取别名

        //以下的p、b、c、*p、s、s[0]都是左值
        //左值:可以取地址
        int* p = new int(0);
        int b = 1;
        const int c = b;
        *p = 10;
        string s("111111");
        s[0];
        //右值引用给左值取别名
        int&& rrx1 = move(b);
        int*&& rrx2 = move(p);
        int&& rrx3 = move(*p);
        string&& rrx4 = move(s);
        string&& rrx5 = (string&&)s;
        
  • 左值引用和右值引用的底层

    • 底层的实现和语法层的规定可能是相背离的,因此不要试图用底层实现来佐证语法层。

    • 左值引用和右值引用,在语法层面上是没有开空间的!!!

    • 以下图为例,来观察左值引用和右值引用的底层
      在这里插入图片描述

      由此处汇编可以看出,实际上左值引用和右值引用在底层上都是开了空间的。对于左值引用,这个空间内存储一个 指向被引用的数据所在的空间。 对于右值引用,这个空间内存储一个 指向被引用的数据所在的空间。 从这里看,左值引用和右值引用在底层实现上几乎没有区别,一个存储的是语法层上左值所在的地址;一个存储的是语法层上右值所在的地址

      • 强制类型转换一定产生临时变量
        看了左值引用和右值引用的底层,那么在语法层上,想要右值引用引用左值,真的只能通过调用move吗?并不是的,我们只需要产生一个临时变量,然后用右值引用引用该临时变量即可。
        什么意思呢?如下

        int b = 1;
        int&& rr1 = (int)b;
        int&& rr2 = (double)b;
        int&& rr3 = (size_t)b;
        //等等,都是可以的,强制类型转换产生临时变量,而临时变量是右值,因此可以右值引用。即通过强转使得右值引用 引用 左值。
        
  • 右值引用的一些应用场景

    • 移动构造

      移动构造在深拷贝时才有意义

    • 移动赋值

      移动赋值在深拷贝时才有意义

完美转发

由于 模板实例化为右值引用时,右值引用的属性会退化成左值*(即 右值引用本身是左值)*,而我们想要传入的参数原本的属性。完美转发来解决这种场景

  • 在模板中,T&& 表示万能引用,而不表示右值引用
    万能引用: 当 传入的数据类型为左值时,T&&为左值引用,实例化为传入数据类型&;当传入数据类型为右值时,T&&为右值引用,实例化为传入数据类型&&。

    template<class T>
    T&& Add(T&& a, T&& b)
    {return a + b;
    }
    
  • 完美转发
    在std中提供了实现 完美转发的函数模板。
    在这里插入图片描述

    完美转发是通过传入的参数的实际类型来进行传递的。

    • 应用场景样例

      void Fun(int& x) { cout << "左值引用" << endl; }
      void Fun(const int& x) { cout << "const 左值引用" << endl; }
      void Fun(int&& x) { cout << "右值引用" << endl; }
      void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 引用
      // 传左值->左值引用
      // 传右值->右值引用
      template<typename T>
      void PerfectForward(T&& t)
      {// 模版实例化是左值引用,保持属性直接传参给Fun// 模版实例化是右值引用,右值引用属性会退化成左值,转换成右值属性再传参给FunFun(forward<T>(t));
      }
      int main()
      {PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);			  // const 左值PerfectForward(std::move(b)); // const 右值return 0;
      }
      

      如果没有使用完美转发,那么当参数传入PerfectForward函数后,会在该函数中调用Fun(int&x);或者Fun(const int& x);,这是因为 无论t是左值引用还是右值引用,其本身的属性都是左值。
      使用完美转发后,那么当参数传入PerfectForward函数后,会通过原本的数据的参数类型来调用Fun函数。举个例子:PerfectForward(std::move(b));函数,调用时,t的类型为const int&&,t本身是左值,因此Fun(t)调用的为void Fun(const int& x);而Fun(<forward<T>(t));forward<T>(t)是通过自动推导,返回一个 与 传入t的数据的属性相同的数据,这里可以理解为此处的forward<T>(t)返回了一个std::move(t),因为传入t的数据的属性是const 右值,因此这里将t再转发为右值。


http://www.ppmy.cn/server/142747.html

相关文章

Go语言24小时极速学习教程(二)复合数据(集合)操作

在Go语言中&#xff0c;复合数据类型是由基本数据类型组合而成的更复杂的数据结构。常见的复合数据类型包括数组&#xff08;Array&#xff09;、切片&#xff08;Slice&#xff09;、映射&#xff08;Map&#xff09;、结构体&#xff08;Struct&#xff09;和接口&#xff08…

腾讯云产品推荐----域名的使用

一、域名的定义 域名&#xff08;英语&#xff1a;Domain Name&#xff09;&#xff0c;又称网域&#xff0c;是由一串用点分隔的名字组成的互联网上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。由…

【算法】二分

1. 找到有序区间中 x 最左边的数字的位置 static int getL(int a[], int l, int r, int x) {while (l < r) {int mid l r >> 1;if (x < a[mid]) {r mid;} else {l mid 1;}}if (a[l] ! x) return -1;return l;} 2. 找到有序区间中 x 最右边的数字的位置 stati…

如何使用Django写个接口,然后postman中调用

好的&#xff0c;下面是一个详细的步骤&#xff0c;展示如何使用 Django 创建一个简单的 API 接口&#xff0c;并在 Postman 中进行调用。 1. 创建 Django 项目和应用 首先&#xff0c;确保你已经安装了 Django。如果还没有安装&#xff0c;可以使用以下命令安装&#xff1a;…

【项目日记】仿mudou的高并发服务器 --- 整体框架搭建 ,实现时间轮模块

命运的局限尽可永在&#xff0c; 不屈的挑战却不可须臾或缺。 --- 史铁生 --- 项目地址在这里: https://gitee.com/penggli_2_0/TcpServer 仿mudou的高并发服务器 1 项目介绍2 模块组成3 实现时间轮模块3.1 设计思想3.2 定时任务类3.3 TimeWheel时间轮类 1 项目介绍 这是一…

智谱AI清影升级:引领AI视频进入音效新时代

前几天智谱推出了新清影,该版本支持4k、60帧超高清画质、任意尺寸&#xff0c;并且自带音效的10秒视频,让ai生视频告别了"哑巴时代"。 智谱AI视频腾空出世&#xff0c;可灵遭遇强劲挑战&#xff01;究竟谁是行业翘楚&#xff1f;(附测评案例)之前智谱出世那时体验了一…

深度学习transformer

Transformer可是深度学习领域的一个大热门呢&#xff01;它是一个基于自注意力的序列到序列模型&#xff0c;最初由Vaswani等人在2017年提出&#xff0c;主要用于解决自然语言处理&#xff08;NLP&#xff09;领域的任务&#xff0c;比如机器翻译、文本生成这些。它厉害的地方在…

12、Linux系统的网络基本设置

查看网络接口信息ifconfig ip addr/ip a #简单查看网络接口信息 ifconfig #表示只显示当前活跃的设备接口信息 ifconfig -a #查看当前主机所有的&#xff08;all&#xff09;网络设备&#xff0c;包括未运行的设备。 如我们查看本机网卡ens33的…