什么是引用
引用变量是C++中的一种特殊类型的变量,它可以视为已经声明的变量取一个别名,从语法层面编译器并不会为引用变量开辟空间,引用变量与它引用的变量共用同一块空间。
引用变量的类型与被引用变量类型相同,但需要在变量名前加&表示该类型的引用,引用变量的定义方式如下。
int main()
{
int a = 0;
int& b = a; //给a取了一个别名叫b
return 0;
}
其中,int&表示int 类型的引用,b为引用变量名,a为所引用的变量。
举个例子:
你的名字叫张狗蛋,你的同学给你取了一个别名叫狗蛋,那张狗蛋和狗蛋都是你,同理如果给a变量取一个别名叫b,那a和b都表示同一块空间。
在举个列子:
首先定义变量的本质是在内存中开辟一块空间,变量名的本质其实是一种标签它简化了我们使用这块空间的方式,我们可以通过变量名来访问和操作这块空间,引用就是为这块空间多创建几个标签。
引用变量的类型与被引用变量类型相同,但需要在类型后面加&表示该类型的引用,引用变量的定义方式如下。
int main()
{
int a = 0;
int& b = a; //给a取了一个别名叫b
return 0;
}
其中,int&表示int 类型的引用,b为引用变量名,a为所引用的变量。
引用的使用
列如,定义一个 int 类型的变量 x,然后再定义一个 int 类型的引用 y,将 y 引用到 x 上,代码如下:
int x = 10;
int &y = x;
这样就可以使用 y 去访问 x 的值。
y = 20;
cout << x << endl; //输出20
需要注意的是,在定义引用变量时必须初始化,而且引用的类型必须与所引用的对象类型相同。另外,引用变量在定义后不能再引用其他变量,因为它只是所引用变量的别名。
引用常用场景
做函数参数
void Swap(int& x, int& y)
{
int temp = x;
x = y;
x = temp;
}
引用做函数参数时可以减少拷贝因为引用是别名不需要开辟额外的空间,并且形参与实参是共用同一块空间,行参数改变会影响到实参。
做函数返回值
int& Add(int a, int b)
{
static int c = a + b;
return c;
}
引用做返回值时同样可以减少拷贝因为返回指针和引用不会产生临时变量,但需要注意的是不能返回已经销毁空间的引用。如
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int a = 20;
int b = 30;
int ret = Add(a,b);
return 0;
}
这里函数返回时引用对象的空间就已经返还给了操作系统,如果此时还继续使用该空间的话可能会发生错误。
引用的特性
引用变量的类型与被引用变量的类型必须相同引用变量用在定义时必须初始化一个变量可以有多个引用引用变量一旦引用一个变量,就不能在引用其他变量引用只能允许权限的平移和缩小(后面会接受)引用不能直接引用字面常量(后面介绍)如果引用被const修饰,仅代表该别名不能被修改(后面介绍)引用权限相关
变量的权限指的是,该变量是否具有常属性能否被修改,因此引用变量与被引用变量间通常会有以下三种权限情况
权限的放大
一个不具有常属性的引用变量,去引用一个具有常属性变量
//权限的放大
void F1()
{
const int a = 10; //被const修饰过具有常属性
int& b = a; //不具有常属性
}
权限的缩小
一个具有常属性的引用变量,去引用一个不具有常属性的变量
void F3()
{
int x = 0; //不具有常属性
const int& y = x; //具有常属性
}
权限的平移
引用变量与被引用的变量都具有常属性或者都不具有常属性。
void F2()
{
//x1与y1都被const修饰过
const int x1 = 0;
const int& y1 = x1;
//都没有被const修饰过
int x2 = 0;
int& y2 = x2;
}
c++只允许权限的缩小和平移,因为假如一个变量它本身限制比较严格不能给被修改但被引用之后它却能够间接修改了这不扯淡吗。
注:引用的权限问题对于指针也是一样的
临时变量与const修饰引用相关
临时变量
如何理解下面这段代码,编译器是可以正常编译通过的。
void F()
{
double d = 3.14;
const int& x = d;
const int& y = 10;
}
不是说只能引用同类型吗?还有为啥能引用字面常量?
c++不支持引用不同类型,也不支持引用字面常量,它们可以编译通过的原因是临时变量。
不同类型间的拷贝会发生隐式类型转换,隐式类型转换又会产生临时变量。所以这里并不是直接把d赋值给x,而是先进行隐式类型转换赋值一个临时变量,在由x来引用这个临时变量。
注:这里的必须要加const,因为临时变量具有常属性。
c++并不支持直接引用字面常量但支持间接引用字面常量,在引用一个字面常量时会产生临时变量,是先把字面常量赋值给这个临时变量,在由y来引用这个临时变量。
例如,对于如下代码:
const int &r = 42;
编译器会将这段代码转换成如下形式:
const int temp = 42; // 生成临时变量 temp 并赋值为 42
const int &r = temp; // 将 r 初始化为 temp 的引用
const修饰引用相关
const修饰引用变量,可以让引用变量无法被修改,但仅代表该别名不能被修改。
定义如下面三个变量
int a = 10;
int& b = a;
const int& c = b;
c不能够被修改但是a和b可以被修改。
举个例子你在家里你爸妈叫你张狗蛋,在学校里同学老师叫你狗蛋,这个学校有一个限制不能在学校玩手机,你作为学生狗蛋不能在学校里面玩手机但是你回到家作为张狗蛋就可以玩手机,这个限制限制的只是学生狗蛋。同理这个const限制的也只是c。
b = 20;
a = 30;
引用与指针区别
指针是一个变量,它存储了一个地址,可以用来间接访问其他变量或对象的值。引用变量在语法概念上就是一个别名,没有独立空间,和其引用的变量共用同一块空间
但引用在底层上实际是有空间的,因为引用的底层和指针的底层非常相似。例如下面这段代码:
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 30;
截取vs2019的汇编代码对比,可以发现汇编代码执行的操作几乎都是一样的
int a = 10;
00EC14F1 mov dword ptr [a],0Ah //将十进制数 10 存入变量 a 的内存地址
int& ra = a;
00EC14F8 lea eax,[a] //取出变量 a 的地址并存储到寄存器 eax 中
00EC14FB mov dword ptr [ra],eax //将寄存器 eax 中的地址存储到引用 ra 的内存地址中
ra = 20;
00EC14FE mov ecx,dword ptr [ra] //从引用 ra 的内存地址中取出地址,并存储到寄存器 ecx 中
00EC1501 mov dword ptr [ecx],14h //将十进制数 20(16进制是14) 存入寄存器 ecx 中指向的内存地址中
int* pa = &a;
00EC1507 lea edx,[a] //取出变量 a 的地址,并存储到寄存器 edx 中
00EC150A mov dword ptr [pa],edx //将寄存器 edx 中的地址存储到指针变量 pa 的内存地址中
*pa = 30;
00EC150D mov eax,dword ptr [pa] //从指针变量 pa 的内存地址中取出地址,并存储到寄存器 eax 中
00EC1510 mov dword ptr [eax],1Eh //将十进制数 30(16进制是1E) 存入寄存器 eax 中指向的内存地址中
一些区别
引用概念上定义一个变量的别名,指针存储一个变量地址。引用在定义时必须初始化,指针没有要求引用在初始化时引用一个变量后,就不能再引用其他变量,而指针可以在任何时候指向任何一个同类型实体没有NULL引用,但有NULL指针在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)引用自加即引用的变量增加1,指针自加即指针向后偏移一个类型的大小有多级指针,但是没有多级引用访问实体方式不同,指针需要显式解引用,引用编译器自己处理引用比指针使用起来相对更安全引用的总结
引用变量是C++中的一种特殊类型的变量,它可以视为已经声明的变量取一个别名,引用变量与它引用的变量共用同一块空间。引用变量的类型与被引用变量类型相同,但需要在变量名前加&表示该类型的引用引用的常用场景是做函数参数和函数返回值因为可以减少拷贝增加效率,做函数返回值时需要注意不能返回以及销毁变量的引用引用变量时必须初始化且引用的类型必须与被引用的对象类型相同一个变量可以有多个引用,引用变量一旦引用一个变量,就不能在引用其他变量引用只能允许权限的平移和缩小,引用不能引用字面常量引用和指针的区别是:指针是一个变量,它存储了一个地址,可以用来间接访问其他变量或对象的值。引用变量在语法概念上就是一个别名,没有独立空间,和其引用的变量共用同一块空间,但引用在底层上实际是有空间的,因为引用的底层和指针的底层非常相似