昨天看了C++primer的2.5节,引用变量,看完之后,对引用变量有一些疑惑,然后自己编写了一些测试程序,想搞清楚引用变量的底层到底做了些什么。下面,首先,我从C++ primer一书的2.5节内容讲起,再进行一些延伸。

一、C++ primer2.5节学习笔记:

注意(1)将普通的引用绑定到const对象是不合法的

  (2)const引用可以初始化为不同类型的对象或者初始化为右值,而非const引用引用不能初始化为右值

  原因在于:一般情况下,应该使非const引用指向同类型的变量,当指向的是不同类型的变量时,会出现以下情况:

例:

编写如下的测试程序:

 1 int main()
 2
 3 {
 4
 5         double dval = 3.14;
 6
 7         const int &ri = dval;
 8
 9         return 0;
10
11 }

如上图所示,能够编译成功,但是会显示这么一条warning,将double类型转化为const int类型,可能会loss of data。

首先,看dval存储的地址,是0x0012ff5c,而ri存储的地址是0x0012ff44,而引用所在的地址应该和其指向的变量的地址相同,出现这个问题就是因为引用和其指向的变量类型不同,实际上,编译器会将const int &ri = dval; 转化为如下代码:

1 int temp = dval;
2
3 const int &ri = temp;

所以ri引用指向的已经不是dval,而是这个过程中创建的一个临时变量temp,进行强制类型转化(转化为了3),这一点从内存中也能够看出,ri指向的变量地址是0x0012ff44,此处,存储着0x0000_0003,即一个整型变量(这里占了4个字节)

所以,如果ri不是const,那么就可以对ri进行改变,但是程序员的原意是通过ri的修改从而改变dval,但是这种情况下,改变ri的值不能改变dval,而只能改变temp(实际上没有这个变量名)的值,这和程序员的原意是违背的,所以C++标准中干脆将这种用法规定为不合法,下面是我们将const int&ri = dval;改成了int &ri = dval;后编译出现的情况:

从上图中可以看出,这种用法是不合法的。

二、探究C++的引用:

从一条语句的反汇编看起:

1 const int &ri = dval;    // const引用指向不同的类型

首先,dval的地址是在0x0012ff5c,这里的8字节数据存储着浮点数3.14,然后,观察const int &ri = dval;反汇编后的代码:

004113E7  fld        qword ptr [dval]      ;浮点协处理器指令004113EA  call      @ILT+200(__ftol2_sse) (4110CDh)      ; 应该调用函数是进行强制类型转化,结果存在eax寄存器中004113EF  mov       dword ptr [ebp-24h],eax     ; 将强制类型转化后的结果存在内存中; (可理解为C++ primer书中所说的产生了一个temp变量; 这个变量的地址就是ebp-24h)004113F2  lea       eax,[ebp-24h]                  ; 将所存结果的地址(lea)给eax寄存器004113F5  mov       dword ptr [ri],eax          ;即将上面所说的地址(不妨认为是temp变量的地址);给变量ri所在的内存区域

单步完这一步后,可以看到内存中有两处颜色变红了,表示在这一过程中内存中有两处进行了修改,根据上面的反汇编的分析,的确应该是这样,首先,是内存中的[ebp-24h]处(”temp变量“处)变成了0x0000_0003,然后是将这个临时变量的地址,即ebp-24h的值赋给了ri变量所在的内存区域。

实际上,将鼠标指针放在ebp的上面可以看到其十进制的值,如下所示

所以此时ebp = 1245032 = 0x0012ff68

ebp-24h = 0x0012ff44

所以ri变量处的内容应该是0x0012ff44

而ri变量的地址应该是0x0012ff50,从上面的内存图中可以看出,0x0012ff50在这一过程中变化了,并且内容是0x0012ff44,实际上,在反汇编的窗口中进行单步也能够看出这一点:

”temp变量“改变前:

”temp变量“改变后,ri变量改变前:

ri变量改变后:

从上面的三幅图中可以看出,我们前面的结论是正确的。

但是,在这里出现了一个问题,我们来查看Watch窗口,可以看到:

??????????????????????????

上面和我们前面按照反汇编理解的是不一样的,按照我们的理解,应该是:

ri:存储着被引用变量所在的地址(这里应该是”temp变量“的地址),即0x0012ff5c,

*ri:即0x0012ff5c存储单元的内容,即3

&ri: 即ri变量所在的地址,按照前面调试的结果,应该是0x0012ff50。

但是在Watch窗口中看到的结果是:

ri = 3

*ri = 3

&ri是temp变量的地址,0x0012ff5c

上面的这个问题先放着,先来看这个测试程序的最后两句,是两个普通的非引用变量的初始化,我们来看这部分的反汇编代码:

可以看到val1变量的初始化很简单,就是把一个常量5放到了内存中变量val1所在处,而val2的初始化也可理解为就是把变量val1的内容搬移到val2所在处,但是考虑到x86系列不能再两个存储器单元之间传送数据,所以这里需要通过一个寄存器eax,把val1中的内容搬到了val2中,注意到,这里用的汇编指令都是mov,从这里,联系前面的分析,就能看出引用变量和非引用变量进行初始化时的最重要区别,非引用变量初始化时,给这个变量开辟了一个内存空间,将等号右边的表达式结果搬移(mov)到了这个变量所在的内存单元处,而引用变量初始化时,也给这个变量开辟了内存空间,但是区别在于是将等号右边的相同类型变量(被引用变量)的地址赋给了这个引用变量(这是就非const变量或者const变量时的一种特殊情况讨论的,若是const变量,并且等号右边和引用变量不是同一类型时,则在中间要加上一步,就是创建一个临时变量(类型和引用变量相同),给这个临时变量开辟一段内存空间,然后把等号右边的表达式的结果存入这个临时变量中,接下来,再把这个临时变量的地址赋给这个引用变量)。

所以,从这一点上来看,引用完成的功能和指针很像,但是可能是C++必须要区分开引用这种用法,所以虽然从上面来看引用变量的类型实际上是一个指针类型,但是编译器可能不这样看,而是把在代码中出现的这个引用类型都翻译成引用类型的内容,所以在后面的程序中使用引用类型的时候,比如说改变引用ri的值的时候,实际上改变的是*ri(这里先把ri看成指针类型理解吧)的值,这可能也是前面在Watch窗口中查看时出现问题的原因。

上面这段话只是我的推测,在C++方面看的书还比较少

下面接着来看下面的语句的反汇编结果和执行情况:

1         const double &ri2 = dval;    // const引用指向相同的类型

执行后:

可以看出,const变量指向相同类型的变量时,实际上在内存中也开辟了一个空间,这个空间中放置着ri2变量,ri2变量的内容是一个地址,即被引用变量dval的地址,但是,如前所述,按照这种指针的方法理解的话,就会把引用变量ri2理解成了指针(其实从这个结果来看,ri2就是一个指针变量),所以,以后理解引用变量时,最好就把引用变量看成一个不占用任何内存空间的”变量“,其地址就是被引用变量的地址(仅仅就两者类型一致时,不一致时有”临时变量“),就好像引用变量是被引用变量的一个别名。

接着看下面的语句:

1 double &ri3 = dval;    // 非const引用指向相同的类型

这和上面的const引用指向相同的类型实际上是一样的。

结果截图如下:

最后,看const引用指向右值(常数)的情况:

1 const int &ri4 = 4;    // const引用指向右值(常数)

结果为:

实际上,这和const变量指向不同类型的情况是相同的。也存在着一个临时变量。

下面,对前面的分析进行一个总结:

一方面,按照反汇编分析的结果来看:

(1)如果引用变量指向的是相同类型的变量,那么,首先会为这个引用变量开辟一段内存空间(实际上这是在编译阶段就完成了),然后把被引用变量的地址放到这段内存空间中,即把被引用变量的地址赋给引用变量,

(2)如果引用变量初始化式右端的变量和引用变量是不同类型的,或者初始化式右边就是一个右式(这两种情况都只有const引用是合法的),这时,首先,将开辟一段内存空间,把初始化式右边的结果(可能需要强制类型转化等)放在这个内存空间中(可理解为定义了一个临时变量),然后再把这个临时变量对应的地址赋给引用变量。

但是前面已经讲了,按照反汇编的结果理解的话,可能会有将引用和指针混淆的疑惑,而实际上,在C++中使用引用时,也不需要知道这么多的底层细节,所以,可以这样来理解引用:

(1)如果引用变量指向的是相同类型的变量,那么这个引用变量就相当于这个被引用变量的一个别名,两者占用相同的内存空间,对引用变量做的任何改变也会改变相应的被引用变量。

(2)如果引用变量初始化式右端的变量和引用变量是不同类型的,或者初始化式右边就是一个右式(这两种情况都只有const引用是合法的),这时,首先,编译器会定义一个临时变量,把初始化式右边的结果(可能需要强制类型转化等)赋给这个临时变量,然后再将原引用指向这个类型相同的临时变量(这一步的过程和(1)相同)。

下面是写的这段测试程序的反汇编代码,放在这里方便查看:

最后,说明一下,这篇文章中很多都是我自己的看法,有些没在书上找到,不一定正确,如果有错,希望大家提醒!

转载于:https://www.cnblogs.com/mgpanpan/archive/2012/09/06/2672767.html

C++中引用变量的探究相关推荐

  1. sed在shell脚本中引用变量

    需求 在测试过程中,有很多配置项是写在文件中的,这时想通过脚本来改文件内容,这是个很常见的需求 问题 sed 是linux里很强大好用的文本修改工具,但是在使用过程中,需要在shell里的sed语句中 ...

  2. Python在字符串中引用变量

    Python在字符串中引用变量 在字符串中加入变量有三种方法: 1.+ 连字符 name = 'zhangsan' print('my name is '+name) #结果为 my name is ...

  3. Lambda表达式中引用变量的问题

    Lambda表达式中引用变量的问题 Lambda表达式内部自定义的变量肯定没问题.引用的外部final变量也没问题.问题在于effectively final变量的理解,及应用场景的认识.引用的外部变 ...

  4. C++中引用变量详解

    目录 一.什么是引用? 二.引用的注意事项 三.引用的本质 四.常量引用 五.引用的使用场景​​​​​​​ 六.引用和指针的区别 一.什么是引用? 引用实际上是给一个变量起别名,编译器不会为引用变量开 ...

  5. c++中引用变量的使用

    c++中引用是已定义的变量的别名,例如,如果把b作为a变量的引用,则可以交替使用a和b来表示该变量.引用的主要用途是作为函数的形参,通过将引用变量作为参数,函数将使用原始数据,而不是副本(把变量之间传 ...

  6. Python之在字符串中引用变量的4种方法

    在字符串中加入变量有三种方法: 1.+ 连字符 name = 'zhangsan' print('my name is '+name) #结果为 my name is zhangsan 2.% 字符 ...

  7. shell双引号中引用变量

    在shell中传入带双引号的变量 -d '{ "username":"user", "role":["role"] }' ...

  8. JMeter JSON 转义符中引用变量导致请求发送失败

    问题描述类似: https://blog.csdn.net/icecolour/article/details/73176118 在做jmeter发送json报文中存在转义字符的情况下例如:{&quo ...

  9. 动态改变标题_插入控件-gt;引用变量-gt;实现动态图表纵横筛选

    欢迎关注我的微信公众号:HR爱玩儿Excel和PPT,分享有趣又有逼格的Excel和PPT创意和技巧,emmm...不关注也没有关系...哼 昨晚没睡好,因为睡前老友跟我说了句话: 我们总部做的表没有 ...

  10. php在双引号中输出变量要加大括号,php中输出变量加大括号{}作用_PHP教程

    php输出变量加大括号,这是什么写法?看下面一段代码: 代码如下 header("Content-Type:text/html; charset=utf-8"); $test=&q ...

最新文章

  1. 1.cocos2dx内存管理和CCArray,CCMenuItem
  2. 一般php的入口在哪,PHP单入口是否是必须的
  3. hadoop-0.20.1+120 hive-0.3.99.1+0 试用hwi(hive web interface
  4. National Instruments实习心得
  5. [置顶]mybatis分页插件实现分页...
  6. 1081 检查密码 (15 分)—PAT (Basic Level) Practice (中文)
  7. 公共技术点之 Android 动画基础
  8. 2 抽象工厂模式(Abstract Factory)
  9. 圆柱体积怎么算立方公式_圆柱体体积计算公式?
  10. 火星开发板_数据科学家来自火星,软件开发人员来自金星
  11. 光敏电阻、光电二三极管及接收头
  12. 怎么更改网络选项为家庭计算机,我的现在是公用网络我想更改为家庭网络应该如何更改...
  13. python学习----登陆
  14. 7-5 判断某整数是正整数、负整数还是零 (6分)
  15. 离散数学学习笔记-2-群 子群
  16. STM32F103调试出现 while((RCC-CR RCC_CR_PLL2RDY) == 0) 死循环。
  17. LocalDate 获取英文月份
  18. 客户订单管理系统使用教程
  19. #计算机应用与技巧分享 #应用推荐 #录屏 Captura 免费开源的屏幕录制工具
  20. 医学主题词表(Medical Subject Headings, MeSH)

热门文章

  1. C++ - 使用copy函数打印容器(container)元素
  2. ActiveMQ官方文档翻译-内嵌消息中间件
  3. 让VMware低版本运行VMware高版本创建的虚拟机
  4. Linux时间操作(time、gettimeofday)
  5. 一个Java程序员对2011年的回顾
  6. AndroidStudio使用localMaven
  7. HashTable源码
  8. android开机自启动程序设置
  9. [图论] 树剖LCA
  10. python管理工具ports_采用python flask 开发如何管理 host port