C++面试 左值、右值、左值引用、右值引用

2023-11-20

1、左值和右值

左值(left-values),缩写:lvalues  ,located value 可定位值,其含义是可以明确其存放地址的值,更确切说对其的使用是基于地址

右值(right-values),缩写:rvalues , read value 可读的值,通常指代赋值运算=右侧的常量值,字面值,或者函数的返回值,它们没有具体的指代名,即无法通过地址访问,通常在赋值表达式结束后变销毁。

 

一般可以认为:左值对应变量的地址,右值对应变量的值,首先说左值和右值,他们绝不是简单的等号左边和右边的区别,总结来说:

  •   左值可以寻址,而右值不可以。
  •   左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
  •   左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。
int value=fun();

最后c++11中还有一个将亡值的概念,是c++11中新增的跟右值引用相关的表达式,这样的表达式通常是将要被移动的对象


2、左值引用和右值引用

左值引用是对值的引用类型,用     T & a    来表示

右值引用是对值的引用类型,用     T &&a   来表示

左值引用和右值引用同为引用,他们在声明的同时必须被初始化(引用语法规定

不要混淆 取地址 和 引用,当&说明符前面带有类型声明,则是引用,否则就是取地址(必须是声明以及初始化),通俗来说 &在 ”=” 号左边的是引用,右边的是取地址。 

 

左值引用:也就是通常所述的引用,引用是原先左值的别名,在定义时完成初始化,并且不可以作变更。左值引用和左值共同使用内存中同一份内容数据

const int&a = 1 //常量左值引用是可以绑定右值的
const int a = 1 //从语法上讲后者的右值在表达时结束后就销毁了,而前者不会

函数在传值方式传递参数,以及返回函数值(例如返回类型为复杂类类型(class)),都会执行拷贝构造,将带来大量无畏的拷贝构造开销。

这就是我们尽量用const 引用代替值传递做函数参数的原因,它在某种程度上可以提高效率

 

右值引用:即(Move Semantics:移动语义),避免无意义的拷贝赋值操作,C++11提出右值引用的概念,其本质是接替右值的所有权(不销毁原先的内存内容,而是将所有权移交给被交付的对象。)。

int &&value = 1    ;//右值引用一个常量值
int &&value = fun();//右值引用一个临时的函数返回值
  • 相对于左值,右值的生命周期很短,如函数的临时返回值,可以安全的转移控制权
  • 将右值的资源不释放,而是采取右值引用的方式继续使用,减少大量的拷贝,复制带来的开销。
  • 编译器会默认开启返回值优化,解决重复对象构造问题,而采取右值引用可以语言层面实现。

C++11引入的右值引用带来了深刻的性能提升,改变了以往复制,拷贝的繁琐操作,转而采取更为智能的移动技术,其本质通过移交所有权,在不改变内存内容情况下,完成资源的转移。

例如源码STL中的,vector的扩容使用到了右值引用来提高效率:

当Vector的size增大到capacity的一定比例后,需要申请一片更大的内存空间,同时将原先的数据转移到新空间。在移动技术之前,这意味着大量元素的深拷贝,而采取移动技术,无需释放原先空间,而只需要将原先空间所有权交由给新空间创建者即可。

额外知识点深拷贝、浅拷贝:https://blog.csdn.net/u014430031/article/details/115383480

 总结生面两段话,左值引用和右值引用座位函数参数都能避免对象的拷贝和构造

但是我们通过右值引用改变一个右值时是没有意义的,而我们通过左值引用改变一个左值是有意义的。


3、Perfect Forwarding:完美转发(move、forward函数

实际上std::move就是一个类型转换器,将左值转换成右值而以。我们来看一下它的实现吧!

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_case<typename remove_reference<T>::type&&>(t);
}

通用引用

首先我们来看一下move的输入参数,move的输入参数类型称为通用引用类型。什么是通用引用呢?就是它既可以接收左值也可以接收右值。

代码中有两种类型的通用引用: 一种是auto,另一种是通过模板定义的T&&。实际上auto就是模板中的T,它们是等价的。

模板的类型推导

通用引用好强大呀!它既可以接收左值又可以接收右值,它是如何做到的呢?这就要讲讲模板的类型推导了。

代码中分为两种类型:基本类型引用类型

基本类型:不带地址的普通原始类型。

引用类型:存储的值是地址,指向存放数据的位置。

模板的类型推导规则还是蛮复杂的,这里我们只简要说明一下,有兴趣的同学可以查一下C++11的规范。我们还是举个具体的例子吧:

template <typename T>
void f(T param);  //第一类


//下面是传值的模板,由于传入参数的值不影响原值,所以参数类型退化为原始类型
int x = 10;         // x是int
const int& rx = x;  // rx是const int &
f(rx);              // T是int

对于第一类其推导时根据的原则是,函数参数传值不影响原值,所以无论你实际传入的参数是普通变量、常量还是引用,它最终都退化为不带任何修修饰的原始类型。

 

template <typename T>
void func(T& param); //第二类

template <typename T>
void function(T&& param); //第三类


//下面是传引用模板, 如果输入参数类型有引用,则去掉引用;如果没有引用,则输入参数类型就是T的类型
int x = 10;         // x是int
const int& rx = x;  // rx是const int &
func(x);            // T为int
func(rx);           // T为const int


//下面是通用引用模板,与引用模板规则一致
function(x);        // T为int&
function(5);        // T为int

第二类为模板类型为引用(包括左值引用和右值引用)或指针模板。这一类在类型推导时根据的原则是去除对等数量的引用符号,其它关键字照般。还是我们上面的例子,func(x)中x的类型为 int&,它与T&放在一起可以知道T为int。另一个例子function(x),其中x为int&它与T&& 放在一起可知T为int&

返回类型

我们来看一下move的remove_reference看着很陌生,接下来我们再分析一下remove_reference类,看它又起什么作用吧。其实,通过它的名子你应该也能猜个大概了,就是通过模板去除引用。我们来看一下它的实现吧。

template <typename T>
struct remove_reference{
    typedef T type;  //定义T的类型别名为type
};

template <typename T>
struct remove_reference<T&> //左值引用
{
    typedef T type;
}

template <typename T>
struct remove_reference<T&&> //右值引用
{
   typedef T type;
}

通过上面的代码我们可以知道,经过remove_reference处理后,T的引用被剔除了。假设前面我们通过move的类型自动推导得到T为int&&,那么再次经过模板推导remove_reference的type成员,这样就可以得出type的类型为int了。

remove_reference利用模板的自动推导获取到了实参去引用后的类型。现在我们再回过来看move函数的时候是不是就一目了解了呢?之前无法理解的5行代码现然变成了这样:

int && move(int&& && t){
    return static_case<int&&>(t);
}

//或
int && move(int& && t){
    return static_case<int&&>(t);
}

经上面转换后,我们看这个代码就清晰多了,从中我们可以看到move实际上就是做了一个类型的强制转换。如果你是左值引用就强制转换成右值引用。

引用折叠

上面的代码我们看起来是简单了很多,但其参数int& &&int && &&还是让人觉得很别扭。因为C++编译器根本就不支持这两种类型。咦!这是怎么回事儿呢?

查看一下引用折叠规则:

   
Expanded type Collapsed type
T& & T&
T& && T&
T&& & T&
T&& && T&&

总结一句话就是左值引用总是折叠为左值引用,右值引用总是折叠为右值引用。

 

forward的作用

std::forward被称为完美转发,它的作用是保持原来的属性不变。啥意思呢?通俗的讲就是,如果原来的值是左值,经std::forward处理后该值还是左值;如果原来的值是右值,经std::forward处理后它还是右值。

forward实现原理

要分析forward实现原理,我们首先来看一下forward代码实现。由于我们之前已经有了分析std::move的基础,所以再来看forward代码应该不会太困难。

……

template <typename T>
T&& forward(typename std::remove_reference<T>::type& param)
{
    return static_cast<T&&>(param);
}

template <typename T>
T&& forward(typename std::remove_reference<T>::type&& param)
{
    return static_cast<T&&>(param);
}

……

forward实现了两个模板函数,一个接收左值,另一个接收右值。在上面有代码中:

typename std::remove_reference<T>::type

的含义我们在分析std::move时已经向你做了说细的说明,其含义就是获得去掉引用的参数类型。所以上面的两上模板函数中,第一个是左值引用模板函数,第二个是右值引用模板函数。

紧接着std::forward模板函数对传入的参数进行强制类型转换,转换的目标类型符合引用折叠规则,因此左值参数最终转换后仍为左值,右值参数最终转成右值。

 

总结:

右值引用将左值与右值区分开来。它们可以帮助您通过消除不必要的内存分配和复制操作来提高应用程序的性能。它们还使您能够编写接受任意参数的函数的一个版本,并将其转发给另一个函数,就好像直接调用了另一个函数一样

 

 

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

C++面试 左值、右值、左值引用、右值引用 的相关文章

  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 没有强命名的代码签名是否会让您的应用程序容易被滥用?

    尝试了解authenticode代码签名和强命名 我是否正确地认为 如果我对引用一些 dll 非强命名 的 exe 进行代码签名 恶意用户就可以替换我的 DLL 并以看似由我签名但正在运行的方式分发应用程序他们的代码 假设这是真的 那么您似
  • 通过 CMIS (dotCMIS) 连接到 SP2010:异常未经授权

    我正在使用 dotCMIS 并且想要简单连接到我的 SP2010 服务器 我尝试用 C 来做到这一点 如下所示http chemistry apache org dotnet getting started with dotcmis htm
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • 用于检查类是否具有运算符/成员的 C++ 类型特征[重复]

    这个问题在这里已经有答案了 可能的重复 是否可以编写一个 C 模板来检查函数是否存在 https stackoverflow com questions 257288 is it possible to write a c template
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • 关于 C++ 转换:参数 1 从“[some_class]”到“[some_class]&”没有已知的转换

    我正在研究 C 并且遇到了一个错误 我不知道确切的原因 我已经找到了解决方案 但仍然想知道原因 class Base public void something Base b int main Base b b something Base
  • 带动态元素的 WPF 启动屏幕。如何?

    我是 WPF 新手 我需要一些帮助 我有一个加载缓慢的 WPF 应用程序 因此我显示启动屏幕作为权宜之计 但是 我希望能够在每次运行时更改屏幕 并在文本区域中显示不同的引言 这是一个生产力应用程序 所以我将使用非愚蠢但激励性的引言 当然 如
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 如何在 C 中调用采用匿名结构的函数?

    如何在 C 中调用采用匿名结构的函数 比如这个函数 void func struct int x p printf i n p x 当提供原型的函数声明在范围内时 调用该函数的参数必须具有与原型中声明的类型兼容的类型 其中 兼容 具有标准定
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 哪种 C 数据类型可以表示 40 位二进制数?

    我需要表示一个40位的二进制数 应该使用哪种 C 数据类型来处理这个问题 如果您使用的是 C99 或 C11 兼容编译器 则使用int least64 t以获得最大的兼容性 或者 如果您想要无符号类型 uint least64 t 这些都定
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现

随机推荐

  • ssh Forward X11

    参考链接 https www jianshu com p 24663f3491fa https www cnblogs com tsfh p 9022170 html https blog csdn net lvbian article d
  • Vue中修改浏览器图标Logo

    1 找到index html文件 2 修改这段代码中的图片 3 图标没有变化的话清除一下游览器缓存
  • linux查看根目录下所有文件夹大小的方法

    linux查看根目录下所有文件夹大小的方法如下 1 进入根目录 cd 2 使用命令 du sh 查看根目录下每个文件夹的大小3 进入占用空间比较大的文件夹 然后再使用2中命令查找大文件 原文地址 https zhidao baidu com
  • git的使用场景

    Git 是一种分布式版本控制系统 常用于管理软件开发过程中的代码 以下是 Git 的一些常见使用场景 代码版本控制 Git 可以跟踪代码的历史变更 并在必要时还原代码到以前的某个版本 分支管理 Git 可以创建多个分支 使得多个开发者能够并
  • 以太坊创始人Vitalik Buterin北京演讲:Casper与分片技术最新进展

    三言财经6月3日现场报道 在今天的以太坊技术及应用大会上 以太坊创始人Vitalik Buterin做了题为 Casper与分片技术最新进展 的主题演讲 V神在演讲中阐述了Casper和分片的技术流程 以及如何在系统中成为验证者验证节点 对
  • 中国中间件第一人---袁红岗

    最早开发Windows上的企业应用软件 打造独立知识产权的EJB服务器Apusus 很多JAVA程序员对袁红岗极其佩服 源于他做了很多人不敢想更不敢做的事情 这就是他打造了国产的EJB服务器 很快 金蝶将在国内推出自主产权EJB服务器的3
  • 小白也能快速学会的Micropython编译指南

    小白也能快速学会的Micropython编译指南 大家好 我是CSDN上的 上坂龍二 哦 今天给大家带来的是 如何快速一次成功地将Micropython和自己喜欢的模块编译进自己的Esp32固件中哦 事前准备 Python python的环
  • 【MySQL系列】--初识数据库

    个人主页 阿然成长日记 点击可跳转 个人专栏 数据结构与算法 C语言进阶 不能则学 不知则问 耻于问人 决无长进 文章目录 一 何为数据库 二 数据库的发展历程 三 数据库的分类 1 关系数据库 2 非关系型数据库 NoSQL 3 键值数据
  • 国军标 软件测评 静态分析常见问题总结

    违背国军标R x x x 禁止 define被重复定义 没有用 undef 解除前面的定义 违背国军标R 1 1 7 以函数形式定义的宏 参数和结果必须用括号括起来 违背国军标R 1 1 13 函数声明中必须对参数类型进行声明 并带有变量名
  • C/C++ n的阶乘 图解递归过程【简单易懂,代码可以直接运行】

    C C n的阶乘 简单易懂 代码可以直接运行 输入一个整数 n 请你编写一个函数 int fact int n 计算并输出 n 的阶乘 输入格式 共一行 包含一个整数 n 输出格式 共一行 包含一个整数表示 n 的阶乘的值 数据范围 1 n
  • 关于pom中mysql-connector-java的jar包引入高版本报错的解决过程

    如果你是类似下面的2点配置 1 jdbc properties文件 jdbc driver com mysql jdbc Driver jdbc url jdbc mysql localhost 3306 o2o characterEnco
  • python字典中如何添加键值对

    添加键值对 首先定义一个空字典 gt gt gt dic 直接对字典中不存在的key进行赋值来添加 gt gt gt dic name zhangsan gt gt gt dic name zhangsan 如果key或value都是变量也
  • Qt Win 10窗口毛玻璃效果

    直接看效果 标题 核心代码 HWND hWnd HWND winId HMODULE hUser GetModuleHandle L user32 dll if hUser pfnSetWindowCompositionAttribute
  • 网络编程知识

    网络编程知识 一 网络七层模型 OSI模型 OSI 模型 Open System Interconnection model 是一个由国际标准化组织 提出的概念模型 试图提供一个使各种不同的计算机和网络在世界范围内实现互联的标准框架 它将计
  • windows 远程ssh 登录linux 网络连接超时

    该方法适用于已经配置过的ssh服务 当电脑休眠 重启或关机再开机后windows ssh 远程登录ubuntu失败 此前都是正常使用 首先查看自己Ubuntu是否有网络 ifconfig一下 如果有 再去查看自己的ssh服务器是否开启 sy
  • 基于javaEE的图书管理系统

    极简的图书管理系统 无任何样式修饰 适合新手练手 图文并释 1 实现了用户注册 登录 图书的添加 修改 删除和修改操作 2 工具需要 eclipse mysql Tomcat 3 做系统之前在eclipse需要配置Tomcat服务器和导入m
  • Webpack 5 超详细解读(四)

    31 proxy 代理设置 为什么开发阶段需要设置代理 在开发阶段 我们需要请求后端接口 但是一般后端接口地址和我们本地的不在同一个服务中提供 这时进行访问就会存在跨域的问题 所以我们需要对我们的请求进行转啊操作 模拟跨域请求代码如下 ht
  • Java容器有哪些?哪些是同步容器,哪些是并发容器?

    Java容器有哪些 哪些是同步容器 哪些是并发容器 一 基本概念 容器集 同步容器 并发容器 二 Collection集合接口 List接口 LinkedList类 ArrayList类 Vector类 Stack类 Set接口 HashS
  • SQL报错——Incorrect column specifier for column ‘id‘

    自增 字段类型应该设置为int类型
  • C++面试 左值、右值、左值引用、右值引用

    1 左值和右值 左值 left values 缩写 lvalues located value 可定位值 其含义是可以明确其存放地址的值 更确切说对其的使用是基于地址 右值 right values 缩写 rvalues read valu