第14课 右值引用(1)_基本概念

2023-11-13

1. 左值和右值

(1)两者区别:

  ①左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。

  ②右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。

(2)右值的分类

  ①将亡值(xvalue,eXpiring value):指生命期即将结束的值,一般是跟右值引用相关的表达式,这样表达式通常是将要被移动的对象,如返回类型为T&&的函数返回值(如std::move)、经类型转换为右值引用的对象(如static_cast<T&&>(obj))、xvalue类对象的成员访问表达式也是一个xvalue(如Test().memberdata,注意Test()是个临时对象)

  ②纯右值(prvalue, PureRvalue):按值返回的临时对象运算表达式产生的临时变对象原始字面量lambda表达式等。

(3)C++11中的表达式

  ①表达式是由运算符(operator)和运算对象(operand)构成的计算式。字面值和变量是最简单的表达式函数的返回值也被认为是表达式

  ②表达式是可求值的,对表达式求值将得到一个结果这个结果有两个属性类型和值类别,而表达式的值类别必属于左值、将亡值或纯右值三者之一

  ③“左值”和“右值”是表达式结果的一种属性。通常用“左值”来指代左值表达式,用“右值”指代右值表达式。

2. 右值引用和左值引用

(1)右值引用和左值引用

  ①右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化

  ②左值引用是具名变量/对象的别名,右值引用是匿名变量/对象的别名。

  ③左值和右值是独立于它的类型的,即左右值与类型没有直接关系,它们是表达式的属性具名的右值引用是左值,匿名的右值引用是右值。如Type&& t中t是个具名变量(最简单的表达式),t的类型是右值引用类型,但具有左值属性。而Type&& func()中的返回值(是个表达式)是右值引用类型,但具有右值属性(因为是个匿名对象)。

(2)C++中引用类型及其可以引用的值类别

引用类型

可以引用的值类别

备注

非常量左值

常量左值

非常量右值

常量右值

Type&

Y

N

N

N

只能绑定到非常量左值

const Type&

Y

Y

Y

Y

万能类型、用于拷贝语议

Type&&

N

N

Y

N

只能绑定到右值。用于移动语义和完美转发

const Type&&

N

N

Y

Y

暂无用途

  ①常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。

  ②常量左值引用可以使用右值进行初始化,这时它可以像右值引用一样将右值的生命期延长。不过相比于右值引用所引用的右值,常量左值所引用的右值在它的“余生”中只能是只读的。

【编程实验】左、右值引用

复制代码

#include <iostream>
#include <type_traits>

//编译选项:g++ -std=c++11 test1.cpp -fno-elide-constructors
using namespace std;

//左/右值以及左/右值引用

struct Test
{
    int m;

public:
    Test(){cout << "Test()" << endl;}
    Test(const Test& t){cout << "Test(const Test&)" << endl;}
    Test(Test&& t){cout << "Test(Test&&)" << endl;}
    
    ~Test(){cout << "~Test()" << endl;}
};

Test&& func()
{
    return Test(); //不安全!返回局部对象的引用(用于演示)!
}

Test ReturnRvalue()
{
    return Test(); 
}

int main()
{
    //1. 左、右值判断
    int i = 0;
    int&& ri = i++; //i++为右值,表达式返回是i的拷贝,是个匿名变量。故为右值。
    int&  li = ++i; //++i返回的是i本身,是具名变量,故为左值。
    
    int* p = &i;
    int& lp = *p;    //*p是左值,因为可以取*p的地址,即&(*p);
    int* && rp = &i; //取地址表达式结果是个地址值,故&i是纯右值。
    
    int&& xi1 = std::move(i); //std::move(i)是个xvalue
    int&& xi2 = static_cast<int&&>(i); // static_cast<int&&>(i)是个xvalue
    
    auto&& fn = [](int x){ return x * x; }; //lambda表达式是右值,可以用来初始化右值引用
    cout << std::is_rvalue_reference<decltype(fn)>::value << endl; //1
    
    Test t;
    int& rm1  = t.m; //由于t是左值,而m为普通成员变量,所以m也为左值。
    int&& rm2 = Test().m; //由于Test()是个右值,所以m也是右值
    
    int Test::*pm = &Test::m; //定义指向成员变量的指针,指向non-static member data;
    //int&& rm3 = t.*pm; //error,由于t是左值,*pm也是左值,不能用来初始化右值引用。
    int& rm3 = t.*pm;    //ok
    int&& rm4 = Test().*pm; //ok,Test()是临时变量,为右值。所以*pm也是右值
    
    //2. 左/右值引用的初始化
    int a;
    int&  b = a;  //ok
    //int&& b = a;  //error,右值引用只能绑定到右值上    
    
    Test&& t1 = ReturnRvalue();  //返回值是个临时对象(右值) 被绑定到t1上,使其“重获新生”,
                                 //生命期与t1一样。
    Test   t2 = ReturnRvalue();  //返回值是个临时对象(右值),用于构造t2,之后该临时对象
                                 //就会马上被释放。
    //Test&  t3 = ReturnRvalue();    //普通左值引用不能绑定到右值
    const Test& t4 = ReturnRvalue(); //常左值引用是个“万能引用”,可以绑定到右值    
    
    
    //system("pause");
    return 0;
}

复制代码

3. universal引用(T&&)

(1)T&&的两种含义

  ①右值引用:当T是确定的类型时,T&&为右值引用。如int&& a;

  ②当T存在类型推导时,T&&为universal引用,表示一个未定的引用类型。如果被右值初始化,则T&&为右值引用。如果被左值初始化,则T&&为左值引用

(2)引用折叠

  ①由于引用本身不是一个对象,C++标准不允许直接定义引用的引用。如“int& & a = b;”(注意两个&中间有空格,不是int&&)这样的语句是编译不过的。

  ②当类型推导时可能会间接地创建引用的引用,此时必须进行引用折叠。具体折叠规则如下:

    A. X& &、X& &&和X&& &都折叠成类型X&。即凡是有左值引用参与的情况下,最终的类型都会变成左值引用。

    B. 类型X&& &&折叠成X&&。即只有全部为右值引用的情况才会折叠为右值引用。

  ③引用折叠规则暗示我们,可以将任意类型的实参传递给T&&类型的函数模板参数。

(4)注意事项

  ①只有当发生自动类型推导时(如函数模板的类型自动推导或auto关键字),&&才是一个universal引用。当T的类型是确定的类型时,T&&为右值引用。

  ②当使用左值(类型为A)去初始化T&& t时,类型推导为A& &&,折叠会为A&,即t的类型为左值引用。而如果使用右值初始化T&&时,类型推导为A&&,一步到位无须折叠。

  ③universal引用仅仅在T&&下发生任何一点附加条件都会使之失效,而变成一个普通的右值引用(const T&&被const修饰就成了右值引用)

【编程实验】引用折叠

复制代码

#include <iostream>
#include <vector>
using namespace std;

//编译选项:g++ -std=c++11 test2.cpp

class Widget{};

template<typename T>
class Foo
{
public:
    typedef T&& RvalueRefToT;  //RvalueRefToT为universal引用
};

//universal引用: T&&存在类型推导
template<typename T>
void func(T&& param)    //存在类型推导param为universal引用
{};

//非universal引用:形参必须是严格的T&&格式。
template<typename T>
void func(vector<T>&& param){}; //param是一个右值引用,因为当给func传入实参时,T被推导后vector<T>&&的类型是确定的。
                                
//非universal引用:形参必须是严格的T&&格式。
//哪怕被const修饰也不行
template<typename T>
void func(const T&& param);  //param是一个右值引用

//比较universal引用和右值引用
//假设实例化后为:Vector<Widget> v;
template<class T, class Allocator=allocator<T>>
class Vector{

public:
    //虽然push_back的形参符合T&&格式,但不是universal引用因为Vector实例化后,push_back的形参类型就确定下来
    //所以在调用时push_back函数时并不存在类型推导。
    void push_back(T&& x);
    
    //Arg&&存在类型推导,所以args的参数是universal引用。因为参数Args独立于vector的类型参数T,所以每次emplace_back被调用的时候,Args必须被推导。
    template<class ...Args>
    void emplace_back(Args&&...args);
};

int main()
{
    void f(Widget&& param);   //没有类型推导,param为右值引用
    Widget&& var1 = Widget(); //没有类型推导,var1为右值引用
    
    //1. auto中可能发生引用折叠
    auto&& var2 = var1;   //存在类型推导,var2为universal引用。var2被左值初始化,auto被推导为Widget& &&,折叠后为Widget&
    int w1, w2;
    auto&& v1 = w1;           //v1是universal引用。被左值初始化,所以其类型为int&
    auto&& v2 = std::move(w1);//v2为universal引用,被右值初始化,所以类型为int&&
    
    //2. decltype中可能发生引用折叠:decltype(x)会先取出x的类型,再通过引用折叠规则来定义变量
    decltype(v1)&& v3 = w2;            //v1的类型为int&,所以v3为int& &&,折叠后为int&
    decltype(v2)&& v4 = std::move(w2); //v2的类型为int&&,所以v4为int&& &&,折叠后为int&&
    decltype(w1)&& v5 = std::move(w2); //w1的类型为int,所以v5为int&&
    
    //3. typedef时可能发生的引用折叠
    Foo<int&> f1;  //==>typedef int& && RvalueRefToT; 折叠后:typedef int& RvalueRefToT;
    Foo<int&&> f2; //typedef int&& && RvalueRefToT;   折叠后:typedef int&& RvalueRefToT;
    
    int i = 0;
    int& r1 = i; //ok
    //int& &r2 = r1; //error,不能直接定义引用的引用
    
    typedef int& rint;
    rint r2 = i; //ok,r1为int&类型
    rint &r3 = i; //间接定义引用的引用时,会发生引用折叠。如int& &r3 ==>int& r3
    
    //4. 函数模板参数中可能发生引用折叠
    Widget w;
    func(w);   //用左值w初始化T&&,存在类型推导T为Widget&,传入func后为Widget& &&,折叠后为Widget&,所以param的类型为Widget&,是个左值引用
    func(std::move(w)); //用右值初始化T&&,T被推导为Widget,传入
                        //func后为Widget&&,所以param为右值引用。
    vector<int> v;                
    func(std::move(v));   //调用右值引用的版本:func(vector<T>&& param)。
    
    return 0;
}

复制代码

4. &&的总结

(1)左值和右值是表达式的属性,独立于它们的类型。比如,右值引用类型可能是左值也可能是右值。编译器将具名的右值引用视为左值,匿名的右值引用视为右值

(2)auto&&或函数参数存在类型推导时,T&&是一个未定的引用类型。它可能是左值引用,也可能是右值引用,取决于初始化的值类型。

(3)所有的右值引用叠加到右值引用上仍然是一个右值引用,其它种叠加都是左值引用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第14课 右值引用(1)_基本概念 的相关文章

  • 一个学习编程的网站

    推荐一个学习编程的网站 https www runoob com
  • 请假流程

    作者 nogocn 在某一公司中 部门员工要休假的话需要部门主管的批准 如果休假天数大于10天的话 在部门主管的同意后 还必须上级主管批准 如果是部门主管要休假只要上级主管批准即可 在休假被批准之前 申请人可以撤销休假申请 每个员工还有多少
  • STM32的功耗模式

    按功耗由高到低排列 STM32 具有运行 睡眠 停止和待机四种工作模式 低功耗各模式下芯片工作情况 睡眠模式 仅关闭了内核时钟 内核停止运行 但其片上外设 CM4 核心的外设全都还照常 运行 有两种方式进入睡眠模式 它的进入方式决定了从睡眠
  • Java实现数据脱敏的方法

    在Java中 可以使用各种技术来实现数据脱敏 下面将介绍几种常见的Java实现数据脱敏的方法 字符串截取 字符串截取是一种简单的数据脱敏方法 它将敏感数据的一部分字符替换成 号或其他字符 例如 将身份证号码的前6位和后4位替换成 号 这样可
  • 开发一个文生图的功能

    文章目录 效果 开发环境 原理 核心代码 代码仓库 问题 效果 开发环境 Python 3 10 PyCharm 原理 借助开源项目stable diffusion 通过该项目封装python库diffusers 可以轻易的实现文生图的功能
  • css 强制不换行

    css中强制不换行 文本不会换行 文本会在在同一行上继续 直到遇到 br 标签为止 white space nowrap
  • LeetCode-剑指 Offer II 114. 外星文字典,BFS 搜索算法及图的表示

    剑指 Offer II 114 外星文字典 现有一种使用英语字母的外星文语言 这门语言的字母顺序与英语顺序不同 给定一个字符串列表 words 作为这门语言的词典 words 中的字符串已经 按这门新语言的字母顺序进行了排序 请你根据该词典
  • P25透明屏:探究在商业广告领域的应用表现

    P25透明屏是一种新型的显示屏技术 具有高透明度和高分辨率的特点 它可以将图像或视频直接投影到透明的表面上 使得观众可以透过屏幕看到背后的景物 同时也能够看到屏幕上的内容 P25透明屏广泛应用于商业展示 户外广告 产品展示等领域 P25透明
  • 写代码的心得

    一 初级阶段 管他三七二十一 需求直接开干 程序能跑通能干活就行 二 中级阶段 拿到需求心里过一遍 80 的架构和接口已经梳理定义完毕 尚有部分细节的没想明白怎么实现 边开发边想 边写边重构 等开发完 架构也定义清晰 三 高级阶段 拿到需求
  • 编写出一个通用的人员类(Person),该类具有姓名(Name)、年龄(Age)、性别(Sex)等域。然后对Person 类的继承得到一个学生类(Student),该类能够存放学生的5门课的成绩,并能

    编写出一个通用的人员类 Person 该类具有姓名 Name 年龄 Age 性别 Sex 等域 然后对Person 类的继承得到一个学生类 Student 该类能够存放学生的5门课的成绩 并能求出平均成绩 最后在Main函数中对studen
  • vue点击当前元素添加class 删除兄弟元素的class

    在vue中当我们要实现点击元素动态添加类名时 我们不能像JQ那样去用 xxx addClass class siblings removeClass class 实现 那我们应该怎样去做呢 解决方案 1 在data里面申明一个属性 默认值最
  • LeetCode-1780. 判断一个数字是否可以表示成三的幂的和【数学】

    LeetCode 1780 判断一个数字是否可以表示成三的幂的和 数学 题目描述 解题思路一 将n转为3进制 如果没有2出现那么返回true 例如12 110 3 返回true 21 210 3 返回false 解题思路二 0 解题思路三
  • 计算机毕业设计-基于SSM的个性影片推荐管理系统

    项目系统开发的技术 Java语言 Java主要采用CORBA技术和安全模型 可以在互联网应用的数据保护 它还提供了对EJB Enterprise JavaBeans 的全面支持 java servlet API Java java serv
  • 重磅发布

    微众银行区块链积极听取社区意见和建议 基于多年研发FISCO BCOS底层开源平台的经验 沉淀了一套 FISCO BCOS应用落地指南 百科式地为FISCO BCOS的应用落地提供操作指引 现将该指南开放回馈社区 获取指南 可在文末添加 小
  • HashMap和LinkedHashMap的区别

    java为数据结构中的映射定义了一个接口java util Map 它有四个实现类 分别是 HashMap Hashtable LinkedHashMap TreeMap Map主要用于存储健值对 根据键得到值 因此不允许键重复 重复了覆盖
  • 用Flutter构建漂亮的UI界面 - 基础组件篇

    1 前言 Flutter作为时下最流行的技术之一 凭借其出色的性能以及抹平多端的差异优势 早已引起大批技术爱好者的关注 甚至一些闲鱼 美团 腾讯等大公司均已开始使用 虽然目前其生态还没有完全成熟 但身靠背后的Google加持 其发展速度已经

随机推荐

  • 基于SpringMVC的拦截器(Interceptor)和过滤器(Filter)的区别与联系

    一 简介 1 过滤器 依赖于servlet容器 在实现上基于函数回调 可以对几乎所有请求进行过滤 但是缺点是一个过滤器实例只能在容器初始化时调用一次 使用过滤器的目的是用来做一些过滤操作 获取我们想要获取的数据 比如 在过滤器中修改字符编码
  • 【人脸识别】基于主成分分析PCA算法人脸识别门禁系统含Matlab源码

    1 简介 人脸是反应人身份的最直接的 最可靠的信息资源 通过人脸我们可以很快辨识一个人 这就是所谓的人脸识别 最初的人脸研究人员是一些从事社会心理学的工作者 从20世纪50年代开始有一些文献资料陆续发布在各种学术网上 人脸识别开始了最初的发
  • 软件工程——结构化分析:一种自上而下的分析方法(包含习题)

    什么是结构化分析 数据流图 数据字典 功能分解结构图 过程描述 结构化分析的优势与挑战 结合不同方法以适应不同场景 总结 习题1 银行计算机储蓄系统 习题2 机票预定系统 习题3 患者监护系统 习题4 复印机的状态转换图绘制 习题5 高校计
  • 一文详解分布式系统的分区

    为什么要分区 数据的复制是冗余的过程 冗余会增加可用性 并且可以有效均衡读取负载 而数据的分区是一个整体转换为局部的过程 这种拆解就像你拥有大量图书 但你的书架放不下 所以需要再加几个书架存储是一个道理 将整体拆分 局部存储在多个较小空间内
  • spring中创建bean的方式

    一 常见的bean创建方式 1 基于xml配置bean 2 使用 Component派生注解 3 使用 Configuration和 Bean注解 1 常见的使用xml中setter方法创建bean bean xml文件中配置bean时 加
  • 史上最全的OpenCV入门教程

    一 Python OpenCV 入门 欢迎阅读系列教程 内容涵盖 OpenCV 它是一个图像和视频处理库 包含 C C Python 和 Java 的绑定 OpenCV 用于各种图像和视频分析 如面部识别和检测 车牌阅读 照片编辑 高级机器
  • JS实现奇偶数的判断

    奇数和偶数的判断是数学运算中经常碰到的问题 比如 有变量x 如果x 1则为奇数 为2则为偶数 这篇文章主要讲解通过JavaScript来实现奇偶数的判断 方法一 求余 if else的形式 var num parseInt prompt 请
  • 人物专访

    把算法应用到各行各业中 这是我从创业初期就有的梦想 华院计算创始人 董事长宣晓华表示 文 科创板日报 黄心怡 成立于2002年的华院计算 可谓国内算法和AI的最早探索者之一 多年来始终致力于算法技术的研究和应用 面对当前的大模型浪潮 宣晓华
  • keyshot保存为ksp_keyshot渲染教程:keyshot教你如何简单的渲染冰与水

    摘要 keyshot是一个互动性的光线追踪与全域光渲染程序 keyshot的渲染的教程有哪些呢 下面是小编整理的关于keyshot渲染教程之keyshot教你如何简单的渲染冰与水 keyshot是一个互动性的光线追踪与全域光渲染程序 key
  • Windows解决camelot报错OSError: Ghostscript is not installed

    文章目录 解决方案 1 安装并配置Ghostscript 2 添加环境变量 3 重启python应用 解决方法也很简单 就是安装并配置Ghostscript 解决方案 1 安装并配置Ghostscript 首先访问 https ghosts
  • LeetCode--初级算法--数组篇-存在重复

    题目 给定一个整数数组 判断是否存在重复元素 如果任何值在数组中出现至少两次 函数返回 true 如果数组中每个元素都不相同 则返回 false 示例 1 输入 1 2 3 1 输出 true 示例 2 输入 1 2 3 4 输出 fals
  • 我是如何在12周内由零基础成为一名程序员的(转)

    我是如何在12周内由零基础成为一名程序员的 我的故事 在海军陆战队服役超过10年后 我于去年7月份退役了 随后在8月份找到了一份赌场的工作做公关 到今年2月中旬的时候又被辞退了 到5月中旬的时候我在DE协会找到了一份临时的 初级用户体验工程
  • Redis持久化的原理及优化

    更多内容 欢迎关注微信公众号 全菜工程师小辉 Redis提供了将数据定期自动持久化至硬盘的能力 包括RDB和AOF两种方案 两种方案分别有其长处和短板 可以配合起来同时运行 确保数据的稳定性 RDB 保存数据快照至一个RDB文件中 用于持久
  • An HTTP error occurred when trying to retrieve this URL.

    问题背景 conda install xxx 提示 An HTTP error occurred when trying to retrieve this URL Fetching package metadata CondaHTTPErr
  • 【Leetcode】863. 二叉树中所有距离为 K 的结点

    题目描述 题解 用map记录每个结点的父结点 然后让dfs从target结点开始 假设target就是根结点 然后递归时纪录深度 只要深度等于k 就是和target的距离等于k 就可以存入list 执行用时 14 ms 在所有 Java 提
  • LeetCode-1304. Find N Unique Integers Sum up to Zero

    Given an integer n return any array containing n unique integers such that they add up to 0 Example 1 Input n 5 Output 7
  • 毕业遭失业,前途一片黑暗...不得已转行软件测试,太多心酸和无助...

    大家好 我叫小涵 一名应届毕业生 目前已经成功转行互联网 写这篇文章的目的是因为很多人不喜欢自己的现状 想通过学习改变 奈何没有出路 所以想为这部分人提供一些思路 其次文章会总结我自己转行前后的经历和思考 提供一些我亲测有效的面试资源 找不
  • 解决Vmware Unbuntu 22虚拟机网络故障问题

    上午启动Vmware Unbuntu 22虚拟机 发现不能上网 屏幕右上侧的网络图标没有显示 怀疑是昨天在虚拟机上做路由器功能设置的时候某个操作产生的问题 于是进行排障 先尝试重启网络服务 service NetworkManager re
  • elasticsearch安装 及 启动异常解决

    虚拟机使用net连接模式 1 Download and unzip the latest Elasticsearch distribution 2 Run bin elasticsearch on Unix or bin elasticse
  • 第14课 右值引用(1)_基本概念

    1 左值和右值 1 两者区别 左值 能对表达式取地址 或具名对象 变量 一般指表达式结束后依然存在的持久对象 右值 不能对表达式取地址 或匿名对象 一般指表达式结束就不再存在的临时对象 2 右值的分类 将亡值 xvalue eXpiring