【C++】C++ 引用详解 ② ( 引用的意义 | 引用本质分析 | 引用的常量特征 | 引用所占内存空间与指针相同 | 引用在 C++ 编译器实现 | 引用编译时会自动翻译为常量指针 )

2023-10-15 16:31:36 浏览数 (1)

一、引用的意义

1、意义说明

" 引用 " 的 意义 :

  • 引用 作为 已存在 变量 的 别名 , 其 效果 等同于 一级指针 ;
  • 引用 使用 简单 , 不需要像指针一样 , 使用取地址符 获取地址 , 使用 指针符号 * 访问数据 ;

2、引用与指针对比

引用 与 指针示例 : 下面的两个函数 , 分别使用 指针 和 引用 作为参数 ,

  • 二者实现了相同的功能 , 性能也相同 ;
  • 但是使用引用 , 可以像操作变量一样操作外部元素 , 不需要使用指针和取地址符 , 其 可读性 , 可维护性 大大提升 ;

指针示例 :

代码语言:javascript复制
// 交换 a 和 b 的值
// C 语言中可以使用该方法
void swap2(int* a, int* b)
{
	int c = 0;
	c = *a;
	*a = *b;
	*b = c;
}

引用示例 :

代码语言:javascript复制
// 交换 a 和 b 的值
// C   中推荐的方法
void swap3(int& a, int& b)
{
	int c = 0;
	c = a;
	a = b;
	b = c;
}

在 C 语言中 , 用好引用 , 可以写出 高质量 , 高性能 , 高可读行 , 高可维护性 的代码 ;

二、引用本质分析


1、引用的常量特征

在下面的代码中 , 先定义变量 a , 然后定义 已存在变量 a 的引用 b ;

代码语言:javascript复制
// 定义变量 a
int a = 10;

// 定义变量 a 的引用 b
int& b = a;

定义 引用 时 ,

  • 如果 单独 定义引用 , 必须进行初始化操作 ;
  • 如果 在函数参数 中使用引用 , 可以不进行初始化 ;

上述性质 , 类似于 " 常量 " , 说明 引用 具有 " 常量 " 的特征 ;

上述 int& b = a; 代码 , 如果使用 C 语言表达 , 就是

代码语言:javascript复制
int* const b = &a;

定义的是一个指针常量 , 该指针是常量 , 指针本身 也就是 指针指向的地址 不可更改 ;

2、引用和变量都是相同的内存空间的别名

将上述 变量 a 和 引用 b 的地址 , 打印出来 ,

打印出的 变量 a 和 引用 b 的地址是相同的 , 说明这两个都是内存空间的别名 ;

代码如下 :

代码语言:javascript复制
// 包含 C   头文件
#include "iostream"

// 使用 std 标准命名空间
//		该命名空间中 , 定义了很多标准定义
using namespace std;

// 导入 C 头文件
#include <stdio.h>

int main()
{
	// 定义变量 a
	int a = 10;

	// 定义变量 a 的引用 b
	int& b = a;

	// 打印变量 a 和 引用 b 的地址
	printf("&a = %d, &b = %dn", &a, &b);


	// 控制台暂停 , 按任意键继续向后执行
	system("pause");
    return 0;
}

执行结果为 :

代码语言:javascript复制
&a = 12516116, &b = 12516116
Press any key to continue . . .

3、引用所占内存空间与指针相同

引用 和 变量 都是相同的内存空间的别名 , 引用 本身 也占用内存空间 ,

引用 所占 的 内存空间 , 与 指针 所占的内存空间 是相同的 ;

验证 引用 所占的内存空间很简单 , 只需要在 结构体 中定义引用 , 然后获取该结构体的大小即可 ;

定义如下结构体 :

代码语言:javascript复制
struct Student
{
	int age;
	int& a;
	int& b;
};

使用 sizeof 函数 , 获取上述结构体的大小 ;

使用下面的代码验证 :

代码语言:javascript复制
// 包含 C   头文件
#include "iostream"

// 使用 std 标准命名空间
//		该命名空间中 , 定义了很多标准定义
using namespace std;

// 导入 C 头文件
#include <stdio.h>

struct Student
{
	int age;
	int& a;
	int& b;
};

int main()
{
	printf("sizeof(Student) = %dn", sizeof(Student));


	// 控制台暂停 , 按任意键继续向后执行
	system("pause");
    return 0;
}

执行结果 :

代码语言:javascript复制
sizeof(Student) = 12
Press any key to continue . . .

上述 Student 结构体 , 占

12

字节的 内存空间 , int 类型已知占 4 字节 , 剩余的 引用 a 和 b 各占 4 字节 , 与指针所占的内存空间相同 ;

三、引用在 C 编译器实现


1、C 引用是常量指针

综合上述引用的特征 :

  • 引用具有常量的特征 , 是一个常量 ;
  • 引用和变量都是相同的内存空间的别名 , 其地址都指向内存空间 ;
  • 引用本身也占用内存空间 , 占用大小与指针相同 ;

综合上面的三种特点 , C 语言编译器 中 , 引用的本质是 :

代码语言:javascript复制
类型* const 指针名称;

指针 ;

引用在 C 语言内部是 常量指针 , 下面 C 语言的 " 引用 "

代码语言:javascript复制
引用类型& 引用名称

等同于 下面的 C 语言的 " 常量指针 "

代码语言:javascript复制
指针类型* const 指针名称

C 语言中的 引用 , 其在 编译器中的实现 就是 " 常量指针 " , 因此 引用 占用的内存空间与 指针 占用的内存空间 相同 ;

2、引用编译时会自动翻译为常量指针

C 语言 为了提高 引用 的实用性 , 代码的可读性 , 隐藏了 引用 也会占用存储空间的 事实 , 该事实不会影响 开发者进行编程 , 只是对理解引用本质造成了困难 ;

C 语言中 的 函数中 , 使用 引用 作为函数参数 , 如下代码 :

代码语言:javascript复制
void swap(int& a, int& b)
{
	int c = 0;
	c = a;
	a = b;
	b = c;
}

C 编译器编译上述代码时 , 会自动将上述代码翻译为 :

代码语言:javascript复制
void swap(int* a, int* b)
{
	int c = 0;
	c = *a;
	*a = *b;
	*b = c;
}

3、引用与指针做形参分析

在向 void swap(int* a, int* b) 函数 , 传入参数时 , 如果是 指针做参数 , 传入的参数必须是地址 , 需要开发者手动使用 取地址符 & 获取变量地址 , 传递给 函数 做实参 ;

在向 void swap(int& a, int& b) 函数 , 传入参数时 , 如果是 引用做参数 , 开发者 编写代码时 , 传入的是 int 类型变量的值 , C 编译器编译时 , 会自动在 int 类型变量前 添加 取地址符 , 不需要开发者 手动 使用 取地址符 & 获取变量地址 ;

显然 , 后者 开发难度 要小于 前者 ;

4、函数间接赋值 与 引用本质分析

使用函数进行间接赋值 , 需要满足如下三个条件 :

  • 函数中定义 指针类型 的 形参 , 调用函数时 修改函数外的 实参 ;
  • 将 实参 取地址 , 传递给 函数 ;
  • 在函数中 , 通过指针修改 实参的值 , 以达到修改外部变量的效果 ;

如果将 函数 的形参类型 设置为 引用 类型 , 也能达到 间接赋值 的效果 ;

引用 实际上是 把 间接赋值 的三个条件的后两个条件进行了合并 , C 编译器遇到引用 , 还是需要将 引用 还原为 C 语言中的 取地址 传入函数 , 在函数内部使用指针访问实参 ;

0 人点赞