一、前言
引用是C++引入的重要机制(C语言没有引用),它使原来在C中必须用指针来实现的功能有了另一种实现的选择,在书写形式上更为简洁。那么引用的本质是什么,它与指针又有什么关系呢?
二、引用的实现方式
引用被称为变量的别名,它不能脱离被引用对象独立存在,这是在高级语言层面的概念和理解,并未解释引用的实现方式。常见错误说法是“引用”自身不是一个变量,甚至编译器可以不以引用分配空间。
实际上,引用本身是一个变量,只不过这个变量的定义和使用与普通变量有显著的不同。为了解引用变量底层实现机制,考查如下代码:
int i=5;
int &ri=i;
ri=8;
在Visual Studio 2017环境的debug模式调试代码,反汇编查看源码对应的汇编代码的步骤是:调试->窗口->反汇编,即可得到如下原码对应的汇编代码:
int i=5;
00A013DE mov dword ptr [i],5 //将文字常量5送入变量i
int &ri=i;
00A013E5 lea eax,[i] //将变量i的地址送入寄存器eax
00A013E8 mov dword ptr [ri],eax //将寄存器的内容(也就是变量i的地址)送入变量ri
ri=8;
00A013EB mov eax,dword ptr [ri] //将变量ri的值送入寄存器eax
00A013EE mov dword ptr [eax],8 //将数值8送入以eax的内容为地址的单元中
return 0;
00A013F4 xor eax,eax
考查以上代码,在汇编代码中,ri的数据类型为dword,也就是说,ri要在内存中占据4个字节的位置。所以,ri的确是一个变量,它存放的是被引用对象的地址。由于通常情况下,地址是由指针变量存放的,那么,指针变量和引用变量有什么区别呢?使用指针常量实现上面的代码功能。考查如下代码:
int i=5;
int* const pi=&i;
*pi=8;
按照相同的方式,在VS2017中得都如下汇编代码:
int i=5;
011F13DE mov dword ptr [i],5
int * const pi=&i;
011F13E5 lea eax,[i]
011F13E8 mov dword ptr [pi],eax
*pi=8;
011F13EB mov eax,dword ptr [pi]
011F13EE mov dword ptr [eax],8
观察以上代码可以看出:
(1)除了pi与ri变量名不同,所得汇编代码与第一段所对应的汇编代码完全一样。所以,引用变量在功能上等于一个指针常量,即一旦指向某一个单元就不能在指向别处。
(2)在底层,引用变量由指针按照指针常量的方式实现。
三、总结
当我们直到了引用变量在底层就是按照指针常量的方式实现,我们就很容易实现以下区别点了:
1、引用在创建时必须初始化,引用到一个有效对象;而指针在定义时不必初始化,可以在定义后的任何地方重新赋值。
2、指针可以是NULL,引用不行
3、引用貌似一个对象的小名,一旦初始化指向一个对象,就不能将其他对象重新赋值给该引用,这样引用和原对象的值都会被更改。(即指针常量不可改变)。
4、引用的创建和销毁不会调用类的拷贝构造函数和析构函数。
四、引用设计的初衷是什么?
在C语言中,并没有引用这种设计,只有通过指针修改相应变量的值。
在我的理解中,引用设计的最大一个初衷就是实现简洁的代码书写。
当然,引用作为一个类似于指针常量的东西,在一定程度上保证了数据安全性问题(防止篡改指针),但这其实可以直接使用指针常量来实现,但是指针常量写起来很麻烦,所以这也可以归结到书写简洁的原因上。