• C++入门之引用(超详解)


    1 引用概念

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

    用法:

    类型& 引用变量名(对象名) = 引用实体;

    void TestRef()
    {
        int a = 10;
        int& ra = a;//<====定义引用类型
        printf("%p\n", &a);
        printf("%p\n", &ra);
    }
    

    在这里插入图片描述

    注意:引用类型必须和引用实体是同种类型的

    2 引用特性

    1. 引用在定义时必须初始化
    2. 一个变量可以有多个引用
    3. 引用一旦引用一个实体,再不能引用其他实体

    void TestRef()
    {
       int a = 10;
       // int& ra;   // 该条语句编译时会出错
       int& ra = a;
       int& rra = a;
       printf("%p %p %p\n", &a, &ra, &rra);  
    }
    

    3.常引用的相关细节

    常引用这里涉及的东西比较多,又比较琐碎,相关解释就写在了下面的代码中:

    void Func(const int& x)
    {
    
    }
    
    int main()
    {
    	int a = 0;
    	// 权限平移
    	int& ra = a;
    
    	// 指针和引用赋值中,权限可以缩小(可读可写缩小权限为只可读),但是不能放大
    	const int b = 1;
    
    	// 拷贝
    	//a = b;
    
    	// 我引用你,权限放大 不行
    	//int& rb = b;
    
    	// 我引用你,我的权限的缩小 可以
    	const int& rra = a;
    	//rra++;
    	a++;
    
    	// 权限平移
    	const int& rb = b;
    	
    	//下面这四个调用都能正常实现
    	Func(a);//权限缩小
    	Func(b);//权限平移
    	Func(rra);//权限平移
    	Func(10);//权限平移
    
    	return 0;
    }
    

    其实在C语言中的整形提升,数据截断,类型转换都会产生临时变量,为什么呢?
    举一个很简单的例子,就像下面程序输出的是d吗?并不是d而是编译器在其内部创建的一个临时变量,输出的是这个临时变量,基于这样的原因,我们还要知道一点就是临时变量具有常性(const)
    在这里插入图片描述
    基于这样的基础再看下面代码,也许会有不一样的收获!

    void func(const int& N = 10)//引用做缺省参数需要加上const
    {
    
    }
    
    int Count()
    {
    	int n = 0;
    	n++;
    	// ...
    	return n;
    }
    
    int main()
    {
    	const int& b = 10;
    
    	double d = 12.34;
    
    	cout << (int)d << endl;//输出的其实是临时变量
    
    	int i = (int)d; // 正确
    	
    	//int& ri = d; //错误,类型不一样需要进行类型转换,
    				//而类型转换会产生一个int类型的临时变量,
    				//临时变量具有常性,需要在前面加上Const
    	const int& ri = d; // 这样才是正确的
    	cout << ri << endl;
    
    	//int& ret = Count();//错误 这里函数返回的是一个常量值,需要在前面加上Const
    	const int& ret = Count();//正确
    
    	return 0;
    }
    

    4.使用场景

    1. 做参数
    void Swap(int& left, int& right)
    {
       int temp = left;
       left = right;
       right = temp;
    }
    

    在这里插入图片描述

    引用做参数优点
    1、减少拷贝,提高效率。
    2、输出型参数,函数中修改形参,实参也跟着修改了。

    1. 做返回值
    int& Count()
    {
       static int n = 0;
       n++;
       // ...
       return n;
    }
    

    思考下面代码输出什么结果?为什么?

    int Count1()
    {
    	int n = 0;
    	cout << &n << endl;
    
    	n++;
    	// ...
    	return n;
    }
    
    int& Count2()
    {
    	static int n = 0;
    	cout << &n << endl;
    
    	n++;
    	// ...
    	return n;
    }
    
    int main()
    {
    	int ret1 = Count1();
    	int ret2 = Count2();
    	
    	cout << ret1 << endl;
    	cout << ret2 << endl;
    	
    	return 0;
    }
    

    在这里插入图片描述

    解释:

    1. 如果函数返回时,出了函数作用域,如果返回对象还在,就像上面的static int n(它的内存空间在堆上,函数栈帧销毁n的空间也没还给系统),则可以使用引用返回。
    2. 如果已经还给系统了,则必须使用传值返回,所谓传值返回就是上面的Count1函数,函数在被销毁之前,如果n占用的内存空间很大,那么会在main函数里调用Count1函数之前创建一个临时拷贝,如果很小的话就会在寄存器里暂时存下它的值,总之在函数销毁之后要返回的结果会存在一个空间,这也是传值调用效率为什么比传引用低的原因。下面传值、传引用比较也会有详细的介绍。
    3. 还需要注意的是如果上面函数Count2中的static去掉那么编译器会报警告。如下图所示。
      在这里插入图片描述
      原因就是因为n的内存空间已经被销毁,传引用返回访问的n是已经被销毁的内存空间,我们对这块内存空间的访问是非法的。

    5 传值、传引用比较

    以值作为参数或者返回值类型,在传参返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

    5.1传值、传引用调用函数的效率比较

    #include 
    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 < 1000000; ++i)
     TestFunc1(a);
     size_t end1 = clock();
     // 以引用作为函数参数
     size_t begin2 = clock();
     for (size_t i = 0; i < 1000000; ++i)
     TestFunc2(a);
     size_t end2 = clock();
    // 分别计算两个函数运行结束后的时间
     cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
     cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
    }
    

    在这里插入图片描述

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

    #include 
    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 < 1000000; ++i)
     TestFunc1();
     size_t end1 = clock();
     // 以引用作为函数的返回值类型
     size_t begin2 = clock();
     for (size_t i = 0; i < 1000000; ++i)
     TestFunc2();
     size_t end2 = clock();
     // 计算两个函数运算完成之后的时间
     cout << "TestFunc1 time:" << end1 - begin1 << endl;
     cout << "TestFunc2 time:" << end2 - begin2 << endl;
    }
    

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

    6 引用和指针的区别

    在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

    int main()
    {
    int a = 10;
    int& ra = a;
    cout<<"&a = "<<&a<<endl;
    cout<<"&ra = "<<&ra<<endl;
    return 0;
    }
    

    但在底层实现上实际是有空间的,实际上语法层面,ra是a的别名,不开空间。而在C++底层,引用是通过指针实现的。

    来看下面的例子:

    int main()
    {
    int a = 10;
    int& ra = a;
    ra = 20;
    int* pa = &a;
    *pa = 20;
    return 0;
    }
    

    我们来看下引用和指针的汇编代码对比:
    在这里插入图片描述
    所以实际上引用也是开空间的。

    引用和指针的不同点:

    1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
    2. 引用在定义时必须初始化,指针没有要求
    3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
    4. 没有NULL引用,但有NULL指针
    5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
    6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
    7. 有多级指针,但是没有多级引用
    8. 访问实体方式不同,指针需要显式解引用,引用编译器会自己处理
    9. 引用比指针使用起来相对更安全

    end
    有哪里看不懂可以随时向博主提问
    有错误的地方欢迎各位老铁批评指正。

  • 相关阅读:
    在windows上配置本地域名解析,配置hosts文件
    做自媒体怎样在一年之内赚到 10万元?
    入门【网络安全/黑客】启蒙教程
    多表查询、Navicat软件、PyMySQL模块
    大数据架构
    Digi重启XBee-Pro S2C生产,有些差别需要注意
    【调试工具|mqtt-spy】mqtt客户端调试工具 mqtt-spy 详细使用教程
    【flask框架】模型model
    原创工具14Finger-全能web指纹识别与分享平台
    npm login报错 ‘proxy‘ config is set properly. See: ‘npm help config‘
  • 原文地址:https://blog.csdn.net/qq_55712347/article/details/127041946