C++入门——引用
创始人
2024-05-07 15:58:49
0

1.概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间
 

类型& 引用变量名(对象名) = 引用实体;
而引用类型必然要与引用实体的类型一致。

void TestRef()
{int a = 10;int& ra = a;printf("%p\n", &a);printf("%p\n", &ra);
}


2.引用特性

1.不同于指针,不初始化也不会报错,引用在定义时必须初始化

int& ra = a;//right
int& ra;//error

2. 一个变量可以有多个引用

int a=0;
int& b=a;
int& c=a;

由于引用只是引用实体的一个别名,因此我们也可以这样使用

int a=0;
int& b=a;
int& c=b;

3. 引用一旦引用一个实体,再不能引用其他实体。

int a=0;
int b=1;
int& c=a;
c=b;

例如上面的代码,c=b的作用并不是让c变为b的别名,而是将b的值赋给c,也就是a


3 .常引用

int a=10;
const int& ra=a;

类似于const常量,const修饰常引用后,我们无法做到改变ra的值来改变a的值,而只能通过a来进行改变。

const int a=10;
int& ra=a;

而这段代码,本身a不能修改,如果直接用ra引用,会导致权限放大,是不可以的

而权限不变和权限缩小都是没有问题的

const int b=10;
const int& rb=b;int c=10;
const int& rc=c;

我们再来看一下下面的代码

double a=1.23
int b=a;
int& c=a;
const int& d=a;

经过测试,我们可以得知,b和d是可以这样使用的,而c不可以,这是因为,在进行隐式类型转换或整形提升时,需要创建一个临时变量来存储,这样,c和d其实就是作为这个临时变量的引用,而临时变量是无法被改变的,所以我们需要进行常引用。

而同样,由于临时变量具有常性

int x1=1;
int x2=2;
int& x=x1+x2;

这样的代码也是不可行的,依然需要使用常引用。

而在后面会讲到的引用传参中,如果不需要改变参数,建议也使用常引用 


4.   引用的使用场景

(1).引用作参数

void Swap(int left, int right)
{int temp = left;left = right;right = temp;
}void Swap(int* left, int* right)
{int temp = *left;*left = *right;*right = temp;
}void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}

例如一个简单的交换函数,在c语言的学习中,我们知道,传值调用参数只是一个拷贝,无法完成实参的交换,因此我们选用传址调用,通过指针来完成交换。

而我们也可以进行传引用,这是,left与right都是实参的别名,对形参进行交换时,其实也就是对实参进行交换。

我们在这里插入一个点,这三个函数是否构成函数重载呢?其实是构成的,因为它们的参数类型是不同的,但是,由于传引用与传值的实参都是相同的,所以即使能构成函数重载,在使用时也会出现问题。

单从上面,我们似乎还感知不到引用的优势,我们可以看一下我们在数据结构中所学到的链表

void SListPushBack(SLTNode** pphead, SLTDateType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){exit(-1);}newnode->data = x;newnode->next = NULL;if (*pphead == NULL)//由于无哨兵位,若链表为空只需要赋值{*pphead = newnode;}else{SLTNode* tail = *pphead;//找尾while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

就以尾插为例,许多同学可能在刚开始学习时无法理解参数中二级指针的含义

而本身我们需要传入的是以指针存储的头结点的地址,但在插入或删除中,头结点的位置可能会被改变,所以我们需要进行传址调用,这便是使用二级指针的原因。

而我们将传址调用改为传引用,同样能做到改变头结点的位置,同时也更容易去理解

void SListPushBack(SLTNode*& rphead, SLTDateType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){exit(-1);}newnode->data = x;newnode->next = NULL;if (rphead == NULL)//由于无哨兵位,若链表为空只需要赋值{rphead = newnode;}else{SLTNode* tail = rphead;//找尾while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

(2).引用作返回值

我们先来简单分析一下传值返回

int Add(int a, int b)
{int c=a+b;return c;
}int main()
{int add=Add(1,2);return 0;
}

 首先先开辟一块主函数的空间,紧接着去调用Add函数。而Add函数的空间内分别有三块空间存储a,b,c的值。而在传值返回中,返回的并不是c,而是c的一个临时变量。而Add这块空间便被销毁了

而临时变量较小时,会被存放在寄存器中,较大时,则会存放在调用Add函数的栈帧中

当我们转到反汇编时,可以看到,在return c中,临时变量的确时存储在eax这个寄存器之中的。

而当我们进行传引用返回时,返回的便不是c的拷贝的临时变量,而是c的引用

int& Add(int a, int b)
{int c=a+b;return c;
}int main()
{int add=Add(1,2);return 0;
}

 而这样,会出现一些问题,例如非法访问。而由于编译器的不同,有些编译器中Add函数在调用过后会被销毁,这样会c为随机值,进而导致add变量为随机值。

例如我们在原代码的基础上做一些改变

int& Add(int a, int b)
{int c = a + b;return c;
}int main()
{int& add = Add(1, 2);cout << add << endl;Add(10, 20);cout << add << endl;return 0;
}

这时,临时变量是c的引用,而add是临时变量的引用,因此add便是c的引用。

而当第二次调用Add函数是,Add函数所占的空间不变,因此会使c变为30,而add也一样

同样,若是Add函数调用过之后会被销毁,当c变量原来的地址被占用时,也会出现类似的问题。

当然,传引用返回也有好处,例如在内存中开辟一块空间存储一个较大的数组等,当我们使用传值返回时,需要进行拷贝,使用大量空间,而传引用返回就不需要进行拷贝,从而会使效率提升。同时,由于是在内存中开辟空间来存储数组,也不会发生非法访问的问题。

同样,引用传参也有效率提升这个优势。


5.引用传参和传返回值的优势 

1.有些场景下会使效率提升(大对象+深拷贝,后面会讲)

2.有些场景下,形参的改变可以改变实参,引用返回可以改变返回对象(后面也会将讲)


6. 引用和指针的区别

1.在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
2.引用在底层实现上实际是有空间的,因为引用是按照指针方式来实现的                                      3. 引用概念上定义一个变量的别名,指针存储一个变量地址。
4. 引用在定义时必须初始化,指针没有要求
5. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
6. 没有NULL引用,但有NULL指针
7. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
8. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
9. 有多级指针,但是没有多级引用
10. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
11. 引用比指针使用起来相对更安全

为什么说引用更加安全呢?举个简单的例子

void test1(int* p)
{*p = 10;
}void test2(int& r)
{r = 10;
}

这样的函数,在传参时,使用指针可能会发生许多人为的错误,例如空指针、野指针等。

test1(NULL);
test1(0);

而使用引用并不会。


 

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...