随着设计模式的学习和实践,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 <iostream>
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<<o1.a<<" "<<o1.b<<" "<<o2.a<<" "<<o2.b<<endl;
function(o1, o2);
cout<<o1.a<<" "<<o1.b<<" "<<o2.a<<" "<<o2.b<<endl;
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中不一样的,他是有自己的机制的。
- 大小: 10.3 KB
分享到:
相关推荐
引用也叫做别名,为一个变量起一个名字,二者代表同一地址,也代表同一变量。 int a=1; int &b=a;//b是a的一个变量,初始化为a 声明一个变量时必须对其进行初始化。 声明一个变量的引用后,在函数执行期间,该引用...
c++比起c来除了多了类类型外还多出一种类型:引用。这个东西变量不象变量,指针不象指针,我以前对它不太懂,看程序时碰到引用都稀里糊涂蒙过去。最近把引用好好地揣摩了一番,小有收获,特公之于社区,让初学者们...
C++引用—临时变量、引用参数和const引用 如果实参与引用参数不匹配,C++将生成临时变量。如果引用参数是const,则编译器在下面两种情况下生成临时变量: 实参类型是正确的,但不是左值 实参类型不正确,但可以转换为...
引用只是变量的别名,不需要分配内存空间。 2)引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL) 3)有多级指针,但是没有...
其中,第一篇是C++数据表达篇,主要内容包括:C++入门、变量和数据类型、运算符和表达式以及程序控制结构等;第二篇是C++面向过程设计篇,包括函数、编译预处理、数组、指针和构造数据类型等内容;第三篇是C++面向...
其中,第一篇是C++数据表达篇,主要内容包括:C++入门、变量和数据类型、运算符和表达式以及程序控制结构等;第二篇是C++面向过程设计篇,包括函数、编译预处理、数组、指针和构造数据类型等内容;第三篇是C++面向...
其中,第1篇是C++数据表达篇,包括C++入门、变量和数据类型、运算符和表达式以及程序控制结构等;第2篇是C++面向过程设计篇,包括函数、编译预处理、数组、指针和构造数据类型等内容;第3篇是C++面向对象编程篇,...
引用就是某一变量(目标)的一个别名,对引用的操作与对变量直 接 操作完全一样。 引用的声明方法:类型标识符 &引用名=目标变量名;
C++ 中引用与指针的区别实例详解 引用是从C++才引入的,在C中不存在。为了搞清楚引用的概念,得先搞明白变量的定义及引用与变量的区别,变量的要素一共有两个:名称与空间。 引用不是变量,它仅仅是变量的别名,...
9,引用变量 10,递归 11,预处理器 12,函数模板 13,程序组织结构 第二部分 使用类 14,C++类 15,构造函数和析构函数 16,转换 17,类对象数组 18,类成员 19,友元 20,类和堆 21,重载运算符 22,类库 23,类的...
什么是变量的引用 对一个数据可以使用“引用(reference)”,这是C++对C的一个重要扩充,引用是一种...声明变量b为引用类型,并不需要另外开辟内存单元来存放b的值。b和a占内存中的同一个存储单元,它们具有同一地址。声
C++中的引用&与取址&是很多初学者经常容易出错的地方,今天本文就对此加以分析总结,供大家参考之...其实他们的区别可以用一句话概括:和类型在一起的是引用,和变量在一起的是取址。下面我们通过实例具体了解一下 1)
9,引用变量 10,递归 11,预处理器 12,函数模板 13,程序组织结构 第二部分 使用类 14,C++类 15,构造函数和析构函数 16,转换 17,类对象数组 18,类成员 19,友元 20,类和堆 21,重载运算符 22,类库 23,类的...
主要介绍了C++中结构体的类型定义和初始化以及变量引用,是C++入门学习中的基础知识,需要的朋友可以参考下
9,引用变量 10,递归 11,预处理器 12,函数模板 13,程序组织结构 第二部分 使用类 14,C++类 15,构造函数和析构函数 16,转换 17,类对象数组 18,类成员 19,友元 20,类和堆 21,重载运算符 22,类库 23,类的...
其中,第1篇是c++数据表达篇,包括c++入门、变量和数据类型、运算符和表达式以及程序控制结构等;第2篇是c++面向过程设计篇,包括函数、编译预处理、数组、指针和构造数据类型等内容;第3篇是c++面向对象编程篇,...
数组(array)是相同类型变量的集合,可以使用共同的名字引用它。数组可被定义为任何类型,可以是一维或多维。数组中的一个特别要素是通过下标来访问它。数组提供了一种将有联系的信息分组的便利方法。 注意:如果...
2.4.1 C++的数据类型 2.4.2 容器=数据类型,标签=变量名 2.4.3 数据类型修饰符 2.4.4 问题分析:整型数据究竟有多大 2.5 C++中的变量声明 2.5.1 C++的命名规则 2.5.2 在哪里声明变量 2.6 C++中的运算符 2.6.1 计算...