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






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



    " 引用 " 语法 是 C++ 语言中 特有的 , 在 C 语言中是没有 引用 这个概念的 ;


    1、变量的本质 - 内存别名


    分析 引用 之前 , 先回顾下 变量 :

    【C 语言】变量本质 ( 变量概念 | 变量本质 - 内存空间别名 | 变量存储位置 - 代码区 | 变量三要素 ) 博客中 , 介绍了变量的本质 :

    变量 的本质是 内存空间 的 " 别名 " , 简称 " 内存别名 " , 相当于 " 门牌号 " ;

    C 语言 / C++ 语言 的 程序 , 通过 变量 来申请 内存空间 , 并为该 内存空间 命名 , 名称就是变量名 ;

    下面的代码中 , 定义变量 a , 就是在 栈内存 中申请了 4 字节内存 , 这 4 字节连续内存的别名是 a , 为该变量赋值 10 , 就是将 10 存储到 4 字节内存中 ;

    int a = 10;
    
    • 1

    通过 " 变量名称 " 可以使用 变量名 代表的 连续内存空间 , 之后使用变量 a 进行计算 , 就是 使用 变量 a 表示的 4 字节内存中的数据进行计算 ;


    2、引入 " 引用 " 概念 - 已定义变量的内存别名


    下面讨论下 , 上述 变量 a 是 4 字节连续内存空间的别名 , 那么 这段 4 字节的内存空间 只能有一个内存别名吗 ? 还是可以有多个内存别名 ?

    答案是可以的 , 如果想要为 上述 4 字节内存空间再次 赋予 一个 " 内存别名 " , 就需要使用 " 引用 " 了 ;


    " 引用 " 就是 已定义 变量 的 内存别名 ;

    • 第一次为 一块内存 赋予 别名 , 是 定义变量 的时候 ;
    • 第二次再为 该内存 赋予 别名 , 就是 获取该变量的 " 引用 " ;

    3、" 引用 " 的优点


    C++ 语言中的 引用 是特殊的变量 , 通过引用可以访问已经存在的变量 ;


    使用 " 引用 " 的优点 :

    • 提高访问效率 : 向 函数 传递参数时 , 使用引用可以减少消耗 , 类似于传入指针 , 如果传入一个较大的数组 , 需要拷贝整个数组作为变量副本 , 拷贝会消耗很多性能 ;
    • 提高代码可读性 : 引用使用时 , 类似于 一级指针 , 使用引用期间 , 不需要 使用 取地址符 & 和 指针符号 * , 提高了代码可读性 和 可维护性 ;
    • 函数返回值 : 函数引用参数 可以作为 返回值使用 ;




    二、引用语法简介




    1、语法说明


    " 引用 " 语法如下 :

    类型& 引用名称 = 变量;
    
    • 1

    & 符号建议紧贴类型写 , 与 引用名称 使用空格隔开 ;

    ( 指针符号 * 建议也是紧贴 指针类型 , 与指针名称使用空格隔开 , 如 : int* p = NULL; )

    引用 定义后 , 可以当做变量使用 ;

    通过引用 , 可以操作变量 , 访问 , 修改 引用 , 变量也会进行相应修改 ;


    使用引用作为函数参数时 ,

    • 传入的实参不需要使用取地址符获取 , 直接将变量传入函数即可 ;
    • 在函数中 访问引用 时 , 不需要使用指针 , 直接使用引用访问传入的变量 ;

    代码示例 :

    	// 定义变量 a , 变量本质是内存别名
    	int a = 10;
    
    	// 定义引用 b , 是变量 a 的别名
    	int& b = a;
    
    	// 通过引用修改变量的值
    	b = 100;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    引用是 C++ 的概念 , 在 C 语言中不能使用引用 ;

    上述代码在 C 语言中实现 是完全不同的 , 下面是 上述代码在 C 语言中的实现 :

    	// 定义变量 a , 变量本质是内存别名
    	int a = 10;
    
    	// 获取 变量 a 的地址 , 赋值给 指针常量 b 
    	// 指针常量 是 常量 - 指针本身不能修改 , 常量指针 是 指针 - 指向常量的指针 
    	// 左数右指 , const 在 指针 * 的右边 , 指针 是常量 , 指针的指向不能更改
    	int* const b = &a;
    
    	// 通过 指针常量 修改 指针指向的内存空间的值
    	// 指针指向不能修改 , 指向的内存中的内容可以修改
    	*b = 100;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上述代码中 ,

    • 首先 , 获取 变量 a 的地址 , 赋值给 指针常量 b ;
      • 指针常量 是 常量 - 指针本身不能修改 ;
      • 常量指针 是 指针 - 指向常量的指针 ;
      • 左数右指 , const 在 指针 * 的右边 , 指针 是常量 , 指针的指向不能更改 ;
    • 然后 , 通过 指针常量 修改 指针指向的内存空间的值 ;
      • 指针指向的地址不能修改 ;
      • 指针指向的内存中的内容可以修改 ;

    2、代码示例 - 引用的定义和使用


    下面的代码中 , 引用 b 是 变量 a 的别名 , 通过 引用 b 可以访问 变量 a 的内存空间 ;

    代码中同时打印 引用 b 和 变量 a , 都可以打印出 变量值 10 ;

    修改 引用 b 的值 , 变量 a 的值也会被修改 ;


    代码示例 :

    // 包含 C++ 头文件
    #include "iostream"
    
    // 使用 std 标准命名空间
    //		该命名空间中 , 定义了很多标准定义
    using namespace std;
    
    // 导入 C 头文件
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    执行结果 :

    a = 10, b = 10
    a = 100, b = 100
    
    • 1
    • 2

    在这里插入图片描述





    三、引用做函数参数




    1、普通引用必须初始化 - 函数参数除外


    普通的引用 , 必须要依附于某个变量 , 在定义 " 引用 " 时 , 必须进行初始化 , 否则就会报如下错误 :

    引用 变量 x 需要初始值设定项
    
    • 1

    在这里插入图片描述


    这里有一种特殊情况 , 在声明时可以不进行初始化 ,

    " 引用 " 做 函数 形参 时 , 可以不进行初始化 ;


    使用 引用 作为 函数参数 , 与 一级指针 效果相同 , 并且用起来比较简单 , 不需要操作指针 ;

    引用 比较符合 Java / C# 语言风格 , 不需要操作繁琐的指针 ;


    定义两个变量 :

    	// 定义变量 a , b ,  变量本质是内存别名
    	int a = 10, b = 20;
    
    • 1
    • 2

    写一个函数 , 交换这两个变量的值 ;


    2、代码示例 - 使用普通变量作为参数 ( 无法实现变量交换 )


    下面的代码中 , 定义的交换函数 , 传入的形参是普通变量 ;

    参数是普通变量 , 实参就是变量的副本 , 变量的作用域仅限于函数内 , 无法传递到函数外部 , 外部的变量无法被改变 ;


    代码示例 :

    // 包含 C++ 头文件
    #include "iostream"
    
    // 使用 std 标准命名空间
    //		该命名空间中 , 定义了很多标准定义
    using namespace std;
    
    // 导入 C 头文件
    #include 
    
    // 交换 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    执行结果 :

    a = 10, b = 20
    a = 10, b = 20
    
    • 1
    • 2

    在这里插入图片描述


    3、代码示例 - 使用指针变量作为参数 ( C 语言中实现变量交换的方法 )


    在下面的代码中 , 使用 C 语言的方式实现了 变量交换函数 ;

    函数参数接收 指针变量 作为 参数 , 传入的实参是变量的地址 ;

    在函数内部 , 访问变量需要通过 指针 * 符号进行 ;

    这样可以实现 外部变量 的数值交换 , 但是 使用 指针 * 进行操作 , 代码十分复杂繁琐 , 不易理解 ;


    代码示例 :

    // 包含 C++ 头文件
    #include "iostream"
    
    // 使用 std 标准命名空间
    //		该命名空间中 , 定义了很多标准定义
    using namespace std;
    
    // 导入 C 头文件
    #include 
    
    // 交换 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    执行结果 :

    a = 10, b = 20
    a = 20, b = 10
    
    • 1
    • 2

    在这里插入图片描述


    4、代码示例 - 使用引用作为参数 ( C++ 语言中实现变量交换的方法 )


    在下面的代码中 , 使用引用作为函数参数 , 也实现了变量交换 ;

    C++ 中的引用使用非常简单 , 没有使用指针进行操作 ;

    在使用引用时 , 可以看到 引用的效果 , 实际上等同于一级指针 ;

    使用引用作为函数参数时 , 传入的实参不需要使用取地址符获取 , 直接将变量传入函数即可 , 在函数中获取引用的值时 , 不需要使用指针 , 直接使用引用访问传入的变量 ;


    代码示例 :

    // 包含 C++ 头文件
    #include "iostream"
    
    // 使用 std 标准命名空间
    //		该命名空间中 , 定义了很多标准定义
    using namespace std;
    
    // 导入 C 头文件
    #include 
    
    // 交换 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    执行结果 :

    a = 10, b = 20
    a = 20, b = 10
    
    • 1
    • 2

    在这里插入图片描述


    5、代码示例 - 完整代码示例


    完整代码示例 :

    // 包含 C++ 头文件
    #include "iostream"
    
    // 使用 std 标准命名空间
    //		该命名空间中 , 定义了很多标准定义
    using namespace std;
    
    // 导入 C 头文件
    #include 
    
    // 交换 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    执行结果 :

    a = 10, b = 20
    a = 10, b = 20
    a = 20, b = 10
    a = 10, b = 20
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述





    四、复杂类型引用做函数参数




    1、复杂类型参数的三种传递方式


    定义一个结构体类型 , 想要传递结构体对象到函数中 , 有三种方式 ;

    // 定义一个结构体 
    // C++ 中结构体就是类
    struct Student
    {
    	char name[64];
    	int age;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在栈内存中先创建该结构体对象 , 为该对象赋值 ;

    	Student s;
    	s.age = 18;
    
    • 1
    • 2

    I 、传递结构体对象本身


    第一种方式 , 直接传递结构体对象本身 ,

    • 函数传递 : 这种方式传递的是 结构体 对象的副本 , 需要拷贝对象然后将拷贝副本作为实参传递给函数 , 拷贝的过程非常消耗性能 ;
    • 参数访问 : 传入的参数在函数中正常访问 ,使用 . 访问结构体成员 ;
    • 参数修改 : 修改该参数 , 不会影响外部结构体对象的值 , 因为修改的是拷贝后的副本 ;
    // 直接传入结构体类对象本身
    void printStudent1(Student s)
    {
    	// 使用变量 , 直接使用 . 访问结构体成员
    	cout << "printStudent1 开始执行 age : " << s.age << endl;
    	s.age = 19;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    II 、传递结构体指针


    第二种方式 , 传递结构体 指针 ,

    • 函数传递 : 这种方式传递的是 结构体 指针 , 实际上是指针的副本 , 几乎不消耗性能 ;
    • 参数访问 : 传入的 指针 参数 在函数中 使用 -> 访问结构体成员 ;
    • 参数修改 : 通过指针 修改该参数 , 外部的结构体对象也会被修改 ;
    // 传入结构体类对象指针
    void printStudent2(Student* s)
    {
    	// 通过 问结构体指针 访问成员需要使用 -> 访问
    	cout << "printStudent2 开始执行 age : " << s->age << endl;
    	s->age = 20;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    III 、传递结构体引用


    第三种方式 , 传递结构体 引用 ,

    • 函数传递 : 这种方式传递的是 结构体 引用 , 引用只是变量的一个别名 , 几乎不消耗性能 ;
    • 参数访问 : 传入的 引用 参数 在函数中 使用 . 访问结构体成员 , 与变量用法一样 ;
    • 参数修改 : 通过指针 修改该参数 , 外部的结构体对象也会被修改 ;
    // 传入结构体类对象指针
    void printStudent2(Student* s)
    {
    	// 通过 问结构体指针 访问成员需要使用 -> 访问
    	cout << "printStudent2 开始执行 age : " << s->age << endl;
    	s->age = 20;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、代码示例 - 使用三种传递方式传递参数


    代码示例 :

    // 包含 C++ 头文件
    #include "iostream"
    
    // 使用 std 标准命名空间
    //		该命名空间中 , 定义了很多标准定义
    using namespace std;
    
    // 导入 C 头文件
    #include 
    
    // 定义一个结构体 
    // 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    执行结果 :

    printStudent1 开始执行 age : 18
    printStudent1 执行完毕 age : 18
    printStudent2 开始执行 age : 18
    printStudent2 执行完毕 age : 20
    printStudent3 开始执行 age : 20
    printStudent3 执行完毕 age : 21
    Press any key to continue . . .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

  • 相关阅读:
    东方博易OJ——1000 - 【入门】熟悉一下Online Judge的环境
    kafka安装步骤以及初步入门
    2023年考研数学测试卷(预测)
    【信号隐藏-数字水印】基于DCT实现音频水印嵌入提取附Matlab代码
    将Visual Studio Code配置成好用的Python IDE
    Linux-基本指令03
    WAS项目更新单个文件
    Spring 多线程事务控制
    从零开始,小白也能学会的创建Git仓库实操
    [管理与领导-105]:IT人看清职场中的隐性规则 - 2 - 刷存在感也是管理工作的一部分, 是展现自身和团队价值的一种手段
  • 原文地址:https://blog.csdn.net/han1202012/article/details/132416680