【C++】“引用“的特性与使用原则

5,393 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、引用的基本介绍

 什么是引用?通俗的讲,引用就是"取别名",我们看下面的例子: 在这里插入图片描述  可以看到,引用并不会开辟新的空间。


二、引用的基本特性

特性①:必须有初始化 在这里插入图片描述 特性②:一个变量可以有多个引用 在这里插入图片描述

可以给别名取别名,但本质上都是对a的引用

特性③:一旦引用了一个实体,就不能引用其他实体

在这里插入图片描述

可以看到,之后b不会再引用实体c,只能对b做赋值修改


三、引用的基本原则

①引用类型和引用实体类型相同

 引用的一个极其重要的基本原则是:对变量实体的读写权限只能缩小不能放大。我们在接下来的例子中来说明这个原则。  引用可以引用常量吗?答案是可以的。但是我们需要加上const来保证读写权限没有被放大: 在这里插入图片描述

✪代码分析  我们来分析这段代码我们需要注意到,引用就是给实体取的别名,二者在地址上是一样的,对别名的修改也会改变实体的值,所以我们在取别名的时候就要保证对引用的读写权限没有扩大。就像上面的例子,10是一个只读变量,如果不加上const,那么就意味着可以通过a来修改常变量10,这显示是不行的。

②引用类型和引用实体类型不同

 引用类型必须和引用实体是同种类型吗?不一定!怎么理解这个问题呢?我们可以类比我们在C语言中学到的隐式类型转化,我们来分析中间的过程:

int main()
{
	double d = 6.66;
	int a = d;
}

 为什么可以将类型为double的变量赋值给类型为int的变量呢?实际上这个赋值过程不是一步到位的,而是存在一个 "中间变量"。这个中间变量接收d的整数部分,最后再把这个中间变量拷贝给a。需要注意的是,这个中间变量具有常属性。引用也是同样的道理: 在这里插入图片描述

✪代码分析  a并不是直接引用d,而是引用这个中间变量。由于中间变量具有常属性,所以前面必须加上const修饰避免扩大读写权限。 在这里插入图片描述  通过取地址我们可以确认a没有引用d,从而也间接说明了中间临时变量的存在。


四、引用的基础应用

①对标传址操作

void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	return 0;
}

 正是由于引用和引用实体存在互相改变的关系,所以我们可以用引用来替代传值操作。拿我们C语言中常写的swap函数举例,这样写起来是不是更爽了呢?  但如果设计的函数中需要传入常数,那么就必须在参数前面加上const,否则存在扩大实体读写权限的风险。经过实际测试,引用的效率和传址的效率是差不多的。  相应的,一些输出型参数也可以用引用来实现,例如:

int* sortArray(int* nums, int numsSize, int* returnSize){

}

 returnSize是我们的输出型参数,在C语言中我们只能通过传址的方式实现,现在用引用也可以实现这样的效果。

②作为返回值

int& count()
{
	static int n = 0;
	n++;
	return n;
}

 用引用作为返回值和传统的方式相比有什么好处呢?我们先来谈谈传统的方式是如何传递返回值的:

  • 首先将预备返回的值存储到临时变量,再将临时变量拷贝给用于接收函数返回值的变量
  • 预备返回的变量小则用寄存器存储,变量大则则创建函数栈帧的时候预先为返回值创建存储空间

 如果我们使用引用,那我们根本就不需要额外的临时空间和额外的拷贝,因为我们是通过引用直接对实体进行操作的,这可以提供程序的效率。 在这里插入图片描述

 但是引用作为返回值只适合返回值出作用域不销毁的情况,如上面static修饰的n变量,如果我们我们引用的变量出函数作用域销毁,那我们实际上通过引用创建了间接的“野指针”

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2): " << ret << endl;
	cout << "Add(1, 2): " << ret << endl;
	return 0;
}

在这里插入图片描述

 ret的值可能是随机值也可能不是,取决于这块空间有没有被重新覆盖和使用。cout函数栈帧的创建覆盖了Add函数的函数栈帧,所以ret的值变成随机值了

再来看几个变式:

int& Add(int a, int b)
{
	static int c;
	c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2): " << ret << endl;
	cout << "Add(1, 2): " << ret << endl;
	return 0;
}

在这里插入图片描述

 变量c用static修饰后,这块空间出函数作用域后不会被销毁,不用担心被其他函数栈帧所覆盖

int& Add(int a, int b)
{
	static int c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2): " << ret << endl;
	cout << "Add(1, 2): " << ret << endl;
	return 0;
}

在这里插入图片描述

注意static语句只在初始化时执行一次