什么是引用
引用(reference)是C++对C语言的重要扩充。引用就是给一个已经存在的变量取了一个别名,并不是创建一个新的变量,所以编译器不会给引用变量开辟内存空间,它和被它引用的变量公用一块内存空间。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如何理解上面的概念呢?
首先我们在创建一个变量时,是需要给他一个变量名的
int a;
上面我们就创建了一个int类型的变量,它的变量名就是a。
在我们创建这个变量的时候,编译器就会在内存中为这个变量分配一块对应的空间,里面存放的就是这个变量对应的值。当我们对它进行赋值操作
a=10;
就是让内存中存放这个变量的空间被修改成了10。
而这里面的变量名a起到了什么作用呢?
这个变量名a其实是给人看的,编译器在编译时,使用的并不是我们给变量定义的变量名,而是我们要操作变量的内存地址,编译的过程中,会把我们代码中所有的变量a都替换成变量a对应的内存地址。所以在计算机的内部,其实是不储存变量名的,这个变量名是给人和编译器看的,编译器可以通过你给的变量名,在符号表中找到这个变量的地址,然后对它进行对应的操作。
而引用是给一个变量取了一个别名,就是说这两个变量名在内存中都是同一块地址,也就是说这其实就是一个变量,只不过它有两个名字。而变量名不在计算机中存储,所以给引用一个变量不会开辟内存空间也就解释的通了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
其使用方法为:类型& 引用变量名(对象名) = 引用实体;
int a=10;
int& b=a;//引用定义
上面就是一个引用的使用方法,b是a的引用。
在类型后面加&的语法表示的就是引用,和取地址时在一个变量前面加&使用的符号最然虽然相同,但是他们的意义是完全不同的,注意区分。
引用的特性
在使用引用的时候,有几点需要注意:
1.引用在定义的时候必须初始化
int& b;
这样的使用方法是不允许的,在使用引用时,必须指定是给谁取别名。
2.一个变量可以有多个引用
int a=10;
int& b=a;
int& c=a;
int& d=b;
上面的a,b,c,d都是同一个变量的别名。
3.引用一旦引用了一个实体,就不能再引用其他实体
int a=10;
int b=20;
int& c=a;
c=b;
在上面的代码中,首先让c成为a的别名,再让c=b,其意义是把b变量的值赋值给c变量,而不是让c在重新引用b。因为定义引用之后,就不能再更改了。
引用的应用场景
1.引用做参数
在我们写C代码时,可能经常遇到这样的场景,当我们在一个函数中需要对传来的参数进行修改时,我们只能使用传指针的方法,然后在函数中解引用进行修改,因为函数在运行时会建立自己的栈帧,对传来的参数都会进行拷贝,运行结束这些临时数据也会被销毁,所以如果不适应指针的方法就无法实现对函数外的参数本身进行修改的目的。
而现在我们也可以通过引用传参的方式实现函数里对函数外的参数进行修改的目的,因为引用是对一个变量起别名,所以函数里的引用就是函数外哪个对应变量的一个别名,所以对它进行修改等同于对对应变量的修改
void Swap(int& a,int& b)
{
int c;
c=a;
a=b;
b=c;
}
int main()
{
int x=1,y=2;
Swap(x,y);
return 0;
}
除此之外,引用还可以做返回型参数。
void Count(int n,int& count)
{
count++;
if(n==0)
return;
n--;
Count(n,count);
}
在这个递归调用中也可以使用引用传参,在函数建立栈帧时,就可能出现count是count的别名的情况,能够实现的原因是因为虽然他们的变量名是相同的,但是两个count是在不同的栈帧的,所以可以编译通过。
2.引用做返回值
引用可以做返回值,那么普通的传值函数返回是怎样的过程呢?
为什么在返回前要创建一个临时变量存储返回值呢?
因为在函数执行完成后,这个函数的栈帧就销毁了,而c又是这个存储在这个栈帧中的,如果直接把c赋值给ret,那么这时候c存储的位置就不再属于我们的程序了,因为它的栈帧已经销毁了,所以这时候能否正常返回就取决于在栈帧销毁后会不会清理栈帧里面的数据,并且即使没有修改数据,这也属于是越界访问的问题,所以会通过一个临时变量来解决这一问题。
这个临时变量存储的位置分为两种情况:
1.返回值较小时(4 or 8字节),一般是寄存器充当临时变量。
2.如果返回值比较大,临时变量是存放在调用这个函数的栈帧中的。
也就是说,所有的传值返回的返回值都会生成一个拷贝。
那使用引用返回呢?
根据函数返回的特点,我们可以假设在ADD函数返回时会创建一个临时的变量,假设它的名字时tmp,是返回值c的引用,栈帧销毁后再把tmp赋值给ret。
这样的代码是存在问题的:
1.存在非法访问的问题
tmp是变量c的引用,所以tmp变量存储的位置是在ADD的栈帧里的,那么在ADD的栈帧销毁后,tmp再去把它的值赋给ret,就属于越界访问的问题,这个代码在编译器可以跑过的原因在于这属于越界读的问题,编译器检查不出来。
2.函数栈帧销毁后可能会清理空间,会改变tmp的值
是否清理空间是取决于编译器的,不同的编译器实现过程不一样,但是即使不清理空间,在栈帧销毁后我们再调用一些别的函数或者执行一些别的操作,都可能改变tmp的值。
综上所述,引用做返回值的适用条件为:离开函数作用域后返回对象还没有被销毁,就可以使用引用返回,即静态变量,全局变量。
引用因为是给变量取别名,而别名是不占空间的,所以在变量很大时,引用传参与引用传返回值会有不错的效率提升。
常引用
有一种变量叫做const修饰的常变量,它与普通变量的区别在于const修饰的变量不能被修改,即他是只读的,而普通变量是可读可写的,那么在引用这类变量时,就要注意变量的权限问题!
const int a=10;
int& b=a;
这种引用方式就是不可行的,本来a变量时const int的,在b对a引用之后,b变成普通int了,即造成了权限放大的问题,会报错。
如果引用时权限变小或者权限不变是没有问题的。
还有一个场景
double d=1.1;
int& i=d;
在上面的代码中,我使用int类型的变量引用了double类型的变量,相近的类型之间进行运算会发生隐式类型转换,而在隐式类型转换时,并不是直接对原有的值进行改变,而是会先创建一个临时变量,然后让临时变量的类型改变,而临时变量是一个右值(即具有常性),所以在引用时使用普通类型引用相当于权限放大,会报错,应该使用const常引用。
所以可以发现
const Type& 可以接受各种类型的变量。
以上就是本篇的全部内容。