目录
零,前言
一,引用的概念
二,引用的作用
1,引用做返回值
2,引用做参数
三,const常引用
四,引用的原理
五,引用与指针的区别
在大学课程C++或者数据结构课本上很多同学发现, 在函数的传址传参中加了一个“ & ”的标志 :
没有深入了解C++之前,很多同学会有点懵,那么接下来我就用这篇文章来介绍引用操作符的所有知识。
C++中的引用不是新的变量,而是给一个变量取一个别名,编译器中不会为引用这个变量创建一个新的空间,它和它引用的变量共同使用同一个内存空间。引用 符号为"&"。引用类似于指针,但是不能替代指针的所有功能。
数据类型& 引用变量名(对象名) = 引用实体;
引用有以下几个特性。
1)引用在定义时必须初始化。
2)一个变量可以有多个引用,也就是说一个变量可以有多个别名。
3)一个引用一旦引用了某个变量,就不能再引用其他变量。
例如:
int main()
{
int a = 10;
int& ra = a; //必须初始化,例如int& ra;是不允许的。
int& rra = a; //可以有多个引用。
return 0;
}
引用的作用如图所示:
如图可知,引用的值和a的值均相同,并且引用的地址和a的地址均一致。
那么既然a,ra和rra,引用还有什么意义呢?引用在实际操作的价值是什么呢?
首先说结论:传引用返回与传值返回相比能减少拷贝,提高函数调用效率。我们首先来看一个例子:
我们可以很显然的看到,传引用返回在调用十万次的情况下,比传值返回所要消耗的时间少100倍,在大工程中 调用次数比十万次还要大,这大大提高了程序运行的效率。
但是使用引用返回存在限制,具体是两点:
如果出了函数作用域,变量还存在,就能使用引用返回。
如果出了作用域函数不存在了,就不能使用引用返回。
至于原因我将会在下面进行解释。
引用作参数时,可以减少拷贝,提高函数效率
如上面引用作返回值所示,引用做参数时同样可以提高效率。我们可以通过比较两者运行时间来判断,那么请读者自行验证。
引用可以作为输出型形参,对形参进行更改,与指针的功能类似例如:
作为输出型参数时,在C++中指针可以被引用替代。在函数实现的时候不需要解引用,并且传参
时也不需要加多余的符号,会更加简便。例如:
当我们使用const来定义一个变量时,再引用该变量时,程序会出错
其原因涉及到权限的放大和缩小。
我们在定义一个变量的同时也就赋予了这个变量权限。我们不加任何修饰地定义a变量时,a变量是可读可写的也就是说可以更改,可以输出。
当我们用const定义变量a时,a就变成只能输出,不能对其进行修改。这就是权限的缩小。如果我们使用int& ra引用了该变量,这就导致ra可读可写,但是a变量本身可读不可写,这就导致了权限的放大,程序出错。
我们只需要在引用前面加上const,使两者权限相同,程序不会报错。
在C++程序中,权限只能缩小,平移而不能放大。
#include
using namespace std;
int main() {
int a = 0;
int& ra = a;//权限平移
const int& rra = a;//rra引用a,权限缩小
//b定义为只读变量
const int b = 10;
a = b//是一种拷贝,a不影响b,不会出错
//int& rb = b;//权限放大,程序出错
const int& rrb = b;//权限平移
return 0;
}
如果我们在设计函数时使用引用作为参数又会发生什么错误呢?
对于上面代码中a,ra,b,rb变量时我们使用传值传参(不加引用)不会发生错误,因为传值传参是对于参数的拷贝,拷贝不会涉及到变量本身。
如果一旦在传参中加了引用,会影响到变量本身的值,会涉及到权限放大缩小,例如上面代码中 :将rra传入某个函数时,一旦在该函数内进行rra的修改,程序就会报错,因为rra是可读不可写的,因此通常在传值传参使使用const常引用作为函数参数.。如下图所示
int a = 3;int& ra = a;
void fun( const int& ra)
如果函数只为了修改参数,我们最好不使用const引用传参。
const引用做参数时也可以传常量。
看了这么多,我相信小伙伴们肯定会疑惑为什么使用引用能够提高函数调用效率 ,在这里我将讲解一下引用的底层实现。
引用作返回值时发生了什么呢?与传值返回的区别是什么?
当我们在执行该程序时,进入main函数首先会创建一个main函数的栈帧,然后在main函数中定义了ret变量。随后我们调用count函数,在main函数的栈帧中创建了一个count函数栈帧。
在count函数中,定义了一个变量n,n存于count函数的栈区。
我们都知道,每个函数在函数调用结束后都会被销毁。由于n存在于栈区,因此在count函数结束count被销毁,n的地址也随之被销毁,导致于函数无法找到n作为返回值。因此操作系统为了解决这个难题,它将n拷贝到一个临时空间中,此空间存于寄存器,寄存器不会随函数销毁而销毁。然后在函数调用结束后操作系统找到了该寄存器内的空间,并将其中的n赋值给ret变量。
我们会发现,如果使用传值返回,我们多了一层将局部变量拷贝到寄存器中空间的操作。
如果我们把它改成引用返回又会发生什么呢?
我们如果使用引用返回,那么我们返回的值是n的别名,相当于返回了n本身。由于n存在于栈帧中,而count函数结束,n空间也就被销毁,找不到n了。由于引用返回是要返回n本身,所以程序会出错。
什么时候不会出错?什么时候才能使用引用返回?
只有当count函数内的参数不存在于count函数这个栈帧中。也就是说,你返回的参数不是在这个函数内部定义的,函数结束后,参数不会随函数结束而销毁。就可以使用引用返回。
引用返回为什么能提高调用效率?
我们从上面知道了,由于传值返回有一个拷贝到寄存器的操作。而引用返回,返回的是变量的别名,也就是它本身,而不需要拷贝这个操作,所以能够提升效率。
引用在语法上就是一个别名,而没有独立空间,和引用实体公用一块空间。而在底层实现上引用存在空间,因为引用就是通过指针来实现的。
引用与指针区别
1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全