文章目录
#1 什么是引用
C语言中的取地址符 &,在C++中被赋予了一层新的内涵,即为引用。
简单来理解,引用就相当于是给一个变量取一个别名。
原本的变量名和新的变量名指向共同一块空间,这就意味着双方的修改是同步的。
当然,既然两者是相同的,那么类型自然也必须一致。
同时,引用不能空引用,引用在定义时必须初始化。
int& a; //这样的语句是错误的
可见,对b的修改同样也影响a。
从地址也可以看出,a与b指向的是同一块空间。
#2 常引用
常引用即为对常变量的引用,正确的如:
const int a = 0;
const int& b = a;
const int& c = 1;
同时如下的引用方式将会报错:
const int d = 0;
int& e = d;
int& f = 1;
这是因为无论是d还是1,都是常变量,只可读不可写。
而定义的e与f因为没有const修饰,可读可写,放大了权限。
权限只能平移或者缩小,故上述语句是错误的。
还有一种容易忽略的错误方式:
double g = 1.1;
int& h = g; //错误
// 下面这个才是正确的写法
const int& h = g;
这里的错误并不是因为类型不同,毕竟第三条语句是正确的。
之所以错误,是因为类型转换时,通常会创建一个临时变量来充当桥梁,而临时变量具有常属性,只可读不可写。这才是错误原因。
#3 使用场景
#3.1 引用作参数
用最基础的交换函数做例子。
本来要交换a与b要用到指针。
用引用作参数则清晰直观,更便于理解。
引用做参数还有别的作用。
例如参数为一个有一千个元素的数组,若不用引用,则意味着要创建一个同样大小的形参,花费时间与空间。
而引用则不需要,效率更高。
当然,这一点指针也可以做到。
#3.2 引用作返回值
引用作返回值,根据是否以引用返回,和是否以引用接收返回值,可分为四种类型。
#3.2.1 不用引用作返回值,也不用引用接收返回值
这就和以前的代码一样,不多赘述。
#3.2.2 用引用作返回值,但不用引用接收返回值
这段代码乍一看和上一段没什么区别,但是暗藏玄机。
以引用作返回值,意味着返回的是N的别名,然而N作为Test函数的局部变量,在离开函数时将会销毁。
我们知道,销毁并不是真的销毁,只是把这处空间视为可用,如果没有新的数据写入,那么原本的数据自然是保留的。
因此,ret接收到的到底是什么,取决于这个返回过程中,N这个变量所在的空间是否被别的数据覆盖。
这里运行代码结果符合预期,只是恰好N的位置没有被覆盖。
因为ret与N指向的不是同一块空间,只要N能够正确的传回给ret,那么后续N的改变自然不影响ret。
但是从使用角度而言,判断N是否被覆盖基本是不可能的,所以对于局部变量,以引用作为返回值是不稳定的,应避免使用。
对于全局变量,如果没有引用符,那么返回值是要经过一次拷贝传给ret的;
用引用作返回值,同样需要经过一次拷贝传给ret,效率上并无差别。
故此种情况基本不会使用。
#3.2.3 不用引用作返回值,但用引用接收返回值
不用引用作返回值,那么就是传统的传值返回,返回值是一个常变量。
故而写法一是错误的。
写法二虽然是正确的,但是对一个常变量取别名,并没有太大的意义,因而实际中也很少使用。
对于全局或静态变量,因为用引用接收,意味着ret是N的别名,那么就省去了一次拷贝,数据空间越大,效率提升越明显。
再看最后一种情况。
#3.2.4 用引用作返回值,也用引用接收返回值
这种情况下,ret和已经销毁的N指向的是同一块空间,后续对于N的改变,都会影响ret。
上图可见,一样的代码,却又不同的结果,这就是因为N,也就是ret的空间被覆盖所导致的。
综上所述,对于局部变量,基本不应该使用引用作为返回值。
对于全局或静态变量,应该在返回值处,和接收处都使用引用,达到最好的效率提升。
#4 底层
在语法上,我们认为引用并不开辟空间。
但从底层来看,引用和指针却是一致的。
从二者的汇编代码可以看出,引用和指针的操作,在底层是完全相同的。