随着设计模式的学习和实践,C++中引用的使用愈发平凡。但是C++中引用类型变量到底是什么东西,这种变量与Java C#中的引用值有什么区别和联系,直到今日才有所了解。这一切都出自于一次偶然的发现。
由于过去长期使用Java这种没有指针的语言,其引用值的概念早就深入人心。
我们知道,当有如下代码时,其实相当于什么都没做。
- void function (Object o1, Object o2)
- {
- Object temp;
- temp=o1;
- o1=o2;
- o2=temp;
- }
这是因为传入函数的是引用值的关系,换句话说,这o1也好o2也罢,这都是指向内存空间的地址值。如果对于这个概念不太理解的话可以继续查阅Java中Passed by value的概念。
在使用过程中,我把这种概念也强加于C++中的引用值中,最后发生了悲剧的事情。
- #include
- using namespace std;
-
- class Object
- {
- public:
- Object(int value1, int value2):a(value1), b(value2) {}
- int a;
- int b;
- };
-
- int main()
- {
- Object o1(1,2);
- Object o2(3,4);
- cout<" "<" "<" "<
- function(o1, o2);
- cout<" "<" "<" "<
- return 0;
- }
-
- void function (Object& o1, Object& o2)
- {
- Object temp=o1;
- o1=o2;
- o2=temp;
- }
输出结果是
3 4 1 2
那么如果我将函数function做如下修改呢
- void function (Object& o1, Object& o2)
- {
- Object& temp=o1;
- o1=o2;
- o2=temp;
- }
结果是:
3 4 3 4
这个结果在开始时让我大吃一惊,我反复分析最中还是失败了,我当时这样想的
如果这个变量我把它解释成像Java中的引用值,那么在第一次输出结果时应该是不会变的。
在Java中就是不变的输出结果。但是如果不解释成引用值又说不通,因为我很明确这肯定是个指针。我始终无法说服自己。最后我反汇编了代码,看看它到底做了什么。
首先是第一种情况
- function(o1, o2);
- 00411587 lea eax,[o2]
- 0041158A push eax
- 0041158B lea ecx,[o1]
- 0041158E push ecx
- 0041158F call function (411023h)
- 00411594 add esp,8
这说明它的确是把对象的内存空间压入了栈中,即传给了函数
- void function (Object& o1, Object& o2)
- {
- 00411770 push ebp
- 00411771 mov ebp,esp
- 00411773 sub esp,0D0h
- 00411779 push ebx
- 0041177A push esi
- 0041177B push edi
- 0041177C lea edi,[ebp-0D0h]
- 00411782 mov ecx,34h
- 00411787 mov eax,0CCCCCCCCh
- 0041178C rep stos dword ptr es:[edi]
- Object temp=o1;
- 0041178E mov eax,dword ptr [o1]
- 00411791 mov ecx,dword ptr [eax]
- 00411793 mov edx,dword ptr [eax+4]
- 00411796 mov dword ptr [temp],ecx
- 00411799 mov dword ptr [ebp-8],edx
- o1=o2;
- 0041179C mov eax,dword ptr [o2]
- 0041179F mov ecx,dword ptr [eax]
- 004117A1 mov edx,dword ptr [eax+4]
- 004117A4 mov eax,dword ptr [o1]
- 004117A7 mov dword ptr [eax],ecx
- 004117A9 mov dword ptr [eax+4],edx
- o2=temp;
- 004117AC mov eax,dword ptr [o2]
- 004117AF mov ecx,dword ptr [temp]
- 004117B2 mov dword ptr [eax],ecx
- 004117B4 mov edx,dword ptr [ebp-8]
- 004117B7 mov dword ptr [eax+4],edx
- }
这段汇编中
1. 开了新的内存空间给temp,并且把o1内存中的所有东西传给了temp。
其实在写这段代码之前我怀疑是否能编译通过,因为在我的分析下这两者属于不同的类型,but it did.
我们再来看看结果为3434的那段函数汇编
- void function (Object& o1, Object& o2)
- {
- 00411770 push ebp
- 00411771 mov ebp,esp
- 00411773 sub esp,0CCh
- 00411779 push ebx
- 0041177A push esi
- 0041177B push edi
- 0041177C lea edi,[ebp-0CCh]
- 00411782 mov ecx,33h
- 00411787 mov eax,0CCCCCCCCh
- 0041178C rep stos dword ptr es:[edi]
- Object& temp=o1;
- 0041178E mov eax,dword ptr [o1]
- 00411791 mov dword ptr [temp],eax
- o1=o2;
- 00411794 mov eax,dword ptr [o2]
- 00411797 mov ecx,dword ptr [eax]
- 00411799 mov edx,dword ptr [eax+4]
- 0041179C mov eax,dword ptr [o1]
- 0041179F mov dword ptr [eax],ecx
- 004117A1 mov dword ptr [eax+4],edx
- o2=temp;
- 004117A4 mov eax,dword ptr [temp]
- 004117A7 mov ecx,dword ptr [eax]
- 004117A9 mov edx,dword ptr [eax+4]
- 004117AC mov eax,dword ptr [o2]
- 004117AF mov dword ptr [eax],ecx
- 004117B1 mov dword ptr [eax+4],edx
- }
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完全不存在
阅读(1850) | 评论(0) | 转发(0) |