引用的概念及用法
所谓的引用并不是说重新定义的一个新的变量,而是给一个已经定义好了的变量起的一个别名。
下面看看引用到底是如何使用的:
void test1()
{
int a = 1;
int& b = a; //引用变量b是a的别名
std::cout<<"a:address->"<<&a<<std::endl;
std::cout<<"A:address->"<<&b<<std::endl; //注意这里的&是取地址
a = 10;
b = 100;
std::cout<<"a = "<<a<<std::endl;
std::cout<<"b = "<<b<<std::endl;
int& c = b; //应用变量c是引用变量b的别名,别名的别名
c = 1000;
std::cout<<"a = "<<a<<std::endl;
std::cout<<"b = "<<b<<std::endl;
std::cout<<"c = "<<c<<std::endl;
}
运行结果如下:
由结果我们可以看出引用变m量b与变量a的地址是一样的,该变b的值也会影响a。并且一个变量可以取多个别名,这里b是a的别名,c是b的别名,也就是a的别名了。就像我们人一样,你有一个大名(身份证上的名字),可能还会有一个小名,也或许还会给自己起一个洋气的英文名,总之不管别人叫哪一个名字,叫的都是你本人就对了。
总结:
1、一个变量可以有多个别名
2、引用必须初始化b
3、引用只能在初始化的时候引用一次,之后不能再引用其他的变量
引用做参数
在之前的学习当中,我们知道调用函数的传参有传值调用和传址调用。下面再来看一看这两种方式:
1、传值调用
void swap(int a,in b)
{
int tmp = a;
a = b;
b = tmp;
}
//现在的我们都知道了这样的函数是无法完成我们希望的交换功能的。
//究其原因,就是因为这里使用的是传值方式,那么如果别人一旦想要
//调用我给我传两个参数,我就要生成两个局部的临时变量用来接收
//别人给我传的两个参数。但是当调用结束,函数栈帧释放,相应的
//用于接收参数的两个局部变量也会被一同释放掉,但是交换是发生在
//这两个局部变量之间的,现在他们已经被释放掉了,并且从头到尾
//都没有对调用方的两个想要交换的变量产生任何影响。因此这里的
//传值调用并完成不了交换的功能。
2、传址调用
void swap(int *a,int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//所谓的传址调用就是传指针。
//现在我们将两个形参改为指针,也就是说别人想要调用我完成交换功能时
//就将想要交换的两个变量的地址传给我就好了。函数调用期间对地址里面
//的内容进行交换,即便调用结束以后,函数栈帧被释放,但是是对两个地址
//的内容进行了交换,释放栈帧以后这两个地址并不是函数栈帧里的,
//所以并不会一并被释放,并且完成了交换的功能。
3、传引用
void swap(int& a,int& b)
{
int tmp = a;
a = b;
b = tmp;
}
//我们知道引用变量就是我们给一个已经定义好的变量起的一个别名,
//所以说,如果我们这里采用传引用的方式,那我们这里的形参就是实参的别名
//在刚刚我们也看了,变量和变量的别名,他们两个的地址是同一个,所以啊,
//这和传址调用有着异曲同工之妙。
引用做返回值
有时候我们一个函数调用结束需要返回一些信息供调用方使用。比如说一个加法函数。
//方法一
int ADD(int a,int b)
{
int ret = a+b;
return ret;
}
//方法二
int& ADD(int a,int b)
{
int ret = a+b;
return ret;
}
//这里方法一是采用值的形式返回,而方法2是采用引用的形式返回。
//我们可以看看汇编语言是如何这两种不同返回方式的,如下图:
那么问题来了,我们有该如何选择以那种方式返回呢???
1、如果返回的对象出了该函数作用域依旧存在,则使用引用返回,因为这样会更加高效
2、如果返回对象处了函数的作用域就不存在了,则使用值返回。
注意:不要返回一个临时变量的引用,因为临时变量在函数调用结束以后会随着栈帧的释放而被释放,而传引用返回的方式返回的是变量的地址,而事实是该变量已经被释放。
引用和指针的区别
在这之前我们一直在说,引用是一个变量的别名,所以可能就会想到说这个引用变量时不会占据任何的空间的。但是!请注意!这种想法是不对的。引用变量也是会占据一定的内存空间的,也需要在栈上额外占用存储空间。
因为引用的底层实现其实是指针。从语法上来看只是一个别名,但在底层上依旧是开辟了一块空间。
int main()
{
int a = 0;
int& b = a;
return 0;
}
看一下这段代码的汇编:是如何处理变量a,和引用变量b
从汇编我们可以看出对引用变量初始化为a的别名,就是将a的地址给了引用变量b。想一想这种方式是不是很熟悉?对了,正如你所想到的我们经常写的一个代码:
int a = 0;
int *p = &a;
//取a的地址赋给指针变量p
这样看来其实引用的底层也就是一个指针,只不过明面上向我们所展示的是一个变量的别名,但我们应该注意引用变量是一个已经定义过的变量的别名,他是别名,他也占空间,因为他的底层实现是指针。
下面就看一看引用和指针的区别:
1、引用只能在定义时初始化一次,之后不能改变指向其他的变量,但指针可以。
2、引用必须指向有效的变量,但指针可以为空。
3、sizeof引用得到的是所指向的变量的大小,但sizeof指针是对象的地址的大小。
4、引用的自增(+ +)自减(- -)是对值的+1或-1,而指针++或–是+或-其所指向的类型大小。