欢迎来到 Claffic 的博客

专栏:《是C++,不是C艹》

前言:

前面带大家学习了函数重载等C++基础,这期继续C++基础的学习:引用。

注:

你最好是学完了C语言,并学过一些初阶的数据结构。


(没有目录) ヽ( ̄ω ̄( ̄ω ̄〃)ゝ

Part1:何为引用

1.一个引子

不知道大家听没听过这个梗:

❓“抓捕周树人跟我鲁迅有什么关系”❓

这是《楼外楼》当中的一个桥段:

一个军官拿着逮捕令前去抓捕鲁迅的时候,鲁迅淡定地看了眼逮捕令,随即表示军官要抓的是周树人,和自己鲁迅没有关系。在鲁迅说完之后,军官居然真的相信并离开了。

鲁迅先生巧妙地用别名自保,实在是高哇(那军官也是没文化)。

抓捕周树人跟我鲁迅有什么关系? 在我们看来,当然有关系啦,鲁迅是周树人的笔名之一啊。

其实周树人还有很多很多笔名,大家可以自行查阅… …

不管先生有多少笔名,都是指的一个人:周树人。

☝️这就有引用的意思,接下来进入正题:

2.概念

你单看引用,想到的是语文课上的一种手法:此处运用了引用的手法,引用了… … 的话,… …

这种理解方式是有大偏差的,应该理解为 “起别名” :

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

重点是 会为别名开辟内存空间

引用的使用:

类型& 引用变量名(对象名) = 引用实体;

☝️你以为是取地址? 错,这是放在数据类型后面的 &,取地址区分开!

void TestRef(){int a = 10;int& ra = a; // 定义引用类型printf("%p\n", &a); // 输出a的地址printf("%p\n", &ra); // 输出ra的地址}

️‍️输出结果:

可见 ara 是同一个变量,就像周树人和鲁迅指的是同一个人一样。

❓那我这样用行不行呢?

int a = 10;char& ra = a; // 定义引用类型

❌可以看到报错:

也就是说:

引用类型必须和引用实体同种类型

3.特征

int a = 10;int& ra;

❓直接这样起别名会怎么样?

❌报错:

所以注意引用在定义时必须初始化。

像周树人那样有多个笔名,我们也可以起多个别名

int& ra = a;int& rra = a;

还有一个潜在的特征就是 一个引用只能对应一个实体,就像鲁迅就是指的是周树人。

特征总结:

• 引用在 定义时必须初始化; • 一个变量可以有多个引用; • 引用一旦引用一个实体,再不能引用其他实体

4.常引用

什么是常引用呢? 就是面向常量的引用嘛

给常量起别名那些事:

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

❌报错:

也就是说给常量起别名时要用常引用:

const int a = 10;// int& ra = a; // 该语句编译出错,a为常量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 被覆盖为最新的值。

这个角度来看,使用引用作返回值条件还挺苛刻。

总结:

不要轻易使用引用作为返回值;

如果函数返回时,出了函数作用域,返回对象还没还给系统,则可以使用引用返回, 如果已经还给系统了,就老老实实使用传值返回。

Part3:有关引用的探讨

1.传值,传引用效率比较

❓你可以先考虑下:传值和传引用作参数/返回值,谁的效率更高?

当然是传引用返回

传值作参数/返回值,不是直接传递实参/返回变量,而是传递实参/返回变量的一份临时拷贝,因此直接传递实参/返回变量效率低下,参数/返回值越大越明显。

这里不妨测试一下两者的效率:

#include#include using namespace std;struct A { int a[10000]; };void TestFunc1(A a) {}void TestFunc2(A& a) {}void TestRefAndValue(){A a;// 以值作为函数参数size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作为函数参数size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2(a);size_t end2 = clock();// 分别计算两个函数运行结束后的时间cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;}int main(){TestRefAndValue();return 0;}

这段代码以函数结束时间来表示传参的效率

️‍️输出结果:

不难看出,引用传参的效率杠杠滴。

总结:

① 传引用作参数/返回值,有些场景下面,可以提高性能(大对象+深拷贝对象)。

② 传引用作参数/返回值,输出型参数和输出型返回值。

引用的使用特别多,是学习的重点。

2.引用和指针的区别

在开头的概念中就说到:

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

引用:

int main(){int a = 10;int& ra = a;cout << "&a = " << &a << endl;cout << "&ra = " << &ra << endl;return 0;}

️‍️输出结果:

不过这只是语法的层面上,那底层实现上呢?

我们可以两者汇编代码的区别:

int main(){int a = 10;int& ra = a;ra = 20;int* pa = &a;*pa = 20;return 0;}

转到反汇编:

惊奇的是,反汇编中引用和指针的逻辑操作是相同的!

⚔️所以我们可以得出这样的结论:

引用是按照指针的方式来实现的,在底层实现上是有空间的。

明白了这点之后,再说一下引用和指针的区别:

引用概念上定义一个变量的别名,指针存储一个变量地址;

引用在定义时必须初始化,指针没有要求;

这个好理解的,给变量起别名的前提是给那个变量起呀,而指针就不需要。

引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;

没有NULL引用,但有NULL指针;

sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;

有多级指针,但是没有多级引用;

指针有一级指针,二级指针,三级指针等,而引用就是标定一个实体,不能再给别名起别名。

访问实体方式不同,指针需要显式解引用,引用编译器自己处理

这在一定程度上表明了引用的便利之处,果然C++比C语言省心呐。

引用比指针使用起来相对更安全。

为什么这么说呢,因为指针当中有野指针,这是一种非常危险的存在,而引用就不会有这样的危险因素。


总结:

这篇博客从引用的基础概念开始,经历了引用的使用和相关探讨,相信你对引用有了一定的认知,这方面是C++的重点内容,要理解呀。

码文不易

如果你觉得这篇文章还不错并且对你有帮助,不妨支持一波哦