C++的引用到底是什么?用了这么久,还不知道它居然也是个指针…
相关文章
C++编程之命名空间、const常量的总结
C++编程之运算符重载
前段时间写过一篇《C++编程之引用的详细总结》 ,看过就知道,哦,原来引用是对象/变量的一个别名,在使用时,是直接操作对象本体,因此通过引用传参,不需要拷贝内存,效率很高。但是最近有人私下问我:“你写的倒是挺全面的,但是引用的本质到底是什么?”
因此,今天决定再深入解释一下引用。
其实 引用的本质在C++内部实现是一个指针常量。C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小和指针相同,这个过程是编译器内部实现,用户不可见。
#include<iostream>
using namespace std;
// 编译器判断是引用,会将入参自动转换成int* const ref = &a;
void Test(int &ref)
{
ref = 10; // ref是引用,此处会转换为 *ref = 10;
}
int main()
{
int a = 100;
int & b = a; //自动转换为int* const b = &a;这就说明为什么引用必须要初始化。
b = 20; //编译器判断b是引用,自动转换为*b = 20;
Test(a);
return 0 ;
}
这就是引用为什么必须要初始化,因为内部转换为常指针时,需要拿到它的地址,所以必须要先初始化。
接着通过反汇编,验证两种情况是等价的。
使用引用传参
#include<iostream>
using namespace std;
void Test(int& a)
{
a = 100;
}
int main()
{
int b = 10;
Test(b);
cout << "b = " << b << endl;
return 0;
}
运行结果
断点调试,进入汇编
00F52AF8 mov eax,dword ptr [a] //dword表示的是双字,四字节。a中保存的是内存中的地址。将该地址处的4字节数据传送到eax中。
00F52AFB mov dword ptr [eax],64h //将64h的值传递给 [eax] 所指示的内存单元,也就是a的本体
如图显示断点到 a = 100 之前,a的值是10.,执行之后,如下图所示。
这样就通过操作引用,实际修改了本体的值。
使用常指针传参
#include<iostream>
using namespace std;
void Test1(int* const a)
{
*a = 1000;
}
int main()
{
int b = 10;
Test1(&b);
cout << "b = " << b << endl;
system("pause");
return 0;
}
运行结果
同样,断点调试到汇编中看看结果
001D2A98 mov eax,dword ptr [a] //a中保存的是内存中的地址。将该地址处的4字节数据传送到eax中。同样是双字节dwod,前面引用也是双字节,说明引用内部就是常指针。
001D2A9B mov dword ptr [eax],3E8h // 将3E8h的值传递给 [eax] 所指示的内存单元,也就是a的本体
如下图所示
当执行 a=1000 后,a的值也变为1000,如下图汇编代码:
因此,从汇编层看,引用在内部的确被转换为常指针。这就解释了引用必须要初始化的原因,初始化后,不能再修改指向。因此可以通过引用, 间接代替指针 ,比如一级指针可以直接用引用代替,二级指针可以用一级指针的引用代替,三级指针可以用二级指针的引用代替等。
总结完引用的本质,接着补充几个实际的例子。
数组的引用
void Test()
{
int arr[10];
int (&pArr)[10] = arr;
for(int i = 0; i< 10;i++)
{
arr[i]=i;
}
for(int i =0; i<10;i++)
{
cout<<pArr[i]<<endl;
}
}
输出结果:
0
1
2
3
...
9
指针的引用
#include<iostream>
using namespace std;
class Person
{
public:
int age;
}
// 使用二级指针给一级指针分配内存。可以通过指针的引用简化为一级指针,如下AllocSpace2
void AllocSpace1(Person **p)
{
*p = (Person*)malloc(sizeof(Person));
return;
}
// 使用指针的引用传参
void AllocSpace2(Person* &p)
{
p = (Person*)malloc(sizeof(Person));
return;
}
int main()
{
Person* p = NULL;
AllocSpace1(&p); // 取地址,传入二级指针
AllocSpace2(p); // 引用,直接传入一级指针本体。
return 0;
}
// 因此,可以使用引用简化指针,即二级指针可以用一级指针的引用代替,一级指针直接用引用代替,减少指针操作。