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.mzph.cn/web/12051.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

334_C++_std::bind中使用shared_from_this()

std::bind(&HttpClient::getPwd, shared_from_this(), std::placeholders::_1, std::placeholders::_2);[ HttpClient继承自NetObj,NetObj是父类,NetObj受到std::shared_pt

分析 vs2019 c++ 中的 decltype 与 declval

&#xff08;1&#xff09; decltype 可以让推断其参数的类型。按住 ctrl 点击 decltype &#xff0c;会发现无法查阅 其定义 &#xff1a; &#xff08;2&#xff09; 但 STL 库里咱们可以查阅函数 declval 的 定义&#xff0c;很短&#xff0c;摘抄如下&#xff1a; templat…

【氮化镓】高温GaN HEMTs大信号模型——ASM-HEMT

这篇文章的标题是《An ASM-HEMT for Large-Signal Modeling of GaN HEMTs in High-Temperature Applications》&#xff0c;由Nicholas C. Miller等人撰写&#xff0c;发表于2023年9月29日。文章的主要内容是关于一种适用于高温应用的GaN HEMTs&#xff08;高电子迁移率晶体管&…

Java 高级面试问题及答案1

Java 高级面试问题及答案 问题1: 请解释Java中的垃圾回收机制&#xff0c;并描述其工作原理。 答案&#xff1a; Java中的垃圾回收&#xff08;Garbage Collection, GC&#xff09;是一种自动内存管理机制&#xff0c;用于识别和回收不再使用的对象&#xff0c;从而释放内存资…

使用System.Drawing绘制基本几何图形

1.使用System.Drawing绘制一个正方形 using System; using System.Drawing; using System.Windows.Forms;public partial class MyForm : Form {public MyForm(){// 你可以在这里设置Form的双缓冲&#xff0c;以避免绘制时出现的闪烁 this.DoubleBuffered true;}protected o…

LeetCode 每日一题 ---- 【1553.吃掉 N 个橘子的最少天数】

LeetCode 每日一题 ---- 【1553.吃掉 N 个橘子的最少天数】 1553.吃掉N个橘子的最少天数方法&#xff1a;记忆化搜索 1553.吃掉N个橘子的最少天数 方法&#xff1a;记忆化搜索 前两天给树浇水&#xff0c;原来浇的是橘子树哇 今天直接来了个大的【困难】 class Solution {Ma…

Linux——缓冲区

一、问题引入 我们先来看看下面的代码&#xff1a;我们使用了C语言接口和系统调用接口来进行文件操作。在代码的最后&#xff0c;我们还使用fork函数创建了一个子进程。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h&…

将jar打包成exe可安装程序,并在html页面唤醒

一、exe4j将jar打包成exe 1.exe4j下载 下载地址&#xff1a;https://www.ej-technologies.com/download/exe4j/files 2.exe4j打包jar 2.1. welcome 可以选择历史配置&#xff0c;新增则直接下一步 2.2. project type选择“jar in exe” mode 2.3. application info设置应用…

【接口测试_03课_-接口自动化思维梳理及Requests库应用】

一、通过代码&#xff0c;实现Jmeter 1、项目要放在虚拟环境里面&#xff0c;解释器要使用虚拟环境的 上面是虚拟环境&#xff0c;下面是系统环境。2选一 venv目录 查看当前虚拟环境已存在的依赖包 2、安装Requests依赖包 1&#xff09;安装命令 pip install requests 如果…

防火墙技术的演进,什么是下一代防火墙(NGFW)?

防火墙技术的演进 防火墙技术的演进经历了不同阶段&#xff0c;从包过滤防火墙到状态检测防火墙&#xff0c;再到集成多种安全功能的UTM&#xff08;统一威胁管理&#xff09;设备&#xff0c;最终发展到具备应用识别能力的NGFW&#xff08;下一代防火墙&#xff09;。 包过滤…

DTAS 尺寸公差分析及尺寸链计算-建模神器 — 用户DIY装配

工业互联网&#xff08;工业4.0) 是未来智能制造的核心&#xff0c;工业软件是智能制造的灵魂。 相关工业软件及系统的自主研发是智能制造和质量升级转型亟需解决的卡脖子环节&#xff0c;而公差分析软件系统是前期质量研发精准设计、降本增效的关键。 数字化时代&#xff0…

知了汇智副总经理赵懋骏出席“走进阿里”CEO联席会,共话AI大模型新趋势

在智能科技日新月异的今天&#xff0c;汇智知了堂副总经理赵懋骏于3月28日受邀出席了在天府软件园举行的“走进阿里–2024年CEO联席会”&#xff0c;会议聚焦阿里云AI技术的最新进展与行业应用&#xff0c;特别是“AI技术正在加速变革&#xff1a;大模型的历史、现在与趋势”&a…

手撕C语言题典——环形链表的约瑟夫问题

目录 前言 一.故事背景 二.题目 ​编辑三.思路 1&#xff09;数组 ​编辑2&#xff09; 循环链表 四.代码实现 搭配食用更佳哦~~ 数据结构之单单单——链表-CSDN博客 数据结构之单链表的基本操作-CSDN博客 前面学了单链表的相关知识&#xff0c;我们来尝试做一下关于…

centos 把nginx更新到最新版本

yum install epel-release # 添加 EPEL 软件仓库&#xff0c;这是 Nginx 官方软件仓库的依赖项 yum install yum-utils # yum-utils 包含了 yum-config-manager 工具&#xff0c;它可以让您轻松地启用、禁用或配置 yum 软件仓库 vi /etc/yum.repos.d/nginx.repo # 增加以下内容…

灌区信息化管理平台系统包含哪些内容?(全面介绍)

政策背景 2022年12月29日&#xff0c;水利部启动48处大中型灌区开展数字孪生灌区先行先试建设。 2023年2月24日&#xff0c;《2023年农村水利水电工作要点》:2023年农村水利水电工作的总体思路包括:紧盯保障国家粮食安全&#xff0c;加快推进大中型灌区现代化改造&#xff0c;…

Linux repo包安装Nginx

Linux repo包安装Nginx 1. 将nginx.repo 文件拷贝到 /etc/yum.repos.d 目录2.找到原来的NGINX配置文件打包备份3.执行Nginx安装命令4. 重启 nginx -s reload5. 查看Nginx版本 1. 将nginx.repo 文件拷贝到 /etc/yum.repos.d 目录 cp nginx.repo /etc/yum.repos.d2.找到原来的…

jQuery 入门:轻松创建与插入节点

在Web开发中&#xff0c;动态地创建和管理DOM&#xff08;文档对象模型&#xff09;节点是一项基本且强大的技能。jQuery&#xff0c;作为JavaScript的一个流行库&#xff0c;以其简洁的API简化了这一过程。本文将通过一个简单的示例&#xff0c;介绍如何使用jQuery来创建新的D…

【力扣一轮】链表-删除链表指定值的元素

删除链表指定元素 力扣链接 代码随想录题解 分为两个版本&#xff0c;一个是带有虚拟头节点&#xff0c;一个是不带。 无论是带有还是不带有&#xff0c;我都遇到了这几个问题&#xff1a; ①while循环时的判断&#xff0c;首先要判断当前节点是否为空&#xff0c;接着才能…

bmi088-linux驱动(I2C)

电气特性&#xff1a; 在正常工作时&#xff0c;gyro 工作电流为5mA&#xff0c;acc 工作电流为150uA。 SPI 时钟和数据电平范围 0 -3.6 结构框图如下&#xff1a; 硬件连接图如下&#xff1a; note&#xff1a; 1. 通过PS引脚选择通讯协议&#xff0c;上拉引脚则选择的是I2C…

系统定期执行命令的方法

系统定期执行命令的方法 一、进入超级用户下 执行命令&#xff1a;sudo su 二、添加要执行的命令 例子&#xff1a;每天0点执行一次myapp.sh命令 先后输入&#xff1a;crontab -e、 1、 回车 设置每天0点执行一次myapp.sh操作&#xff0c;需要写绝对路径 含义&#xff1…