• [C++]C++入门--引用


    🥁作者: 华丞臧
    📕​​​​专栏:【C++】
    博主Gitee
    各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎>在评论区指出。
    推荐一款刷题网站 👉LeetCode



    一、引用概念

    引用不是新定义一个变量,而是给已经存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它们引用的变量共用同一块空间

    //类型& 引用变量名 = 引用实体;
    如下:
    int a = 10;
    int& ra = a; //定义引用类型,必须初始化
    
    • 1
    • 2
    • 3
    • 4

    二、引用特性

    1. 引用在定义时必须初始化
    2. 一个变量可以有多个引用
    3. 引用一旦引用一个实体,再不能引用其他实体
    void TestRef()
    {
       int a = 10;
       // int& ra;   // 该条语句编译时会出错
       int& ra = a;
       int& rra = a;
       printf("%p %p %p\n", &a, &ra, &rra);  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    可以看到a,ra,rra三个变量地址一致,从这里可以看出引用其实就是变量实体的别名,与实体共用同一块空间。

    三、常引用

    void TestConstRef()
    {
        const int a = 10;
        //int& ra = a;   // 该语句编译时会出错,a为常量
        const int& ra = a;
        // int& b = 10; // 该语句编译时会出错,b为常量
        const int& b = 10;
        double d = 12.34;
        //int& rd = d; // 该语句编译时会出错,类型不同;
        const int& rd = d; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    指针和引用赋值中,变量的权限可以缩小,但不可以放大。

    类型转换、整型提升、截断这些的过程中都会产生一个临时变量,语法规定临时变量具有常性即不能修改。

    四、使用场景

    4.1 做参数

    在C语言当中,调用一个函数传参分为两种:一是传值二是传地址,在这两种方式中只有传地址才能通过函数中的形参改变实参;而现在C++当中使用引用做形参同样能达到相同的效果。

    //这是一个例子
    void Swap(int& left, int& right)
    {
       int temp = left;
       left = right;
       right = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    引用做参数的优点

    1. 减少拷贝,提高效率
    2. 输出型参数,函数中修改形参实参也修改了

    关于函数重载

    int Add(int& a, int& b)
    {
        int c = a + b;
        return c;
    }
    
    int Add(int a, int b)
    {
        int c = a + b;
        return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述函数构不构成重载看编译器的识别或者名字修饰规则,但是不能这么写程序会出错,因为调用时存在二义性,编译器识别不出。
    传值、传引用效率比较

    #include
    #include 
    
    using namespace std;
    
    struct A 
    { 
    	int a[10000]; 
    };
    void TestFunc1(A a) 
    {
    }
    void TestFunc2(A& a) 
    {
    }
    void TestRefAndValue()
    {
    	A a;
    	// 以值作为函数参数
    	size_t begin1 = clock();
    	for (size_t i = 0; i < 100000; ++i)
    		TestFunc1(a);
    	size_t end1 = clock();
    	// 以引用作为函数参数
    	size_t begin2 = clock();
    	for (size_t i = 0; i < 100000; ++i)
    		TestFunc2(a);
    	size_t end2 = clock();
    	// 分别计算两个函数运行结束后的时间
    	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
    	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
    }
    
    int main()
    {
    	TestRefAndValue();
    	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

    在这里插入图片描述
    可以看到,数据量很大的时候引用做参数的效率更快

    4.2 做返回值

    传值返回

    我们知道在函数调用时,编译器会为函数在内存中开辟一个函数栈帧;在函数调用返回时函数栈帧销毁之前,返回值会先传给一个临时变量(小则存放在寄存器中,大则在主函数栈帧开辟的空间)返回。

    引用返回

    引用是什么?引用是一个变量的别名,和引用的实体是共用一块空间的;那么在函数栈帧销毁时,函数返回的是实体变量在这个过程中就不会再产生一个临时变量。
    注意:函数栈帧销毁,该函数中的局部变量也销毁了,这时就不能再用引用返回了;如果这个变量是定义在函数栈帧上的,函数栈帧销毁变量也跟着销毁,出了函数作用域返回变量不存在了,不能使用引用返回,因为此时引用返回的结果是未定义的;出了函数作用域,返回变量存在,才能使用引用返回。

    int& Count()
    {
       static int n = 0;
       n++;
       // ...
       return n;
    }
    
    //下面代码输出什么结果?为什么?
    int& Add(int a, int b)
    {
        int c = a + b;
        return c;
    }
    int main()
    {
        int& ret = Add(1, 2);
        Add(3, 4);
        cout << "Add(1, 2) is :"<< ret <<endl;
        //cout << "Add(1, 2) is :"<< ret <
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    打印一次
    在这里插入图片描述
    打印两次
    在这里插入图片描述
    说明

    • 打印一次的时候,add函数栈帧销毁时函数栈帧中存放c变量的空间并没有被销毁改变,此时去访问这块空间的值还是7。
    • 当第二次打印两次的时候,我们第二次非法访问同样一块空间发现值已经被改变,这是因为先取值再打印所以第一次还是7但是此时cout函数调用开辟了栈帧覆盖了那片空间,从而导致了值的改变。
    • 上述的那一段程序是错误,属于非法访问,这说明:出了函数作用域变量就不存在了是不能使用传引用返回的

    引用返回的优点

    1. 减少拷贝,提高效率
    2. 修改返回值

    值和引用的作为返回值类型的性能比较

    #include
    #include 
    
    using namespace std;
    
    struct A 
    { 
    	int a[10000]; 
    };
    A a;
    // 传值返回
    A TestFunc1() 
    { 
    	return a; 
    }
    // 引用返回
    A& TestFunc2() 
    { 
    	return a; 
    }
    
    void TestReturnByRefOrValue()
    {
    	// 以值作为函数的返回值类型
    	size_t begin1 = clock();
    	for (size_t i = 0; i < 100000; ++i)
    		TestFunc1();
    	size_t end1 = clock();
    	// 以引用作为函数的返回值类型
    	size_t begin2 = clock();
    	for (size_t i = 0; i < 100000; ++i)
    		TestFunc2();
    	size_t end2 = clock();
    	// 计算两个函数运算完成之后的时间
    	cout << "TestFunc1 time:" << end1 - begin1 << endl;
    	cout << "TestFunc2 time:" << end2 - begin2 << endl;
    }
    
    int main()
    {
    	TestReturnByRefOrValue();
    	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

    在这里插入图片描述
    通过上述代码的比较,发现传值和传引用在作为传参以及返回值类型上效率相差很大。

    五、引用和指针的区别

    语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间;但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的

    int main()
    {
    	int a = 10;
    	int& ra = a;
    	ra = 20;
    	int* pa = &a;
    	*pa = 20;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上述引用和指针的汇编代码如下:
    在这里插入图片描述
    通过对比不难发现,引用的底层是使用指针实现的

    引用和指针的不同点:

    1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
    2. 引用在定义时必须初始化,指针没有要求;
    3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;
    4. 没有NULL引用,但有NULL指针;
    5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节);
    6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
    7. 有多级指针,但是没有多级引用
    8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
    9. 引用比指针使用起来相对更安全
  • 相关阅读:
    理解ASP.NET Core - 发送Http请求(HttpClient)
    (13.2)Latex图片、表格的绘制与排布
    C++ string类模板
    MATLAB实战 | 粮食储仓的通风控制问题
    手绘板的制作——命令模式与撤销、重制(3)
    【iOS】实现评论区展开效果
    MySQL基础——数据库和表的相关操作
    【论文项目复现1】漏洞检测项目复现_VulDeeLocator
    JVM高级性能调试
    SpringMVC请求(下)-文件上传
  • 原文地址:https://blog.csdn.net/qq_59456417/article/details/128077108