【C++】C++ 引用详解 ① ( 变量的本质 - 引入 “ 引用 “ 概念 | 引用语法简介 | 引用做函数参数 | 复杂类型引用做函数参数 )





一、变量的本质 - 引入 " 引用 " 概念



" 引用 " 语法 是 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 . . .

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/han1202012/article/details/132416680