一、变量的本质 - 引入 " 引用 " 概念
" 引用 " 语法 是 C++ 语言中 特有的 , 在 C 语言中是没有 引用 这个概念的 ;
1、变量的本质 - 内存别名
分析 引用 之前 , 先回顾下 变量 :
在 【C 语言】变量本质 ( 变量概念 | 变量本质 - 内存空间别名 | 变量存储位置 - 代码区 | 变量三要素 ) 博客中 , 介绍了变量的本质 :
变量 的本质是 内存空间 的 " 别名 " , 简称 " 内存别名 " , 相当于 " 门牌号 " ;
C 语言 / C++ 语言 的 程序 , 通过 变量 来申请 内存空间 , 并为该 内存空间 命名 , 名称就是变量名 ;
下面的代码中 , 定义变量 a , 就是在 栈内存 中申请了 4 字节内存 , 这 4 字节连续内存的别名是 a , 为该变量赋值 10 , 就是将 10 存储到 4 字节内存中 ;
int a = 10;
通过 " 变量名称 " 可以使用 变量名 代表的 连续内存空间 , 之后使用变量 a 进行计算 , 就是 使用 变量 a 表示的 4 字节内存中的数据进行计算 ;
2、引入 " 引用 " 概念 - 已定义变量的内存别名
下面讨论下 , 上述 变量 a 是 4 字节连续内存空间的别名 , 那么 这段 4 字节的内存空间 只能有一个内存别名吗 ? 还是可以有多个内存别名 ?
答案是可以的 , 如果想要为 上述 4 字节内存空间再次 赋予 一个 " 内存别名 " , 就需要使用 " 引用 " 了 ;
" 引用 " 就是 已定义 变量 的 内存别名 ;
- 第一次为 一块内存 赋予 别名 , 是 定义变量 的时候 ;
- 第二次再为 该内存 赋予 别名 , 就是 获取该变量的 " 引用 " ;
3、" 引用 " 的优点
C++ 语言中的 引用 是特殊的变量 , 通过引用可以访问已经存在的变量 ;
使用 " 引用 " 的优点 :
- 提高访问效率 : 向 函数 传递参数时 , 使用引用可以减少消耗 , 类似于传入指针 , 如果传入一个较大的数组 , 需要拷贝整个数组作为变量副本 , 拷贝会消耗很多性能 ;
- 提高代码可读性 : 引用使用时 , 类似于 一级指针 , 使用引用期间 , 不需要 使用 取地址符 & 和 指针符号 * , 提高了代码可读性 和 可维护性 ;
- 函数返回值 : 函数引用参数 可以作为 返回值使用 ;
二、引用语法简介
1、语法说明
" 引用 " 语法如下 :
类型& 引用名称 = 变量;
& 符号建议紧贴类型写 , 与 引用名称 使用空格隔开 ;
( 指针符号 * 建议也是紧贴 指针类型 , 与指针名称使用空格隔开 , 如 : int* p = NULL;
)
引用 定义后 , 可以当做变量使用 ;
通过引用 , 可以操作变量 , 访问 , 修改 引用 , 变量也会进行相应修改 ;
使用引用作为函数参数时 ,
- 传入的实参不需要使用取地址符获取 , 直接将变量传入函数即可 ;
- 在函数中 访问引用 时 , 不需要使用指针 , 直接使用引用访问传入的变量 ;
代码示例 :
// 定义变量 a , 变量本质是内存别名
int a = 10;
// 定义引用 b , 是变量 a 的别名
int& b = a;
// 通过引用修改变量的值
b = 100;
引用是 C++ 的概念 , 在 C 语言中不能使用引用 ;
上述代码在 C 语言中实现 是完全不同的 , 下面是 上述代码在 C 语言中的实现 :
// 定义变量 a , 变量本质是内存别名
int a = 10;
// 获取 变量 a 的地址 , 赋值给 指针常量 b
// 指针常量 是 常量 - 指针本身不能修改 , 常量指针 是 指针 - 指向常量的指针
// 左数右指 , const 在 指针 * 的右边 , 指针 是常量 , 指针的指向不能更改
int* const b = &a;
// 通过 指针常量 修改 指针指向的内存空间的值
// 指针指向不能修改 , 指向的内存中的内容可以修改
*b = 100;
在上述代码中 ,
- 首先 , 获取 变量 a 的地址 , 赋值给 指针常量 b ;
- 指针常量 是 常量 - 指针本身不能修改 ;
- 常量指针 是 指针 - 指向常量的指针 ;
- 左数右指 , const 在 指针 * 的右边 , 指针 是常量 , 指针的指向不能更改 ;
- 然后 , 通过 指针常量 修改 指针指向的内存空间的值 ;
- 指针指向的地址不能修改 ;
- 指针指向的内存中的内容可以修改 ;
2、代码示例 - 引用的定义和使用
下面的代码中 , 引用 b 是 变量 a 的别名 , 通过 引用 b 可以访问 变量 a 的内存空间 ;
代码中同时打印 引用 b 和 变量 a , 都可以打印出 变量值 10 ;
修改 引用 b 的值 , 变量 a 的值也会被修改 ;
代码示例 :
// 包含 C++ 头文件
#include "iostream"
// 使用 std 标准命名空间
// 该命名空间中 , 定义了很多标准定义
using namespace std;
// 导入 C 头文件
#include <stdio.h>
int main()
{
// 定义变量 a , 变量本质是内存别名
int a = 10;
// 定义引用 b , 是变量 a 的别名
int& b = a;
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 通过引用修改变量值
b = 100;
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 控制台暂停 , 按任意键继续向后执行
//system("pause");
return 0;
}
执行结果 :
a = 10, b = 10
a = 100, b = 100
三、引用做函数参数
1、普通引用必须初始化 - 函数参数除外
普通的引用 , 必须要依附于某个变量 , 在定义 " 引用 " 时 , 必须进行初始化 , 否则就会报如下错误 :
引用 变量 x 需要初始值设定项
这里有一种特殊情况 , 在声明时可以不进行初始化 ,
" 引用 " 做 函数 形参 时 , 可以不进行初始化 ;
使用 引用 作为 函数参数 , 与 一级指针 效果相同 , 并且用起来比较简单 , 不需要操作指针 ;
引用 比较符合 Java / C# 语言风格 , 不需要操作繁琐的指针 ;
定义两个变量 :
// 定义变量 a , b , 变量本质是内存别名
int a = 10, b = 20;
写一个函数 , 交换这两个变量的值 ;
2、代码示例 - 使用普通变量作为参数 ( 无法实现变量交换 )
下面的代码中 , 定义的交换函数 , 传入的形参是普通变量 ;
参数是普通变量 , 实参就是变量的副本 , 变量的作用域仅限于函数内 , 无法传递到函数外部 , 外部的变量无法被改变 ;
代码示例 :
// 包含 C++ 头文件
#include "iostream"
// 使用 std 标准命名空间
// 该命名空间中 , 定义了很多标准定义
using namespace std;
// 导入 C 头文件
#include <stdio.h>
// 交换 a 和 b 的值
// 该函数无法实现交换 , 因为传入的实参只是副本
// 并不能真实的改变传入的值
void swap(int a, int b)
{
int c = 0;
c = a;
a = b;
b = c;
}
int main()
{
// 定义变量 a , b , 变量本质是内存别名
int a = 10, b = 20;
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 传入变量副本 : 交换 a 和 b 的值 , 交换失败
swap(a, b);
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
}
执行结果 :
a = 10, b = 20
a = 10, b = 20
3、代码示例 - 使用指针变量作为参数 ( C 语言中实现变量交换的方法 )
在下面的代码中 , 使用 C 语言的方式实现了 变量交换函数 ;
函数参数接收 指针变量 作为 参数 , 传入的实参是变量的地址 ;
在函数内部 , 访问变量需要通过 指针 * 符号进行 ;
这样可以实现 外部变量 的数值交换 , 但是 使用 指针 * 进行操作 , 代码十分复杂繁琐 , 不易理解 ;
代码示例 :
// 包含 C++ 头文件
#include "iostream"
// 使用 std 标准命名空间
// 该命名空间中 , 定义了很多标准定义
using namespace std;
// 导入 C 头文件
#include <stdio.h>
// 交换 a 和 b 的值
// C 语言中可以使用该方法
void swap(int* a, int* b)
{
int c = 0;
c = *a;
*a = *b;
*b = c;
}
int main()
{
// 定义变量 a , b , 变量本质是内存别名
int a = 10, b = 20;
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 传入指针地址 : 交换 a 和 b 的值 , 交换成功
swap(&a, &b);
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
}
执行结果 :
a = 10, b = 20
a = 20, b = 10
4、代码示例 - 使用引用作为参数 ( C++ 语言中实现变量交换的方法 )
在下面的代码中 , 使用引用作为函数参数 , 也实现了变量交换 ;
C++ 中的引用使用非常简单 , 没有使用指针进行操作 ;
在使用引用时 , 可以看到 引用的效果 , 实际上等同于一级指针 ;
使用引用作为函数参数时 , 传入的实参不需要使用取地址符获取 , 直接将变量传入函数即可 , 在函数中获取引用的值时 , 不需要使用指针 , 直接使用引用访问传入的变量 ;
代码示例 :
// 包含 C++ 头文件
#include "iostream"
// 使用 std 标准命名空间
// 该命名空间中 , 定义了很多标准定义
using namespace std;
// 导入 C 头文件
#include <stdio.h>
// 交换 a 和 b 的值
// C++ 中推荐的方法
// 使用引用作为函数参数
void swap(int& a, int& b)
{
int c = 0;
c = a;
a = b;
b = c;
}
int main()
{
// 定义变量 a , b , 变量本质是内存别名
int a = 10, b = 20;
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 传入引用本 : 交换 a 和 b 的值 , 交换成功
swap(a, b);
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
}
执行结果 :
a = 10, b = 20
a = 20, b = 10
5、代码示例 - 完整代码示例
完整代码示例 :
// 包含 C++ 头文件
#include "iostream"
// 使用 std 标准命名空间
// 该命名空间中 , 定义了很多标准定义
using namespace std;
// 导入 C 头文件
#include <stdio.h>
// 交换 a 和 b 的值
// 该函数无法实现交换 , 因为传入的实参只是副本
// 并不能真实的改变传入的值
void swap1(int a, int b)
{
int c = 0;
c = a;
a = b;
b = c;
}
// 交换 a 和 b 的值
// C 语言中可以使用该方法
void swap2(int* a, int* b)
{
int c = 0;
c = *a;
*a = *b;
*b = c;
}
// 交换 a 和 b 的值
// C++ 中推荐的方法
void swap3(int& a, int& b)
{
int c = 0;
c = a;
a = b;
b = c;
}
int main()
{
// 定义变量 a , b , 变量本质是内存别名
int a = 10, b = 20;
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 传入变量副本 : 交换 a 和 b 的值 , 交换失败
swap1(a, b);
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 传入指针 : 交换 a 和 b 的值 , 交换成功
swap2(&a, &b);
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 传入引用 : 交换 a 和 b 的值 , 交换成功
swap3(a, b);
// 打印 变量 a 和 引用 b 的值
printf("a = %d, b = %d\n", a, b);
// 控制台暂停 , 按任意键继续向后执行
//system("pause");
return 0;
}
执行结果 :
a = 10, b = 20
a = 10, b = 20
a = 20, b = 10
a = 10, b = 20
四、复杂类型引用做函数参数
1、复杂类型参数的三种传递方式
定义一个结构体类型 , 想要传递结构体对象到函数中 , 有三种方式 ;
// 定义一个结构体
// C++ 中结构体就是类
struct Student
{
char name[64];
int age;
};
在栈内存中先创建该结构体对象 , 为该对象赋值 ;
Student s;
s.age = 18;
I 、传递结构体对象本身
第一种方式 , 直接传递结构体对象本身 ,
- 函数传递 : 这种方式传递的是 结构体 对象的副本 , 需要拷贝对象然后将拷贝副本作为实参传递给函数 , 拷贝的过程非常消耗性能 ;
- 参数访问 : 传入的参数在函数中正常访问 ,使用 . 访问结构体成员 ;
- 参数修改 : 修改该参数 , 不会影响外部结构体对象的值 , 因为修改的是拷贝后的副本 ;
// 直接传入结构体类对象本身
void printStudent1(Student s)
{
// 使用变量 , 直接使用 . 访问结构体成员
cout << "printStudent1 开始执行 age : " << s.age << endl;
s.age = 19;
}
II 、传递结构体指针
第二种方式 , 传递结构体 指针 ,
- 函数传递 : 这种方式传递的是 结构体 指针 , 实际上是指针的副本 , 几乎不消耗性能 ;
- 参数访问 : 传入的 指针 参数 在函数中 使用 -> 访问结构体成员 ;
- 参数修改 : 通过指针 修改该参数 , 外部的结构体对象也会被修改 ;
// 传入结构体类对象指针
void printStudent2(Student* s)
{
// 通过 问结构体指针 访问成员需要使用 -> 访问
cout << "printStudent2 开始执行 age : " << s->age << endl;
s->age = 20;
}
III 、传递结构体引用
第三种方式 , 传递结构体 引用 ,
- 函数传递 : 这种方式传递的是 结构体 引用 , 引用只是变量的一个别名 , 几乎不消耗性能 ;
- 参数访问 : 传入的 引用 参数 在函数中 使用 . 访问结构体成员 , 与变量用法一样 ;
- 参数修改 : 通过指针 修改该参数 , 外部的结构体对象也会被修改 ;
// 传入结构体类对象指针
void printStudent2(Student* s)
{
// 通过 问结构体指针 访问成员需要使用 -> 访问
cout << "printStudent2 开始执行 age : " << s->age << endl;
s->age = 20;
}
2、代码示例 - 使用三种传递方式传递参数
代码示例 :
// 包含 C++ 头文件
#include "iostream"
// 使用 std 标准命名空间
// 该命名空间中 , 定义了很多标准定义
using namespace std;
// 导入 C 头文件
#include <stdio.h>
// 定义一个结构体
// C++ 中结构体就是类
struct Student
{
char name[64];
int age;
};
// 直接传入结构体类对象本身
void printStudent1(Student s)
{
// 使用变量 , 直接使用 . 访问结构体成员
cout << "printStudent1 开始执行 age : " << s.age << endl;
s.age = 19;
}
// 传入结构体类对象指针
void printStudent2(Student* s)
{
// 通过 问结构体指针 访问成员需要使用 -> 访问
cout << "printStudent2 开始执行 age : " << s->age << endl;
s->age = 20;
}
// 传入结构体类对象引用
void printStudent3(Student& s)
{
// 使用 引用 跟普通变量用法相同, 不需要使用 -> 访问
cout << "printStudent3 开始执行 age : " << s.age << endl;
s.age = 21;
}
int main()
{
Student s;
s.age = 18;
// 传入对象 消耗性能
printStudent1(s);
cout << "printStudent1 执行完毕 age : " << s.age << endl;
// 传入指针 需要取地址
printStudent2(&s);
cout << "printStudent2 执行完毕 age : " << s.age << endl;
// 传入引用 直接传入
printStudent3(s);
cout << "printStudent3 执行完毕 age : " << s.age << endl;
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
}
执行结果 :
printStudent1 开始执行 age : 18
printStudent1 执行完毕 age : 18
printStudent2 开始执行 age : 18
printStudent2 执行完毕 age : 20
printStudent3 开始执行 age : 20
printStudent3 执行完毕 age : 21
Press any key to continue . . .