C++:左值(引用)右值(引用)

〇、前言

本文会讨论C++中的左值,右值,左值引用,右值引用,以及会理清它们之间的关系。

一、左值与右值

(一)概述

1. 左值是一般指表达式结束后依然存在的持久化对象。右值指表达式结束时就不再存在的临时对象。便捷的判断方法:能对表达式取地址、有名字的对象为左值。反之,不能取地址、匿名的对象为右值。

2. C++ 表达式(运算符带上其操作数、字面量、变量名等)有两种独立的属性:类型值类别 。类型指变量声明时的类型,而值类别是表达式结果的类型,它必属于左值、纯右值或将亡值三者之一。如int&& x;其中 x 的类型为右值引用,但作为表达式使用时其值类别为左值(因为有名字,可以取址)。比如:

#include <iostream>
void func(int &&b) {
    std::cout << "b: " << b << " addr: " << &b << std::endl;
    b++;
    std::cout << b << std::endl;
}
int main() {
    int c = 10;
    func(std::move(c));
    std::cout << "c: " << c << " addr: " << &c << std::endl;
    return 0;
}

上例中的 b 就是一个右值引用,它接受了一个右值 c。在 main() 中,c 被转化为右值引用传入 func(),因此,func() 中的 b 其实就是 main() 中的 c 别名,它们指向的是同一块内存区域。需要注意的是,func() 中的 b 其实是一个左值,换句话说,右值引用如果绑定了一个右值,它会延长这个右值的生命周期。 这种生命周期的延长意味着,尽管原始表达式产生的值是一个右值,一旦它被一个右值引用所绑定,它就不再是一个"即将销毁的临时值",而更像是一个普通的变量。 这允许开发者在保证效率的同时,也能够更灵活地控制这些值。

运行结果:

g++ 1.cxx -o main -std=c++14
./main
b: 10 addr: 0x16ba16e28
11
c: 11 addr: 0x16ba16e28

我们需要注意的是,main() 中的 c 被转换成了右值引用,但是它的状态没有发生任何改变。它在 func 中被修改,并在 func 返回后仍然保持有效。std::move 只是告诉编译器一个对象可以被“安全地”当作右值来使用,这样就允许在移动语义上下文中使用该对象。

(二)右值的分类

1. 纯右值(prvalue):用于识别临时变量和一些不与对象关联的值。函数返回值为非引用类型、表达式临时值(如1+3)、lambda表达式等。

2. 将亡值(xvalue):是与右值引用相关的表达式,通常指将要被移动的对象。如,函数返回类型为T&&std::move的返回值、转换为T&&的类型转换函数的返回值(注意,这些都是与右值引用相关的表达式)或临时对象。

表格形式:

类型分类特征绑定规则和类型安全转换实例和特殊情况
左值 (lvalue)存在变量、函数调用产生的对象,有固定地址。可绑定到左值引用,例如 int& x = a;变量 int a = 10; 函数调用(返回引用)例如 int& foo();
纯右值 (prvalue)不适宜被移动,不具有可识别的地址。可绑定到右值引用或 const 左值引用,例如 int&& x = 5;字面量例如 42,表达式例如 a + b,函数调用(返回非引用)例如 int foo();
将亡值 (xvalue)适宜被移动,具有地址但对象即将被销毁。可绑定到右值引用,例如使用 std::move 例如 int&& x = std::move(a);通过 std::move 生成的将亡值,例如 std::move(a);使用移动构造函数或移动赋值操作时,传递的实际参数。
泛左值 (glvalue)概括左值和将亡值,即拥有特定身份(地址)且可能即将被销毁。可以绑定到左值引用或右值引用,取决于上下文。例如使用 decltype 捕获引用时,decltype((a)) x = a; 捕获的是 a 的引用。

二、左值引用与右值引用

(一)概述

1. 左值引用和右值引用都属于引用类型。无论是声明一个左值引用还是右值引用都必须立即进行初始化(今天考试刚考到)。

2. 左值引用都是左值。但具名的右值引用是左值,而匿名的右值引用是右值(比如上面的 func 中的 b,虽然它是右值引用,但是它是一个左值)。

(二)可绑定的值类型(设T是个具体类型)

1. 左值引用(T&):只能绑定到左值(非常量左值)

2. 右值引用(T&&):只能绑定到右值(非常量右值)

3. 常量左值引用(const T&):常量左值引用是个“万能”的引用类型:它既可以绑定到左值也可以绑定到右值,它像右值引用一样可以延长右值的生命期。不过相比于右值引用所引用的右值,常量左值引用的右值在它的“余生”中只能是只读的。对于这点,可以参考这个例子:

#include <iostream>
void func(const int &b) {
    std::cout << "b: " << b << " addr: " << &b << std::endl;
    b++; // 会报错
    std::cout << b << std::endl;
}
int main() {
    int c = 10;
    func(std::move(c));
    std::cout << "c: " << c << " addr: " << &c << std::endl;
    return 0;
}

编译出错:

g++ 2.cxx -o main -std=c++14
2.cxx:4:6: error: cannot assign to variable 'b' with const-qualified type 'const int &'
    b++;
    ~^
2.cxx:2:22: note: variable 'b' declared const here
void func(const int &b) {
          ~~~~~~~~~~~^
1 error generated.

4. 常量右值引用(const T&&):可绑定到右值或常量右值。由于移动语义需要右值可以被修改,因此常量右值引用没有实际用处。如果需要引用右值且让其不可更改,则常量左值引用就足够了。

三、万能引用(universal reference)

(一)T&&的含义

1. 当T是一个具体的类型时,T&& 表示右值引用,只能绑定到右值。

2. 当涉及T类型推导时,T&& 为万能引用。若用右值初始化万能引用,则T&&为右值引用。若用左值初始化万能引用,则T&&为左值引用。但不管哪种情况,T&&都是一种引用类型

(二)万能引用

1. T&&是万能引用的两个条件:

(1)必须涉及类型推导

(2)声明的形式也必须正好形如 T&&并且该形式被限定死了,任何对其修饰都将剥夺T&&成为万能引用的资格

2. 万能引用使用的场景

(1)函数模板形参
    
(2)auto&&

一个例子:

#include <iostream>
#include <vector>

using namespace std;

class Widget {};

void func1(Widget &&param){}; // param为右值引用类型(不涉及类型推导)

template <typename T>
void func2(T &&param) {} // param为万能引用(涉及类型推导)

template <typename T>
void func3(std::vector<T> &&param) {
}

template <typename T>
void func4(const T &&param) {}
template <class T> class MyVector {
  public:
    void push_back(T &&x) {
    } // x为右值引用。因为当定义一个MyVector对象后,T己确定。当调用该函数时T的类型不用再推导!

    template <class... Args>
    void emplace_back(
        Args
            &&...args){}; // args为万能引用,因为Args独立于T的类型,当调用该函数时,需推导Args的类型。
};

int main() {

    Widget w;
    func2(w);            // 万能引用, func2(T&& param),param为Widget&(左值引用)
    func2(std::move(w)); // 万能引用, param为Widget&&,是个右值引用。


    int x = 0;
    Widget &&var1 = Widget(); // var1为右值引用(不涉及类型推导)
    auto &&var2 = var1; //万能引用,auto&&被推导为Widget&(左值引用)
    auto &&var3 = x; //万能引用,被推导为int&;(左值引用)

    // 3. 计算任意函数的执行时间:auto&&用于lambda表达式形参(C++14)
    auto timefunc = [](auto &&func, auto &&...params) {
        //计时器启动

        //调用func(param...)函数
        std::forward<decltype(func)>(
            func)( //根据func的左右值特性来调用相应的重载&或&&版本的成员函数
            std::forward<decltype(params)>(params)... //保持参数的左/右值特性
        );

        //计时器停止并记录流逝的时间
    };

    timefunc(func1, std::move(w)); //计算func1函数的执行时间

    return 0;
}

在你提供的代码中,main 函数涉及到万能引用的语句如下:

  1. func2(w);func2(std::move(w)); — 这里的 func2 函数模板参数 T&& 是一个万能引用。它可以绑定到左值和右值。在这两个调用中,第一次调用时 T 被推导为 Widget&(因为 w 是一个左值),而第二次调用时 T 被推导为 Widget(因为 std::move(w) 产生一个右值)。

  2. auto &&var2 = var1; — 这里使用了 auto&&,它也是一个万能引用。var1 是一个左值(尽管它本身是一个绑定到临时对象的右值引用),所以 var2 被推导为 Widget&

  3. auto &&var3 = x; — 这同样使用了 auto&&,这是一个万能引用。由于 x 是一个左值,var3 被推导为 int&

  4. timefunc lambda 表达式的定义中,参数列表 (auto &&func, auto &&...params) 使用了万能引用。这里 auto&& 用于单个参数和参数包,允许这个 lambda 接受任意数量的任意类型的参数,并保持他们的值类别(左值或右值)。

这些例子展示了万能引用在模板类型推导中的强大功能,尤其是在泛型编程和函数重载解析中的应用。通过万能引用,可以写出更灵活的函数和模板,使得它们能够同时接受左值和右值,而无需重载函数。

四、参考

这里。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/611519.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Web】2023浙江大学生省赛初赛 secObj 题解

目录 step 0 step 1 step 2 step 3 题目本身是不难&#xff0c;简单复健一下 step 0 pom依赖就是spring 反序列化入口在./admin/user/readObj 输入流做了黑名单的过滤&#xff0c;TemplatesImpl不能直接打 可以jackson打SignedObject二次反序列化绕过 具体原理看下面这…

基于ESP32和ESP8266的物联网开发过程(二)

在做这个项目前&#xff0c;也做了一些调研。项目的初衷是想要用于智能家居。我比较了小米IoT、阿里云、ESPHOME、巴沙云、点灯科技和ONENET等几个平台。最终选择了Onenet&#xff0c;部分原因是之前用过它的多协议版本&#xff0c;但现在这个版本已经下线了。 小米IoT的公测名…

基于JAVAEE的停车场管理系统(论文 + 源码)

【免费】基于JAVAEE的停车场管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89292324 基于JAVAEE的停车场管理系统 摘 要 如今&#xff0c;我国现代化发展迅速&#xff0c;人口比例急剧上升&#xff0c;在一些大型的商场&#xff0c;显得就格外拥挤&…

深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f525; 引言&#x1f9f1; 原型基础⛓️ 原型链的形成&#x1f504; 修改原型的影响&#x1f3c1; 原型链的尽头为什么null标志着结束&#xff1f;实际意义 &#x1f310; &#x1f504; 继承的实现方式1. 原型链继承…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-11.1,11.2-BSP文件目录组织

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

基于SpringBoot的全国风景区WebGIS按省展示实践

目录 前言 一、全国风景区信息介绍 1、全国范围内数据分布 2、全国风景区分布 3、PostGIS空间关联查询 二、后台查询的设计与实现 1、Model和Mapper层 2、业务层和控制层设计 三、WebGIS可视化 1、省份范围可视化 2、省级风景区可视化展示 3、成果展示 总结 前…

【Vulhub靶场】Nginx 中间件漏洞复现

【Vulhub靶场】Nginx 中间件漏洞复现 一、Nginx 文件名逻辑漏洞&#xff08;CVE-2013-4547&#xff09;1. 影响版本2. 漏洞原理3. 漏洞复现 二、Nginx越界读取缓存漏洞&#xff08;CVE-2017-7529&#xff09;1. 漏洞详情2. 影响版本3. 漏洞复现 三、Nginx 配置错误导致漏洞&…

建发弘爱 X 袋鼠云:加速提升精细化、数字化医疗健康服务能力

厦门建发弘爱医疗集团有限公司&#xff08;简称“建发弘爱”&#xff09;创立于2022年&#xff0c;是厦门建发医疗健康投资有限公司的全资子公司&#xff0c;专业从事医疗健康领域的医疗服务。 建发弘爱通过医疗、健康及产业服务三大板块&#xff0c;为百姓提供医疗和健康全生…

【MySQL基本查询(下)】

文章目录 一、update案例 二、Delete案例注意&#xff1a;delete 全表数据的行为慎用&#xff01;truncate 三、插入查询结果案例 四、了解一些函数1.count函数2.sum函数3. avg函数4.max函数5. min函数 五、group by子句的使用案例having和where 一、update 该关键字的功能就是…

k8s遇到的常见问题及解决

1. error: open /var/lib/kubelet/config.yaml: no such file or directory 解决&#xff1a;关键文件缺失&#xff0c;多发生于没有做 kubeadm init就运行了systemctl start kubelet。 要先成功运行kubeadm init 2. 执行初始化kubeadm init ------的时候报错 The HTTP call…

(Mac)RocketMQ的本地安装测试(详细图示)

目录 部署服务 namesrv / broker下载解压缩运行 namesrvnohup ./bin/mqnamesrv & 启动命令详解运行 broker 测试收发消息运行自带的生产者测试类运行自带的消费者测试类 部署 Dashboard 可视化下载打包运行访问 部署服务 namesrv / broker 下载解压缩 官网下载 https://r…

Excel——项目管理,设置时间到期自动提醒及颜色高亮

效果图 第一步、自动获取合同到期日期 1、首先合同【签约日期】和【到期日期】下面的数据必须是日期格式&#xff0c;不能是其它的格式否则无法计算&#xff0c;如果是其它格式需要转换成标准的日期格式&#xff0c;如下图所示。 2、在“到期日期”下面的第一个单元格中输入公…

7 系列 FPGA 产品介绍及选型

目录 Spartan-7 FPGAsArtix-7 FPGAsKintex-7 FPGAsVirtex-7 FPGAsFPGA芯片命名规则DSP资源BRAM资源Transceivers 资源Transceivers 总带宽I/O 个数及带宽参考文档 Spartan-7 FPGAs Artix-7 FPGAs Kintex-7 FPGAs Virtex-7 FPGAs FPGA芯片命名规则 DSP资源 BRAM资源 Transceiver…

快速上手prometheaus grafana 监控

介绍 prometheaus 一个定时输出指标数据的巡检组件&#xff1f; grafana 一个读取指标&#xff0c;可视化的提供了好看界面的组件&#xff1f; 教程 如何和springboot项目集成 【IT老齐153】超级实用&#xff01;十分钟掌握Prometheus与Grafana监控SpringBoot应用_哔哩哔哩_…

100000订单直接拒掉,君子爱财,取之有道

近一个月询盘可谓寥寥无几&#xff0c;成交率为0&#xff0c;今天好不容易接了一个客户询盘&#xff0c;订单总价高达100000&#xff0c;听完细节直接拒掉&#xff0c;至于原因懂的都懂&#xff0c;不懂得等我慢慢道来。 前两天有2个询盘&#xff0c;其中一个是二次开发&#x…

【Python爬虫实战入门】:教你一个程序实现PPT模版自由

文章目录 &#x1f4a5;一、PPT模版爬取&#x1f525;1.1 第一个爬虫&#x1f6b2;1. 获取下载页面链接 ❤️1.2 第二个爬虫&#x1f6b2;1.3 第三个爬虫&#x1f388;2. 文件保存 ❤️1.4 翻页处理 &#x1f525;二、完整代码 &#x1f525;&#x1f525;&#x1f525; Pytho…

大腾智能网站正式上线

4月30日&#xff0c;我们翘首以盼的时刻终于来临&#xff0c;大腾智能官网&#xff08;www.da-teng.com&#xff09;正式上线&#xff0c;我们对此感到无比兴奋和自豪&#xff01;此刻&#xff0c;我们诚挚地邀请每一位朋友&#xff0c;共同踏上这个全新的数字之旅&#xff0c;…

从需求到实现的关键

版本封面 内容&#xff1a;产品logo&#xff0c;项目名称&#xff0c;所属公司&#xff0c;产品名称&#xff0c;文档类型&#xff0c;版本号&#xff0c;时间&#xff0c;相关人员&#xff08;最好说明下负责人&#xff09;。 作用&#xff1a; 突出重要信息&#xff0c;将…

【软考高项】三十八、风险管理7个过程

一、规划风险管理 1、定义、作用 定义&#xff1a;定义如何实施项目风险管理活动的过程作用&#xff1a;确保风险管理的水平、方法和可见度与项目风险程度相匹配&#xff0c;与对组织和其他干系人的重要程度相匹配 2、输入 项目管理计划 项目章程 项目文件 干系人登记册…

debian10 (armbian) 配置CUPS 服务

更新apt apt-update安装相关软件 apt-get install ghostscript apt-get install dc apt-get install foomatic-db-engine apt-get install cups3.修改配置文件 nano /etc/cups/cupsd.conf Listen localhost:631改为 Listen 0.0.0.0:631 以下四段配置加入Allow All # Only li…
最新文章