目录

  • 一、概述
    • 1、左值和右值
    • 2、引用
    • 3、左值引用和右值引用
      • 3.1 左值引用
      • 3.2 右值引用
  • 二、左值引用与右值引用详解
    • 1、左值引用详解
    • 2、右值引用详解

一、概述

1、左值和右值

左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;
右值当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器。

一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。

2、引用

引用是C++语法做的优化,引用的本质还是靠指针来实现的。引用相当于变量的别名。
引用可以改变指针的指向,还可以改变指针所指向的值。

引用的基本规则:

  • 声明引用的时候必须初始化,且一旦绑定,不可把引用绑定到其他对象;即引用必须初始化,不能对引用重定义;
  • 对引用的一切操作,就相当于对原对象的操作。

3、左值引用和右值引用

3.1 左值引用

 左值引用的基本语法:type &引用名 = 左值表达式;

3.2 右值引用

 右值引用的基本语法type &&引用名 = 右值表达式;右值引用在企业开发人员在代码优化方面会经常用到。右值引用的“&&”中间不可以有空格。

二、左值引用与右值引用详解

1、左值引用详解

先看一下传统的左值引用。

int a = 10;
int &b = a;  // 定义一个左值引用变量
b = 20;      // 通过左值引用修改引用内存的值

左值引用在汇编层面其实和普通的指针是一样的;定义引用变量必须初始化,因为引用其实就是一个别名,需要告诉编译器定义的是谁的引用。

int &var = 10;

上述代码是无法编译通过的,因为10无法进行取地址操作,无法对一个立即数取地址,因为立即数并没有在内存中存储,而是存储在寄存器中,可以通过下述方法解决:

const int &var = 10;

使用常引用来引用常量数字10,因为此刻内存上产生了临时变量保存了10,这个临时变量是可以进行取地址操作的,因此var引用的其实是这个临时变量,相当于下面的操作:

const int temp = 10;
const int &var = temp;

根据上述分析,得出如下结论:
左值引用要求右边的值必须能够取地址,如果无法取地址,可以用常引用;
但使用常引用后,我们只能通过引用来读取数据,无法去修改数据,因为其被const修饰成常量引用了。
那么C++11 引入了右值引用的概念,使用右值引用能够很好的解决这个问题。

2、右值引用详解

C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:

  • 可以取地址的,有名字的,非临时的就是左值; • 不能取地址的,没有名字的,临时的就是右值;
  • 可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值。

从本质上理解,创建和销毁由编译器幕后控制,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象)。

定义右值引用的格式如下:

类型 && 引用名 = 右值表达式;

右值引用是C++ 11新增的特性,所以C++ 98的引用为左值引用。右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。

int &&var = 10;

在汇编层面右值引用做的事情和常引用是相同的,即产生临时量来存储常量。但是,唯一的一点区别是,右值引用可以进行读写操作,而常引用只能进行读操作。

右值引用的存在并不是为了取代左值引用,而是充分利用右值(特别是临时对象)的构造来减少对象构造和析构操作以达到提高效率的目的。

用C++实现一个简单的顺序栈:

class Stack
{public:// 构造Stack(int size = 1000) :msize(size), mtop(0){cout << "Stack(int)" << endl;mpstack = new int[size];}// 析构~Stack(){cout << "~Stack()" << endl;delete[]mpstack;mpstack = nullptr;}// 拷贝构造Stack(const Stack &src):msize(src.msize), mtop(src.mtop){cout << "Stack(const Stack&)" << endl;mpstack = new int[src.msize];for (int i = 0; i < mtop; ++i) {mpstack[i] = src.mpstack[i];}}// 赋值重载Stack& operator=(const Stack &src){cout << "operator=" << endl;if (this == &src)return *this;delete[]mpstack;msize = src.msize;mtop = src.mtop;mpstack = new int[src.msize];for (int i = 0; i < mtop; ++i) {mpstack[i] = src.mpstack[i];}return *this;}int getSize() {return msize;}
private:int *mpstack;int mtop;int msize;
};Stack GetStack(Stack &stack)
{Stack tmp(stack.getSize());return tmp;
}int main()
{Stack s;s = GetStack(s);return 0;
}

运行结果如下:

Stack(int)             // 构造s
Stack(int)             // 构造tmp
Stack(const Stack&)    // tmp拷贝构造main函数栈帧上的临时对象
~Stack()               // tmp析构
operator=              // 临时对象赋值给s
~Stack()               // 临时对象析构
~Stack()               // s析构

为了解决浅拷贝问题,为类提供了自定义的拷贝构造函数和赋值运算符重载函数,并且这两个函数内部实现都是非常的耗费时间和资源(首先开辟较大的空间,然后将数据逐个复制),我们通过上述运行结果发现了两处使用了拷贝构造和赋值重载,分别是tmp拷贝构造main函数栈帧上的临时对象、临时对象赋值给s,其中tmp和临时对象都在各自的操作结束后便销毁了,使得程序效率非常低下。

那么我们为了提高效率,是否可以把tmp持有的内存资源直接给临时对象?是否可以把临时对象的资源直接给s?

在C++11中,我们可以解决上述问题,方式是提供带右值引用参数的拷贝构造函数和赋值运算符重载函数。

// 带右值引用参数的拷贝构造函数
Stack(Stack &&src):msize(src.msize), mtop(src.mtop)
{cout << "Stack(Stack&&)" << endl;/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/mpstack = src.mpstack;  src.mpstack = nullptr;
}
// 带右值引用参数的赋值运算符重载函数
Stack& operator=(Stack &&src)
{cout << "operator=(Stack&&)" << endl;if(this == &src)return *this;delete[]mpstack;msize = src.msize;mtop = src.mtop;/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/mpstack = src.mpstack;src.mpstack = nullptr;return *this;
}

运行结果如下:

Stack(int)             // 构造s
Stack(int)             // 构造tmp
Stack(Stack&&)         // 调用带右值引用的拷贝构造函数,直接将tmp的资源给临时对象
~Stack()               // tmp析构
operator=(Stack&&)     // 调用带右值引用的赋值运算符重载函数,直接将临时对象资源给s
~Stack()               // 临时对象析构
~Stack()               // s析构

程序自动调用了带右值引用的拷贝构造函数和赋值运算符重载函数,使得程序的效率得到了很大的提升,因为并没有重新开辟内存拷贝数据。

mpstack = src.mpstack;

可以直接赋值的原因是临时对象即将销毁,不会出现浅拷贝的问题,我们直接把临时对象持有的资源赋给新对象就可以了。

所以,临时量都会自动匹配右值引用版本的成员方法,旨在提高内存资源使用效率。
带右值引用参数的拷贝构造和赋值重载函数,又叫移动构造函数和移动赋值函数,这里的移动指的是把临时量的资源移动给了当前对象,临时对象就不持有资源,为nullptr了,实际上没有进行任何的数据移动,没发生任何的内存开辟和数据拷贝。

参考:
1、https://www.cnblogs.com/SZxiaochun/p/8017475.html
2、https://zhuanlan.zhihu.com/p/97128024

C++左值、右值、左值引用、右值引用的详解相关推荐

  1. amp 符号 php,php中引用符号(amp;)的使用详解_PHP教程

    与C语言中的指针是有差别的.C语言中的指针里面存储的是变量的内容在内存中存放的地址变量的引用. PHP 的引用允许你用两个变量来指向同一个内容 复制代码 代码如下: $a="ABC" ...

  2. android jni deletelocalref,JNI 引用, DeleteLocalRef使用场景详解

    局部引用: JNI 函数内部创建的 jobject对象及其子类( jclass. jstring. jarray等) 对象都是局部引用,它们在 JNI 函数返回后无效: 一般情况下,我们应该依赖 JV ...

  3. java父类引用子类属性_Java多态性详解——父类引用子类对象

    面向对象编程有三个特征,即封装.继承和多态. 封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据. 继承是为了重用父类代码,同时为实现多态性作准备.那么什么是多 ...

  4. 在mysql中删除数据后,添加数据,id值依然从删除的位置开始增加,问题详解。

    +----+--------------+-----+----------------------+----------+ | id | name | age | email | isActive | ...

  5. python元组的创建取值排序计数_Python元组 tuple的概念与基本操作详解【定义、创建、访问、计数、推导式等】...

    本文实例讲述了Python元组 tuple的概念与基本操作.分享给大家供大家参考,具体如下: 元组 tuple 元组 tuple的定义 元组的创建 元组的元素访问和计数 1. 元组的元素不能修改 2. ...

  6. python tkinter entry默认值_Python Tkinter Entry和Text的添加与使用详解

    输入框和消息显示框是常用的GUI界面部件,其中输入框的重要任务就是返回一个输入的字符串,而消息显示框则要显示指定的信息. 功能:输入一个密码,如果密码正确则显示confirm,不正确则显示sorry ...

  7. android实现左拉菜单,Android原生侧滑控件DrawerLayout使用方法详解

    在android的v4包中有一个控件 Drawerlayout,主要实现了左拉和右拉菜单,类似于之前的"抽屉"功能,此控件使用简单,效果很柔和,操作起来体验非常好,下面是我实现的一 ...

  8. 求旋转数组(左旋和右旋)的常用两个方法(详解)

    前言:首先我们要明白什么旋转数组?它包括左旋和右旋:我们不妨拿一个例子来解释,更加的容易理解:假设有一组数据:1 2 3 4 5 6 7 8 9: 如果是左旋1次=====>2 3 4 5 6 ...

  9. C++入门 “引用”,“内联函数” 详解

    目录 一.引用 1.引用的概念: 2.引用的格式: 3.引用的特性 4.取别名原则: 难点:隐式类型转换的引用 5.引用的使用场景: [1]做参数: [2]做返回值 (1)int& Count ...

  10. 秒懂Java之方法引用(method reference)详解

    [版权申明]非商业目的注明出处可自由转载 博文地址:https://blog.csdn.net/ShuSheng0007/article/details/107562812 出自:shusheng00 ...

最新文章

  1. HDU 2159 FATE【二维完全背包】
  2. python 使用set对list去重,并保持list原来顺序
  3. Android Studio导入github项目详解
  4. 自定义起始时间的时间戳计算(个人代码记录)
  5. BZOJ4066: 简单题
  6. maven 工程依赖了某个jar包 但还是报java.lang.ClassNotFoundException的问题
  7. 匹配正则_Day233:正则表达式匹配方法 match()
  8. javase模拟斗地主洗牌和发牌(54)
  9. 日期格式转换:String 与 date
  10. C语言之变量存储类型与链接属性
  11. weightedrandomSamplers(2)
  12. Linux如何搜索查找文件里面内容
  13. Linux内核同步原语之信号量(Semaphore)
  14. git远程代码回滚_git 远程分支回滚
  15. JVM Advent Calendar:JCP.NEXT,简化了JCP程序
  16. 【Unity】 HTFramework框架(三十九)UI的数据驱动模式,MVVM
  17. Recoil的简单实践和思考
  18. Xeam Visual Installer白金版,Xeam Visual Installer完整用户体验
  19. git 账号密码重置
  20. 测试用例的思路---矿泉水瓶

热门文章

  1. Python自定义函数的位置参数和关键字参数
  2. OpenWrt可以保证插件可用的AP模式设置
  3. java 微信小程序电影购票
  4. html如何给3种渐变色,css中颜色渐变的实现(三种方式)
  5. 基于javaweb+jsp的健身俱乐部管理系统(java+SSM+Mysql+Jsp)
  6. HttpClient工具类封装
  7. 2022亲测狮子鱼16.7.0小程序源码+独立版交易组件
  8. 超级计算机由,官方:现在的世界500强超级计算机由Linux
  9. 解决Spine骨骼混合动画错乱问题
  10. Jmeter官网下载Binaries和Source的区别