1.左值和右值

1.1左值和右值定义

在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置。一般来说,右值是暂时和短暂的,而左值则存活的很久。如下例子

int var = 4;4 = var;  //error
(var + 10) = 4; //error

其中,赋值运算符要求一个lvalue作为它的左操作数,当然var是一个左值,因为它是一个占确定内存空间的对象。
常量4和表达式var+1都不是lvalue(它们是rvalue)。它们不是lvalue,因为都是表达式的临时结果,没有确定的内存空间(换句话说,它们只是计算的周期驻留在临时的寄存器中)。

1.2右值细分

右值又可以分为纯右值,和将亡值。
在c++98中,右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。

在c++11中对c++98进行了扩充,在c++11中右值又分为纯右值和将亡值,其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。

将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。


2.左值引用和右值引用

2.1左值引用

使用语法:类型 &表达式
左值引用就是对一个左值进行引用的类型。引用必须初始化,可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名。
例子

char c_val = 'c';
char *ptr = &c_val;
char &r_val = c_val;

不要混淆 取地址 和 引用,当&说明符前面带有类型声明,则是引用,否则就是取地址。

通俗来说 &在 ”=” 号左边的是引用,右边的是取地址。

2.2右值引用

使用语法 类型 &&表达式

int num = 10;
//int && a = num;  //右值引用不能初始化为左值,这里的num是一个左值
int && a = 10;
a = 100;
cout << a << endl;
// 输出结果为100,右值引用可以对右值修改

右值引用通常用于移动语义和完美转发。
其中,c++移动语义可以参考这篇文章,C++11的移动语义。


3.万能引用和完美转发

3.1万能引用

首先来看一个例子

#include <iostream>using std::cout;
using std::endl;template<typename T>
void func(T& param) {cout << param << endl;
}int main() {int num = 2019;func(num);return 0;
}

这里的编译输出都没有问题,但是我们修改为如下方式

int main() {func(2019);return 0;
}

编译时会出错,因为func传入了一个右值,而他只接受左值或者是左值引用。这里我们重载一个可以接受右值的函数模板,可以实现我们想要的效果

template<typename T>
void func(T& param) {cout << "传入的是左值" << endl;
}
template<typename T>
void func(T&& param) {cout << "传入的是右值" << endl;
}int main() {int num = 2019;func(num);func(2019);return 0;
}

输出为

传入的是左值
传入的是右值

一次函数调用的是左值得版本,第二次函数调用的是右值版本。但是,有没有办法只写一个模板函数即可以接收左值又可以接收右值呢?

C++ 11中有万能引用(Universal Reference)的概念:使用T&&类型的形参既能绑定右值,又能绑定左值。

但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。类型推导阅读链接

所以,上面的案例我们可以修改为:

template<typename T>
void func(T&& param) {cout << param << endl;
}int main() {int num = 2019;func(num);func(2019);return 0;
}

3.2引用折叠

万能引用说完了,接着来聊引用折叠(Reference Collapse),因为完美转发(Perfect Forwarding)的概念涉及引用折叠。一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:

  • 左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
  • 左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
  • 右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
  • 右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用

但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:

所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。

即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。

// ...
int a = 0;
int &ra = a;
int & &rra = ra;  // 编译器报错:不允许使用引用的引用!
// ...

既然不允许使用,为啥还要有引用折叠这样的概念存在 ?!

原因就是:引用折叠的应用场景不在这里!!

下面我们介绍引用折叠在模板中的应用:完美转发

参考阅读,引用折叠与完美转发

3.3完美转发

那么,什么情况下,我们需要一个函数,既能接收左值,又能接收右值呢?

答案就是:转发的时候。

首先来看一个例子

#include <iostream>using std::cout;
using std::endl;template<typename T>
void func(T& param) {cout << "传入的是左值" << endl;
}
template<typename T>
void func(T&& param) {cout << "传入的是右值" << endl;
}template<typename T>
void warp(T&& param) {func(param);
}int main() {int num = 2019;warp(num);warp(2019);return 0;
}

猜一下上述的输出

传入的是左值
传入的是左值

是不是和我们预期的不一样,下面我们来分析一下原因:

warp()函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个warp()函数调用实参是左值,所以,warp()函数中调用func()中传入的参数也应该是左值;第二个warp()函数调用实参是右值,根据上面所说的引用折叠规则warp()函数接收的参数类型是右值引用,那么为什么却调用了调用func()的左值版本了呢?这是因为在warp()函数内部,左值引用类型变为了右值,因为参数有了名称,我们也通过变量名取得变量地址。这里为什么在内部变了,可以参考3.3中的链接,里面有详细的解释。

所以说,会存在一些特殊的情况使得我们的右值在某些函数的内部变为了左值,而我们为什么需要完美转发,就是想要让传入的右值在整个流程中都保持右值的状态不改变。那么如何实现呢?这就是完美转发技术。在c++11中通过std::forward()函数实现,于是我们修改warp函数

template<typename T>
void warp(T&& param) {func(std::forward<T>(param));
}

既可以得到我们想要的结果。

[c++]-c++中的左值和右值、左值引用和右值引用、万能引用和引用折叠及完美转发相关推荐

  1. C++ 中的万能引用、引用折叠、完美转发

    在学习c++过程中,相信不少同学都或多或少的听过万能引用.引用折叠.完美转发,介绍这几个概念之前,首先列举下左值和右值的概念: 左值和右值: 顾名思义,可以简单的理解为在等号左边的值是左值,再等号右边 ...

  2. 详解 C++ 左值、右值、左值引用以及右值引用

    文章目录 一.左值和右值 1.左值 2.右值 3.总结 二.左值引用和右值引用 1.左值引用 2.右值引用 3.对比与总结 三.左值引用的使用场景及实际意义 1.使用场景 2.实际意义 3.短板 四. ...

  3. 移动语义-右值引用-完美转发-万字长文让你一探究竟

    C++ 右值引用 block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38 ...

  4. C++11右值引用、移动语义、完美转发详解

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  5. 【C++】右值引用、移动语义、完美转发(下篇)

    上篇中,主要讲解了右值引用和移动语义的具体定义和用法.在C++11中几乎所有的容器都实现了移动语义,以方便性能优化.本文以C++11容器中的insert方法为例,详细讲解在容器中移动语义是如何提高性能 ...

  6. c 表达式必须是可修改的左值_C++中的左值,右值,左值引用,右值引用

    童帅 2020-2-22 文中的"表达式"都是指赋值表达式 左值,右值,左值引用,右值引用 到底是什么 左值和右值 int a = 10; int b = 5; int c = a ...

  7. C++/C++11中左值、左值引用、右值、右值引用的使用

    C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...

  8. 左值、右值、左值引用、右值引用

    1. 左值 左值(lvalue,left value),顾名思义就是赋值符号左边的值,可以取地址.准确来说,左值是表达式(不一定是赋值表达式)后依然存在的持久对象. 可以将左值看作是一个关联了名称的内 ...

  9. 的引用_左值、右值、左值引用、右值引用

    [导读]:本文主要详细介绍了左值.右值.左值引用.右值引用以及move.完美转发. 左值和右值 左值(left-values),缩写:lvalues 右值(right-values),缩写:rvalu ...

最新文章

  1. 双方互GAN,不如来试试群体博弈?更快更强更自由 | ICLR 2021
  2. python中ThreadLocal的理解与使用
  3. java 11-7String类里的方法的一些案例
  4. java 10 发布_Java 10 发布之后,大多数受访者仍在使用 Java 8(82%)
  5. python基础教程:两个list之间移动元素
  6. #翻译# 关于 Java 性能方面的 9 个谬论
  7. 【华为2016上机试题C++】最高分是多少
  8. linux上还原自主nuget包需要注意的问题
  9. 配置了坐标还是找不到serv_为什么老人家总是这疼那疼,还找不到原因?是矫情还是另有原因...
  10. Wpf中通过绑定来更新textbox的值
  11. n进制快速转化成10进制
  12. 多并发编程基础 之进程 Process
  13. 关于学习的反思(上)---系网开发记(2)
  14. Ubuntu 20.04 虚拟显示器 1080P 配置
  15. 010Editor逆向及注册机实现
  16. android 手机2k分辨率,2K屏幕手机有哪些 2016六款2K分辨率手机推荐
  17. 获取iv和encryptedData
  18. python--基础知识点--继承、多态、鸭子类型
  19. python实现C4.5
  20. windows下AV1的编译

热门文章

  1. Puma560机器人运动学正逆解
  2. Mysql通过ibd恢复误删表数据
  3. linux 学习笔记之atomic
  4. Mysql事务原理详解
  5. Ubuntu 18.04 LTS 永久路由配置指导
  6. 人脸检测(图像处理)
  7. rust 局域网联机_腐蚀怎么搭建服务器联机 游戏联机方法一览
  8. 简述mysql字符集选择方法_MySQL字符集选择
  9. 17、Java中的面向对象的编程思想
  10. OpenCL编程指南-4.1OpenCL C编程