一、引用的意义
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 语言表达 , 就是
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 结构体 , 占
字节的 内存空间 , 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 语言中的 取地址 传入函数 , 在函数内部使用指针访问实参 ;