page contents

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;
}

// 因此,可以使用引用简化指针,即二级指针可以用一级指针的引用代替,一级指针直接用引用代替,减少指针操作。

常量的引用
如何定义常量的引用,如下代码解释

void Test()
{
	//int &a = 10; //非法操作,无法给非常量的引用赋值
	int const &b = 10; //合法操作,编译器在内部会做类似的转换。int temp=10; int const &b = temp;
}

常量的引用的应用场景是什么呢?

void Test1(int &b)
{
	cout << "b = " << b << endl;
}

int main()
{
	int a = 100;
	Test1(a);
	return 0;
}

如上代码所示,使用常量作为参数,在函数内部不会开辟新内存,节省内存空间。但是,如果在 Test1 中做如下操作,有什么影响呢?

void Test1(int &b)
{
	cout << "b = " << b << endl;
	b = 10;
}

很明显可以看出,b的值在新函数中被修改了,在main函数中再次使用时,值已经变了,这就造成了很大的问题,因此出现了常引用。

在这里插入图片描述
引入常量的引用目的是为了防止误操作,但是常量的引用能不能被修改值呢?答案是肯定可以的,如下:

void Test2()
{
	int const &ref = 10;

	int *p = (int*)&ref;
	*p = 10000;
	cout << "ref =" << ref << endl;
}

int main()
{
	Test2();
	system("pause");
	return 0;
}

在这里插入图片描述
因此大家在编写C/C++代码时,常识性的在入参位置做好const保护,除非你的入参是要被修改的,这是C/C++编码界默认的一条铁律,记住这个,才不会被大佬们蔑视哦。

  • 发表于 2020-12-21 10:48
  • 阅读 ( 737 )
  • 分类:C/C++开发

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1478 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章