前言
前面带大家学习了函数重载等C++基础,这期继续C++基础的学习:引用。
注:
最好是学完了C语言,并学过一些初阶的数据结构。
目录
Part1:何为引用
1.一个引子
2.概念
3.特征
4.常引用
Part2:使用场景
1.做参数
2.做返回值
Part3:有关引用的探讨
1.传值,传引用效率比较
2.引用和指针的区别
正文
Part1:何为引用
1.一个引子
不知道大家听没听过这个梗:
❓“抓捕周树人跟我鲁迅有什么关系”❓
这是《楼外楼》当中的一个桥段:
抓捕周树人跟我鲁迅有什么关系? 在我们看来,当然有关系啦,鲁迅是周树人的笔名之一啊。
其实周树人还有很多很多笔名,大家可以自行查阅... ...
不管先生有多少笔名,都是指的一个人:周树人。
☝️这就有引用的意思,接下来进入正题:
2.概念
你单看引用,想到的是语文课上的一种手法:此处运用了引用的手法,引用了... ... 的话,... ...
这种理解方式是有大偏差的,应该理解为 “起别名” :
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
重点是 不会为别名开辟内存空间。
引用的使用:
类型& 引用变量名(对象名) = 引用实体;
☝️你以为是取地址? 错,这是放在数据类型后面的 & ,🐟取地址区分开!
void TestRef() { int a = 10; int& ra = a; // 定义引用类型 printf("%p\n", &a); // 输出a的地址 printf("%p\n", &ra); // 输出ra的地址 }
👁️🗨️输出结果:
可见 a 与 ra 是同一个变量,就像周树人和鲁迅指的是同一个人一样。
❓那我这样用行不行呢?
int a = 10; char& ra = a; // 定义引用类型
❌可以看到报错:
也就是说:
引用类型必须和引用实体是同种类型的
3.特征
int& ra = a; int& rra = a;
❓直接这样起别名会怎么样?
❌报错:
所以注意 引用在定义时必须初始化。
像周树人那样有多个笔名,我们也可以起多个别名:
1. int& ra = a; 2. int& rra = a;
还有一个潜在的特征就是 一个引用只能对应一个实体,就像鲁迅就是指的是周树人。
📝特征总结:
• 引用在定义时必须初始化;
• 一个变量可以有多个引用;
• 引用一旦引用一个实体,再不能引用其他实体
4.常引用
什么是常引用呢? 就是面向常量的引用嘛
给常量起别名那些事:
1. const int a = 10; 2. int& ra = a;
❌报错:
也就是说给常量起别名时要用常引用:
1. const int a = 10; 2. // int& ra = a; // 该语句编译出错,a为常量 3. const int& ra = a; // 正确的
倒过来也一样:
int& b = 10; const int& b = 10;
❌报错:
const int& b = 10; // 正确的 // int& b = 10; // 该语句编译出错,b为常量 const int& b = 10;
所以对待常量,引用也要使用常引用。
Part2:使用场景
学了引用,终归是要用于实践的,那么引用的使用场景有哪些呢?
1.做参数
没错,引用可以像指针那样做参数:
🌰例子:
void Swap(int& x, int& y) { int temp = x; y = x; y = temp; }
这是一个经典的交换函数,我们再看看指针版本的:
void Swap(int* x, int* y) { int temp = *x; *y = *x; *y = temp; }
但从形式上看,引用版的的却简洁,省去了解引用的步骤。
从效率上来看,也是引用版更胜一筹,因为引用变量不占用内存空间。
📝所以传参时大家可以考虑引用。
2.做返回值
先给你一段代码体会一下:
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); cout << ret << endl; return 0; }
引用是不占有内存的,直接返回 c 的别名
说到这里,你应该反应过来了,这段代码是有问题的:
❌错误:
① 存在非法访问,当 Add 函数调用结束,栈帧销毁,c 的空间还给操作系统,而 c 的引用被 ret 接收,还是会去访问 c 的空间;
② 如果 Add 的栈帧销毁后,空间被清理,c 取到的就是随机值,紧接着 ret 接收的就是随机值。当然取决于编译器啦 ~
再来看看下面这段代码:
int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); cout << ret << endl; Add(10, 20); cout << ret << endl; return 0; }
👁️🗨️输出结果:
嘿?我寻思我也没动 ret 啊,怎么变了?
🪄 因为再次调用了 Add 函数,这次调用覆盖了之前已经销毁的栈帧,由于返回的是 c 的引用,而不是 c 本身,所以 ret 被覆盖为最新的值。
这个角度来看,使用引用作返回值条件还挺苛刻。
📝总结:
不要轻易使用引用作为返回值;
如果函数返回时,出了函数作用域,返回对象还没还给系统,则可以使用引用返回,
如果已经还给系统了,就老老实实使用传值返回。