现在的位置: 首页 > 综合 > 正文

关于《关于C++引用类型变量》

2018年03月30日 ⁄ 综合 ⁄ 共 5151字 ⁄ 字号 评论关闭

在JAVAEYE博客中看到《关于C++引用类型变量》一文,原文地址http://tinggo.javaeye.com/blog/755793

 

随着设计模式的学习和实践,C++中引用的使用愈发平凡。但是C++中引用类型变量到底是什么东西,这种变量与Java C#中的引用值有什么区别和联系,直到今日才有所了解。这一切都出自于一次偶然的发现。 
由于过去长期使用Java这种没有指针的语言,其引用值的概念早就深入人心。 
我们知道,当有如下代码时,其实相当于什么都没做。

Java代码 
  1. void function (Object o1, Object o2)  
  2. {  
  3.     Object temp;  
  4.     temp=o1;  
  5.     o1=o2;  
  6.     o2=temp;  
  7. }  

这是因为传入函数的是引用值的关系,换句话说,这o1也好o2也罢,这都是指向内存空间的地址值。如果对于这个概念不太理解的话可以继续查阅Java中Passed by value的概念。 

在使用过程中,我把这种概念也强加于C++中的引用值中,最后发生了悲剧的事情。 

C++代码 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Object  
  5. {  
  6. public:  
  7.     Object(int value1, int value2):a(value1), b(value2) {}  
  8.     int a;  
  9.     int b;  
  10. };  
  11.   
  12. int main()  
  13. {  
  14.     Object o1(1,2);  
  15.     Object o2(3,4);  
  16.     cout<<o1.a<<" "<<o1.b<<" "<<o2.a<<" "<<o2.b<<endl;  
  17.     function(o1, o2);  
  18.     cout<<o1.a<<" "<<o1.b<<" "<<o2.a<<" "<<o2.b<<endl;  
  19.     return 0;  
  20. }  
  21.   
  22. void function (Object& o1, Object& o2)  
  23. {  
  24.     Object temp=o1;  
  25.     o1=o2;  
  26.     o2=temp;  
  27. }  

输出结果是 
3 4 1 2 

那么如果我将函数function做如下修改呢 

C++代码 
  1. void function (Object& o1, Object& o2)  
  2. {  
  3.     Object& temp=o1;  
  4.     o1=o2;  
  5.     o2=temp;  
  6. }  

结果是: 
3 4 3 4 
这个结果在开始时让我大吃一惊,我反复分析最中还是失败了,我当时这样想的 
如果这个变量我把它解释成像Java中的引用值,那么在第一次输出结果时应该是不会变的。 
在Java中就是不变的输出结果。但是如果不解释成引用值又说不通,因为我很明确这肯定是个指针。我始终无法说服自己。最后我反汇编了代码,看看它到底做了什么。 
首先是第一种情况 

汇编代码 
  1.     function(o1, o2);  
  2. 00411587  lea         eax,[o2]    
  3. 0041158A  push        eax    
  4. 0041158B  lea         ecx,[o1]    
  5. 0041158E  push        ecx    
  6. 0041158F  call        function (411023h)    
  7. 00411594  add         esp,8    

这说明它的确是把对象的内存空间压入了栈中,即传给了函数 

汇编代码 
  1. void function (Object& o1, Object& o2)  
  2. {  
  3. 00411770  push        ebp    
  4. 00411771  mov         ebp,esp    
  5. 00411773  sub         esp,0D0h    
  6. 00411779  push        ebx    
  7. 0041177A  push        esi    
  8. 0041177B  push        edi    
  9. 0041177C  lea         edi,[ebp-0D0h]    
  10. 00411782  mov         ecx,34h    
  11. 00411787  mov         eax,0CCCCCCCCh    
  12. 0041178C  rep stos    dword ptr es:[edi]    
  13.     Object temp=o1;  
  14. 0041178E  mov         eax,dword ptr [o1]    
  15. 00411791  mov         ecx,dword ptr [eax]    
  16. 00411793  mov         edx,dword ptr [eax+4]    
  17. 00411796  mov         dword ptr [temp],ecx    
  18. 00411799  mov         dword ptr [ebp-8],edx    
  19.     o1=o2;  
  20. 0041179C  mov         eax,dword ptr [o2]    
  21. 0041179F  mov         ecx,dword ptr [eax]    
  22. 004117A1  mov         edx,dword ptr [eax+4]    
  23. 004117A4  mov         eax,dword ptr [o1]    
  24. 004117A7  mov         dword ptr [eax],ecx    
  25. 004117A9  mov         dword ptr [eax+4],edx    
  26.     o2=temp;  
  27. 004117AC  mov         eax,dword ptr [o2]    
  28. 004117AF  mov         ecx,dword ptr [temp]    
  29. 004117B2  mov         dword ptr [eax],ecx    
  30. 004117B4  mov         edx,dword ptr [ebp-8]    
  31. 004117B7  mov         dword ptr [eax+4],edx    
  32. }  

这段汇编中 
1. 开了新的内存空间给temp,并且把o1内存中的所有东西传给了temp。 
其实在写这段代码之前我怀疑是否能编译通过,因为在我的分析下这两者属于不同的类型,but it did. 
我们再来看看结果为3434的那段函数汇编 

汇编代码 
  1. void function (Object& o1, Object& o2)  
  2. {  
  3. 00411770  push        ebp    
  4. 00411771  mov         ebp,esp    
  5. 00411773  sub         esp,0CCh    
  6. 00411779  push        ebx    
  7. 0041177A  push        esi    
  8. 0041177B  push        edi    
  9. 0041177C  lea         edi,[ebp-0CCh]    
  10. 00411782  mov         ecx,33h    
  11. 00411787  mov         eax,0CCCCCCCCh    
  12. 0041178C  rep stos    dword ptr es:[edi]    
  13.     Object& temp=o1;  
  14. 0041178E  mov         eax,dword ptr [o1]    
  15. 00411791  mov         dword ptr [temp],eax    
  16.     o1=o2;  
  17. 00411794  mov         eax,dword ptr [o2]    
  18. 00411797  mov         ecx,dword ptr [eax]    
  19. 00411799  mov         edx,dword ptr [eax+4]    
  20. 0041179C  mov         eax,dword ptr [o1]    
  21. 0041179F  mov         dword ptr [eax],ecx    
  22. 004117A1  mov         dword ptr [eax+4],edx    
  23.     o2=temp;  
  24. 004117A4  mov         eax,dword ptr [temp]    
  25. 004117A7  mov         ecx,dword ptr [eax]    
  26. 004117A9  mov         edx,dword ptr [eax+4]    
  27. 004117AC  mov         eax,dword ptr [o2]    
  28. 004117AF  mov         dword ptr [eax],ecx    
  29. 004117B1  mov         dword ptr [eax+4],edx    
  30. }  

HoHO,奇迹发现了。 
现在将这一切解释一下。 
 
按照上图我们发现,其实对象是由一个指针维护着,对象的名字便是这个指针,这个概念很好理解,Java中也是这样,我们在学校的时候也是这样学的。但是引用值我们的老师告诉我们这是个别名。但是这个解释太具有中国式的含蓄,这也是为什么造成今日我的迷糊。其实引用也是个指针,他指向上面所说的那个指针,正如图所示。 
好,他们之间的结构已经说清楚了,那为什么还会出现如此诡异的事呢?有人会问还诡异在哪里? 
为什么在代码 
Object& temp=o1; 
o1=o2; 
o2=temp; 
执行完后结果会是3434呢?有人会解释说因为他是指针,交换的结果是指针互相对冲,使得最终都是那个指向o2的地址。 
如果按照这个理论的话我们尝试解释 
Object temp=o1; 
o1=o2; 
o2=temp; 
首先必须申明o1是引用值,即他也是指向那段内存空间指针的指针。你不觉得一个指向指针的指针赋值给一个指向对象的指针,这好像很怪吗? 
其实这一切的一切罪魁祸首是诡异的引用的赋值运算符。 
他在引用使用它时会做点我们看不见的事,简单而言是这样的。 
Object& temp=o1(引用赋值给引用) 结果是:使得两个引用值拥有相同的地址值 
Object temp=o1 (引用赋值给对象) 结果是:引用将其指向的对象的内存空间完全赋值给新的对象 
o1= Object temp(对象给引用赋值) 结果是:这是新建一个引用的常用语法,不解释 
对象赋值给对象略 
简而言之,引用的赋值运算符,不是简单的内存空间之间的交换,他会做出语义判断来决定到底是将地址值给对方呢,还是将指向的目标给对方,还是把指向目标的目标给对方。 

其实如果C++的引用值做的和Java一样,那也就不会有这样的问题。 

最后总结一下 
1. C++的引用这个变量是一个地址 
2. 引用之间的赋值运算是和Java中不一样的,他是有自己的机制的。 

觉得关于引用赋值给引用那里,觉得有些不太准确,或者是我没完全理解他的意思

把我的想法说出来大家探讨一下

关于引用中就是对象变量的地址这一点,在实现上应该是这样

但是:

引用赋值给引用,我觉得不是简单的使两个引用值拥有相同的地址值

否则汇编中应该不是这样一个结果

而且还是解释不通第二个输出为3434,因为函数中o1=o2也是引用赋值给引用


C++中的引用,有一点很重要:就是操作引用,就是操作引用所指对象本身,或许这也就是“引用==别名”的由来


所以函数中的o1(引用)=o2(引用)就相当于o1(对象)=o2(对象),也就是说是把o2对象内存空间的值copy给o1.


那么Object &temp =o1又怎么解释汇编呢?

因为这是一个引用的定义,相当于初始化

基于上面那点Object &temp =o1(引用)就相当于Object &temp =o1(对象),普通的引用的定义

此时操作temp也等同于操作o1了


我觉得说到这里应该就可以解释这个结果了

实际上第二个结果出现的过程就相当于是

{

   o1.a=o2.a

   o1.b=o2.b

   o2.a=o1.a

   o2.b=o1.b

}

就相当于temp完全不存在

抱歉!评论已关闭.