《c++ primer》中这样写的:引用在内部存放的是一个对象的地址,它是该对象的别名。对于不可寻址的值,如文字常量,以及不同类型的对象,编译器为了实现引用,必须生成一个临时对象,引用实际上是指向该对象,但用户不能访问它。
#include<iostream> #pragma pack(1) class data{ public: double data_a; double &data_ra; data():data_ra(data_a){ //引用不能在构造函数体内初始化,要在构造函数名称右边初始化 data_a=1.0; } }; int main(){ double a=100; double &ra=a; double *pa=&a; printf("%d\n",sizeof(ra)); printf("%d\n",sizeof(&ra)); printf("%d\n",sizeof(pa)); data temp; temp.data_a=1.0; temp.data_ra=temp.data_a; printf("%d\n",sizeof(temp)); getchar(); return 0; }
为了防止字对齐的影响,以一个字节对齐后,发现类中的引用是4个字节,也就是一个指针的大小。(节约内存考虑)
而main中引用是大小计算就是本来类型的大小,想想也是,引用本来就是一个对象的外号,指引用的时候就是指该对象。
分析为什么会是这种结果呢?背后是什么机制在起作用?
常量指针与指针常量我们都是知道的
常量指针如 double const *P; //即指针不能通过*P对变量赋值,p可以不初始化
double a=100; double const* cpa2=&a; a=2;//可以赋值 //*cpa2=2;//编译会报错 不能这样赋值
指针常量如 double * const p=&a;//指针所指的方向是不可改变的,且p必须初始化
现在来看看,指针常量与引用何其相似,估计在底层引用就是通过指针常量实现的,再来看
#include<iostream> using namespace std; int main(){ double a=100; double &ra=a; double * const cpa=&a; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(ra)); printf("%d\n",sizeof(cpa)); printf("%d\n",sizeof(*cpa));//这里相当于printf("%d",sizeof(ra)) getchar(); return 0; }
结果是 8 8 4 8
指针常量 *cpa 类似引用ra的作用了,接着就可以解释为什么在类中引用是四个字节(cpa本来就是一个指针,指针的长度就是4),但在访问它的时候编译器就自动加上了*cpa,指向了对象,于是就是它原来类型的长度了。
测试下,果然就是这么一回事。
#include<iostream> #pragma pack(1) class data{ public: double data_a; double &data_ra; data():data_ra(data_a){ //引用不能在构造函数体内初始化,要在构造函数名称右边初始化 data_a=1.0; } }; int main(){ data temp; temp.data_a=1.0; temp.data_ra=temp.data_a; printf("%d\n",sizeof(temp)); printf("%d\n",sizeof(temp.data_a)); printf("%d\n",sizeof(temp.data_ra)); getchar(); return 0; }
类的长度本来是12,而main中测试的时候成为了 8 + 8,符合了假说
既然走到了这一步,为何不在看看汇编里对引用是怎么实现的。
啊哈 ,引用通过间接寻址的方式获取了相应的数据,跟指针的间接寻址是类似的!
引用设计的意义(既然设计出来一定是有他存在的道理的):引用的主要功能是传递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
但是可以想象,引用能做的事,指针也同样可以做,并且指针的功能更为强大。那么为什么还需要引用呢?答案也许是指针虽然强大但是有时太肆无忌惮了,但是引用是个被约束过的‘指针’,需要一个变量的别名的时候就只要使用引用即可。eg:比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。
总结:引用是一个被包装过的指针,实现机制类似指针常量