目录
引用的概念的定义
对于引用,许多同学可能并不陌生,许多C语言版本的数据结构课本中都大量使用了引用来代替指针。
引用不是定义了一个新变量,而是给已有的变量起别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块内存空间。
类型& 引用别名 = 引用对象;
就比如,每个人除了有自己正式的名字(身份证上的名字),可能还有自己的小名,或者别人给你起的外号。
就水浒传中而言,林冲,外号豹子头。林冲和豹子头指的都是林冲本人,不会因为换个名字就变了个人。
与c语言的比较
此时小伙伴们就会发现,引用的符号跟c语言中的取地址符一样。
这是因为 C++为了避免引入太多的运算符,复用了C语言中的一些符号,比如cin与cout的 >> 和 <<。平时注意使用的区分,避免造成混淆。
引用的特性
- 一个变量可以有多个引用
- 引用一旦引用了一个实体,再不能改变引用
- 引用在定义时必须初始化
#include<iostream>
using namespace std;
int main()
{
int a = 10;
//int& ra;
// 编译报错:“ra”: 必须初始化引⽤
int& b = a; //正确的引用
int c = 20;
b = c;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
注意引用不能代替指针,因为引用不能改变指向。
注意:Java中引用可以改变指向
在定义数据结构时必须使用指针,如链表,树等节点间需要使用指针连接。
如果使用引用代表指针,将下一个节点的引用存入当前节点时,当删除一个节点,需要改变删除节点的前一个节点的引用指向,但C++不允许引用对象改变。
引用的使用
- 引用在实践中主要是用于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象的同时改变被引用对象。
- 引用传参跟指针传参的功能是类似的,引用传参相对方便一点。
- 引用返回值的场景相对复杂,此处不做过多解释。
- 引用和指针在实践中相辅相成,功能有重叠性,但各有特点,互相不可替代。C++的引用跟其他语言的引用差别具有很大区别。例如前面提到的C++引用定义后不可以改变指向,Java的引用可以改变指向。
- 一些主要用C语言代码实现版本数据结构教材中,使用C++引用代替指针传参,目的是简化程序,避开复杂的指针。
引用做参数
如何理解上文的减少拷贝提高效率很改变引用对象?
对于c语言中的交换两个数的函数,C++实现如下
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x <<" " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
该处的 rx 在接收实参时完成初始化
该处 rx 是 x 的别名,ry 是 y 的别名。根据引用的概念可以知道,改变 rx 时也会改变 x (改变引用对象时也会改变被引用对象),所以 y 同时也会被改变。
减少拷贝在于 实参传给形参时,传递的是实参的一份拷贝 (这也是就是为何要使用指针直接改变值,C++中使用的是引用),如果传递的是一个非常大的结构体,就会很浪费空间和时间,使用引用和指针时只需要复制地址,所占空间比较小。
对于大多数C语言数据结构教材,大家应该都见过
typedef struct ListNode
{
int val;
struct ListNOde* next;
}LTNode,*PNode;
// ⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序,避开复
杂的指针
void SeqPushBack(SLT& sl, int x);
// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
如果此处使用c语言编写,就应该传入二级指针,但大多数都使用的C++
在这个代码中, LTNode 代表结构体 ,PNode 代表结构体指针
可以理解为 LTNode* 与 PNode 相同
常见写法可替换为 void ListPushBack(PNode& pheada, int x);
void PushBack(LTNode& SL , int x)
{
.....
}
int main()
{
SLT slt;
PushBack(slt , 1);
}
代码中的 SL即是slt的别名
上述代码便可以通过改变SL引用的对象,从而改变slt的内容。
引用做返回值
如果不用引用做返回值
对于传值返回的函数,C/C++会将结果先存到临时开辟的空间中,这样做的原因是防止函数返回值在函数栈帧销毁后,找不到返回值。
如下图
为了避免这种情况,传值返回的函数,返回值会存到临时变量中。
当使用引用做为返回值时
引用做返回值,函数调用表达式,返回值是返回对象别名。
可以类比指针。
const 引用
const 干什么用的
可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用的过程中可以缩小,但是不能放大。
const int a = 10; //此时a 的内容不能被修改
//int& ra = a;
权限不能放大
const int& ra = a;
//权限可以平移
int b = 10;
const int& rb = b;
//权限可以缩小
//不涉及权限的放大和缩小 赋值拷贝,不是同一块空间
int rra = a;
所谓临时对象减少编译器需要一个空间暂存表达式的求值结果临时创建的一个未命名的对象,C++中把这个未命名对象叫做临时对象。
类似于下述情况,都是有关于临时对象的,应该注意平时的使用:
int a = 10;
const int& ra = a;
int& rra = 30; 错误,因为30是个常量,不能被修改,所以应改为
const int& rra = 30; //正确
double d = 2.2;
int c = d;
int& rc = d; //错误
const int& rc = d; //正确
int& e = a*3; // 错误,运算表达式的结果是临时对象
const int& e = a*3; //正确
总结下上面会出现临时对象的场景:
1. 运算表达式的结果,需要临时对象存储
2.类型转换时,中间会产生临时对象
const引用的用处
如果不使用 const 对参数进行限制,就会出现
此时只需要在参数位置加入 const 进行限制
引用和指针的区别(重点理解)
- 语法概念上,引用是一个变量取别名不开空间。指针是存储一个变量空间,要开空间。
- 引用在定义时必须初始化。指针不做要求。
- 引用在初始化引用了一个对象后,不能在引用其他对象。指针可以不断改变指向对象。(注意C++中不能改变,但Java中可以改变)
- 引用可以直接访问指向对象。指针需要解引用才能访问指向对象。
- 引用不能为空,它必须始终引用某个有效的对象或函数。指针可以指向空即nullptr,此时指针表示不指向任何有效的对象。