一、什么是引用
1.1引用概念
引用不是定义一个新的变量,而是给已存在的变量取一个别名,编译器并不会为引用变量开辟内存空间,而是它和它引用的变量共同占用同一块内存空间。
引用格式:类型& 引用变量名(对象名)=引用实体。
我们直接来举一个样例:
上图中b是a的引用,而c又是b的引用,同时a、b、c其实指向的都是同一块内存空间。
注意:我们不可以这样使用引用,int& b,这是一种错误写法。即:
引用在Swap函数应用场景
下面把引用应用到Swap函数上,请看:
void Swap(int& m, int& n) { int tmp = m; m = n; n = tmp; } int main() { int a = 10; int b = 20; cout << "a=" << a << endl; cout << "b=" << b << endl; Swap(a, b); cout << "a=" << a << endl; cout << "b=" << b << endl; return 0; }
上面代码函数中的形参就是实参的一个引用,即m是a的引用,n是b的引用,改变m就是改变a,改变n就是改变b。
为指针定义别名
引用还可以为指针定义别名,请看:
上图就是我们通过引用交换了两个变量的地址。
1.2引用特性
1.引用必须要进行初始化(即int& a;这种写法是错误的)。
2.一个变量可以有多个引用
3.引用一旦引用一个实体,则不能在继续引用其它实体。
同时,引用类型必须要与引用实体是同一种类型。
二、引用使用场景
2.1引用做参数
比如,我们刚刚的交换函数Swap就是利用的引用来做参数:
void Swap(int& m, int& n) { int tmp = m; m = n; n = tmp; }
2.2引用做返回值
下面是引用作传值返回。
int fun() { static int n = 0; n++; return n; } int main() { int ret = fun(); return 0; }
函数fun中的变量n存放在静态区中,出了局部域后栈帧不会销毁。但是在进行返回n的时候依然会生成临时变量。所以,引用作返回值的时候,不管变量n是局部变量还是静态区的变量又或者是全局变量。编译器都会生成一个临时变量。
倘若我们不想生成临时变量的话应该怎么半呢?这就是引用的另一大作用:即引用作返回值的时候不会生成临时变量。
不生成临时变量的好处:减少了拷贝,提高了效率。请看:
这里就是返回的n的别名(即返回的n的引用)。
当我们的变量n是局部变量而非静态区变量的时候。请看:
我们已经知道这里返回的是局部变量n的别名,当把局部变量n的值给ret的时候,局部变量n所在的函数栈帧已经销毁了(即归还局部变量n所占空间的使用权),所以返回变量n的值理论上应该是不确定的。所以这里程序的运行结果应该有两种情况:
情况一:如果函数fun结束后,栈帧销毁,但是没有销毁栈帧,此时ret的结果侥幸是正确的。
情况二:如果函数fun结束后,栈帧销毁,清理栈帧了,那么此时ret的结果是随机的。
总之具体栈帧是否销毁还是要看编译器是如何处理的。
还有一种情况,请看:
首先,ret依然是n的别名(因为返回的是n的别名,而ret又是n的别名的别名,所以最后ret就是n的别名)。
其次我们发现程序运行结果就成了随机值了,这里的话就是刚刚上面提到的第二种情况了,即函数栈帧销毁后,清理了函数栈帧,所以打印出来的就是随机值。这里的话依然是和编译器的处理方式有关。
我们再来看一种情况:
这里打印出来依然是随机值,但是其具体原因与刚刚那种情况有些不同,这里rand()函数调用栈帧正好把fun()函数的栈帧覆盖了(说白了就是后面的函数栈帧覆盖了前面的函数栈帧),打印出来时随机值其实归根揭底还时栈帧被清理的问题。
2.3引用的几大作用
(1)引用作参数(形参的改变可以改变实参),即引用作输入型参数;引用也可以作输出型参数提高效率。
(2)引用作返回值(当对象比较大的时候,与传值返回相比会极大的提高效率)。
三、常引用
常引用这里我们通过几段代码来引出来:
请看:
上述变量a经过const修饰后不可更改,既然a本身已经不可以进行更改了,所以就不要妄想通过引用b来更改a了,这里强调的就是在引用的过程中,权限不可以放大。
再来看一段代码:
上述代码中强调的就是引用过程中权限可以平移但是权限不可以缩小。就比如说上述代码中的const int& b = x;这里缩小的并不是变量x的权限,而是缩小的引用b作为别名的权限。
再来看这里,这里要强调的是x++是可以的,但是b++是不可以的,因为引用过程中引用b经过const修饰后权限被缩小了,这里虽然我们不能通过引用b来改变变量x,但是我们可以直接对变量x进行操作(即x++)就可以了。
再来看看const int& m = 10;这条语句,首先这条语句是正确的,引用b是常量10的别名,既然10是常量即不可更改,那么就需要const进行修饰。说白了这里就是给常量区别名。
下面我们先来看一下隐式类型转换的代码:
发生类型转换的时候中间会产生临时变量,所以只要发生类型转换,都会产生临时变量。
请再来看下段代码:
上述代码的话为什么int& c = a;报错了,但是const int&c = a;没有报错。
这里依然是临时变量的问题,请看下图:
注意类型转换才会产生临时变量,相同类型不会产生临时变量。下面我们来看一看为什么不同类型会产生临时变量而相同类型不会产生临时变量的原因:
上述代码之所以会打印出来就是因为发生了类型转换,产生了临时变量。当运算符两边的内容不是同一类型的时候会发生类型提升,提升有提升的规则,一般时范围小的向范围大的进行提升。上述变量j和变量i是如何进行比较的呢?因为这两个变量不是同一个类型,所以这里依然会生成一个临时变量,对变量i进行提升,这个提升的过程其实就是生成一个临时变量的问题,这个临时变量在这里是8个字节,即生成double类型的临时变量,然后依靠这个double类型的临时变量区和double类型的变量j进行比较。
下面我们再来举一个权限放大的例子:
此时引用ret1经过const修饰后,相当于权限的平移,就不会报错了。
再来看一看权限的平移和权限的缩小的例子:
四、指针和引用的区别:
1.引用概念上定义一个变量的别名,子还真存储一个变量地址。
2.引用在定义时必须初始化,而指针没有要求。
3.引用在初始化时引用一个实体后,就不能在引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
4.没有NULL引用,但是有NULL指针。
5.在sizeof中含义不同:引用结果为引用类型的大小,但是指针始终时地址空间所占个数。
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7.有多级指针,但是没有多级引用。
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
9.引用比指针使用起来相对更安全。
好了,以上就是对C++中引用的一些基本内容。
就到这里吧,再见啦各位!!!