1. 什么是引用:
引用(referene)是一种复合类型,即基于其他数据类型的类型(指针亦是一种复合类型)。引用实质是给变量起了一个别名,而不是一个实体的类型,所以编译器不会给引用分配内存。同时,对引用的操作等效于对原变量的操作。
2. 定义一个引用:
定义一个引用,在变量名前加上声明符&即可。
(1)引用必须被初始化,即一个引用必须明确其引用的是谁。
int a = 1024;
int &b = a; // b是一个对a的引用
int &c; // 错误,引用未被初始化
在上述代码中,b是一个引用,它引用的是a,故b的值是1024。但b不是一个实体的变量,它并不占用实际的内存,且所有对b的操作都将同等地作用在a上。
(2)引用的目标不能更改
定义一个引用时,程序把引用将其引用的变量值绑定(bind)到一起,而不是数值的拷贝,这是与普通的变量赋值是显然不同的。也正因为如此,引用在初始化后,便不能在更改引用的目标。
int a = 1024;
int b = 2014;
int &r = a; // r等于1024
int &r = b; // 报错,引用的目标不能更改
(3)引用的目标必须时一个对象
int a = 1024;
int &b = a; // 正确,a是一个对象;
int &c = b; // 错误,引用本身不是一个对象;
int &d = 1024; // 错误,1024是一个字面值,不是一个对象
3. 引用的使用场合:
引用主要用在函数的参数传递中。
(1)作为形参和实参:
引用作为形参时,由于引用只是对象的别名,所以此时的形参是实参的别名。这样进行参数传递,形参在函数内部的变化都会作用在实参上。而不像用普通的变量进行参数传递,传值是单向的,只有使用指针才能实现双向的参数传递。
同时,引用本身不占内存,所以使用引用进行参数传递时,不仅节省了内存开销,而且避免了拷贝构造的过程,提高了代码的执行效率。
(2)作为返回值:
引用作为返回值,优点与作为形参时类似,同样节省了内存开销,并避免了变量拷贝的过程。但此时需要注意引用对象的作用域问题。若返回一个引用,该引用的引用对象是函数内部的一个局部变量,则函数结束后,该变量的内存就销毁了,该变量也就不复存在,变量的引用也自然失去了意义。要避免这个问题,可以使用new一个新对象的方式来完成对该对象的引用。
4. 引用的本质:
上面的部分阐述了引用的优点,那么它在底层究竟是如何实现的?其实引用在底层的实现和指针没有区别,同样占有一定的内存空间,但编译器对其进行了“包装”,在语言的层面上,展示给程序员的表现就是引用只是一个对象的别名,不占用任何内存。编译器具体的“包装”原理如下:
对于对象val,r_val是它的引用,p_val是指向val的指针。则实际上,r_val和p_val均占有一定的内存,该内存中存储着val的地址,在32位机器上,该段内存占用4个字节的空间。r_val和p_val分别作为函数的形参时,在函数调用的过程中,编译器会对它们取地址,二者不同的是,编译器取引用地址时的汇编指令是mov,即取的是引用本身占据的内存中存储的内容,也就是引用对象的地址,在此就是val的地址,这也正是引用看上去不占用内存,只是对象的一个别名的原因;而编译器其去指针的地址时,执行的汇编指令是lea,即取的是指针本身占据的内存的地址,在此是p_val的地址,所以在使用指针时,要获得val的值,必须加上取内容操作符,即*p_val。(参考资料:c++引用和指针的实现)
根据以上分析可以知道,引用在底层就是指针,同样占据一定的内存空间;但在语言层面,经过编译器的“包装”(不同的编译器的实现方式不一定相同),引用“看上去”只是对象的一个别名,不占用任何内存。