其实 引用的本质在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++编码界默认的一条铁律,记住这个,才不会被大佬们蔑视哦。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!