首页 > 编程语言 >C++基础-引用详解(全网最详细,看这篇就够了)

C++基础-引用详解(全网最详细,看这篇就够了)

时间:2024-07-18 20:28:16浏览次数:12  
标签:返回 const int 全网 C++ 详解 static 引用 权限

目录

前言

本节我们正式进入C++基础之引用的学习,下面小编将会带领大家由浅入深逐步解剖引用的语法及应用场景,话不多说,我们直接上干货!!

一、引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
例如:《西游记》中孙悟空,也称“齐天大圣”,“美猴王”等,但都表示同一个人

引用的语法为: 类型 & 引用变量名(对象名) = 引用实体

下面,我们来举个例子

#include<iostream>
using namespace std;
 
int main()
{
    int a = 1;
    int& ra = a;   //ra是a的引用,引用也就是别名,相当于a再取了一个名称ra.

    int& b = a;
    int& c = b;//在物理空间上,a、ra、b、c共用一块空间,所以a、ra、b、c的值相同,都等于a = 1.

    c = 2;//这块空间上有四个名称,此时对任意一个名称操作,都改变同一块空间的值,所以此时a = ra = b = c = 2.
    return 0;
}

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

二、引用的特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用

3.引用一旦引用一个实体,就再也不能引用其他实体

int main()
{
    int a = 1;
    //int& b;    //这行编译时会报错,因为引用在定义时必须初始化
    int& c = a;
    int d = 2;
    c = d; //这里不是c变成d的引用,而是将d赋值给c
    return 0;
}

三、常引用

加了 const 的变量是不能修改的,即变成的一种常量,可以理解为只读型;而没加 const 的变量可以理解为可读可写型

下面小编带大家理解一下“权限放大”、“权限缩小”以及“权限平移
注意:权限放大和缩小只适用于指针引用

int main()
{
//1.权限放大
    const int a = 0;
    //int& b = a;  //这里编译会报错,原因:a的类型是const int,是只读的;而b的类型是int,是可读可写的;权限放大了,报错。
    const int& b = a;//改成这样就不会报错,因为此时a,b都是可读可写的,属于权限平移。 

//2.权限缩小
     int c = 1;
     int& d = c;
     const int& e = c;//这里是可以的。原因:c是可读可写的,e变成c的别名是只读的,属于权限缩小。

//3.指针的情况
     const int* cp1 = &a;
     //int* p1 = cp1;  //这里不可以,属于权限的放大

     int* p2 = &c;
     const int& cp2 = p2; //这里可以,是权限的缩小
     
//总结:引用取别名时,变量访问的权限可以缩小,不能放大。
     return 0;
}

最后的一种情况理解起来比较难,也是需要注意分辨的,小编总结如下:
类型转换的情况时:

int main()
{
    int i = 0;
    double ii = i;//隐式类型转换,这里是可以的,语法允许的,注意这里不是引用
    ------------------------------------------------
    //double& dd = i; 
    //这里是不行的,属于权限被放大了。
    const double& dd = i;
    //这样才是可以的,是权限的平移。
}

这里是为什么呢,很多同学会懵掉,下面我们要理解赋值和引用是怎么发生的,请看下面原因图解

– 原因: 类型转换时,会产生一个临时变量,赋值时 i 会先赋给临时变量,再由临时变量赋值给 dd ; 所以这里 dd 引用的不是 i,而是临时变量。 而临时变量具有常属性。 是只读的, dd 引用时若不加 const ,则属于权限放大了。

–图解:
在这里插入图片描述
总结:

  1. 权限放大和缩小只针对引用和指针,而普通变量之间的赋值是没有权限缩小和放大的关系的(例如:int i = 1;const int b = i; 是可以的)。
  2. 权限只能缩小,不能放大。
  3. 临时变量具有常属性。
  4. 权限的放大,const 不能给非 const ; const 只能给 const。
  5. 权限的缩小,非 const 既可以给非 const ;也能给 const。

四、引用的使用场景

4.1 引用做参数

//   用C语言时的做法
//void Swap_c(int* p1,int* p2)
//{
//     int temp = *p1;
//    *p1 = *p2;
//     *p2 = temp;
//}

void Swap_cpp(int& r1,int& r2)
{
     int temp = r1;
     r1 = r2;
     r2 = temp;
}

int main()
{
    int a = 0,b = 1;
    //Swap_c(&a,&b);
    Swap_cpp(a,b);
    return 0;
}

引用做参数,避免了使用指针;改变 Swap_cpp 函数内的形参 r1,r2 就是改变了 main 函数传入的实参 a,b;因为 r1, r2 分别是a, b的别名

我们来看一下上面的栈帧图,它们在同一个进程里面。
在这里插入图片描述

在这里我们多提一下函数传参种方式:传值传地址(指针)传引用
后两种上面已有了展示,下面我们来看看传值

在这里插入图片描述
这里传值并不能达到这个函数的交换效果,因为这块 r1,r2会分别产生一块空间,a,b 传值会传给这两块空间,而函数里面的交换的是两个空间 r1, r2的值,函数结束时空间也会销毁,并不会影响a,b 的值。也就是大家熟悉的形参的改变不会影响实参。

而传引用时 r1,r2 就不会产生空间,而是作为 a,b 的别名和a,b 共用一块空间,所以能改变 a, b 的值。看下图:
在这里插入图片描述

4.2 引用做返回值

  • 传引用返回:意思是返回的是返回对象的别名。(同一个空间)
  • 传值返回:意思是返回的是返回对象的拷贝。(不同空间)

在这里插入图片描述
上图就是传值返回和传引用返回的一个对比,我们来分析一下;

首先传值(无论是参数传值还是传值返回都会产生一个拷贝变量),如上图传值返回返回的不是n, 而是n 的一个拷贝变量(临时变量),我们说过临时变量是具有常属性的,所以在 main 函数中接收的时候用 int& r1 = Count1()编译错误的(权限放大);所以用 const int& r1 = Count1() 接收就没问题了;而传引用返回时返回的是 n 的一个别名,相当于返回的是 int& tmp = n 中的 tmp , 这里是不会产生额外的空间的,所以用 int& r2 = Count2() 接收是没问题的,此时相当于 r2 是 n 的别名。

这里解释一下static,这里的static修饰的是生命周期,不会影响权限问题,static int 的类型还是 int ,理解权限问题时大家可以把static int 看作 int 理解,至于这里static的作用是什么,我们下面会详细分析。

这里没有 static 的话,程序会出很大的问题。
我们看下面一段代码,下面代码会输出什么结果呢?大家可以思考后去VS试试,下面我会给出结果。

没有加 static 时

int& Add2(int a, int b)
{
    int c = a + b;
    return c;
}

int main()
{
    int& ret = Add2(1, 2);
    Add2(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

没有加 static 的结果:
在这里插入图片描述

这里是随机值,有些编译器会是 7;
在这里插入图片描述

加了 static 后

int& Add2(int a, int b)
{
    static int c = a + b;
    return c;
}

int main()
{
    int& ret = Add2(1, 2);
    Add2(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

加了 static 后的结果:

在这里插入图片描述

这里是为什么呢?
为什么没加 static 的结果是错误的,为什么没加 static 结果会是随机值或者是7 ?(7是受Add(3,4)影响,函数结束回收空间之后没有被置成随机值的结果,现在的编译器一般都是随机值了)。
我们来看一看没加 static 时的栈帧图:
在这里插入图片描述

解释:

  • 第一次调用 Add(1,2) 的时候,传参的时候把1传给了a,2传给了b,加起来就是c=3,这时候传引用返回,ret接受了c这个返回值,所以ret就是c的别名,第一次调用完成之后,进行了返回,返回之后Add函数的栈帧就被销毁了(销毁了,其实这块空间还在,只是使用权不是你的了),紧接着再调用Add(3,4),这时候还是建立了同样大小的栈帧,这时候 c 的那块空间的值变成了 7;再返回,此时栈帧也被销毁了(结果为 7 是因为栈帧销毁后 c 这块空间还没有被置成随机值,出现随机值就是因为栈帧销毁后 c 这块空间被置成随机值了),所以会出现7和随机值这两种情况。
  • 这里说明当返回变量 c 是局部变量时,引用返回是不安全的。因为出了函数的作用域,函数的栈帧已经销毁了,但是你还在访问销毁的那块空间。
    这种不安全还体现在 Add(1,2)执行完之后,你再随机调用一个其他函数(printf都行),都会在 main 函数下面建立栈帧,这个栈帧有可能就覆盖了 ret ;把它置成随机值。
  • 如果你不是看得很明白的话,或许下面这篇文章能帮助你理解:
  • 函数引用返回底层分析 – 栈帧创建和销毁

那怎么解决这个问题呢?加上 static 问题就迎刃而解了。

我们来看看加了 static 之后的栈帧图:
在这里插入图片描述

  • 加了static 之后,c 的 进程地址 在堆区,此时Add 函数执行完之后,函数栈帧被销毁了,但是 c 的这块空间还在,并没有受影响。
  • 注意:加了static 之后,static int c = a + b;只会在第一次调用定义时被执行一次,下次再调用这个函数则不会再执行,会直接跳过。

总结: 一个函数要使用引用返回,返回变量出了这个函数的作用域还存在,就可以用引用返回,否则就不安全。(全局变量,静态变量适用于引用返回)
函数使用引用返回的好处
1.少创建拷贝一个临时对象,提高效率。

五、传值、传引用效率比较

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

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

#include<iostream>
#include <time.h>
struct A{ 
    int a[10000];
 };
A a;
//值返回
A TestFunc1{return a;}
//引用返回
A& TestFunc1{return a;}

void main()
{
// 以值作为函数参树
    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;
}

结果:
在这里插入图片描述

可以看出,传引用返回在实践中时间效率更优,且数据越大越明显。

5.2 值和引用作为参数传递之间的性能差别

#include <time.h>
struct A{ 
    int a[10000];
 };
A a;
//值返回
A TestFunc1(A a){}
//引用返回
A& TestFunc1(A& a){}

void main()
{
    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()
{
   int a = 10;
   int& ra = a;
 
   cout<<"&a = "<<&a<<endl;
   cout<<"&ra = "<<&ra<<endl;
 
   return 0; 
}

底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

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

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

结束语

至此本节内容全部讲完,本节的内容较多且比较晦涩,着重讲了C++之引用篇的一些语法、应用场景及一些底层实现。另外,小编的能力有限,如果有什么地方描述有误或者知识缺陷,欢迎各位大佬指出,小编定当及时改正。下节内容将给大家带来更精彩的C++知识,欢迎大家捧场!
最后想说这是小编的第一篇文章,感谢各位友友的观看,你们的支持就是小编创作的最大动力,友友们动动手指点个赞,留个评论吧!谢谢大家!

标签:返回,const,int,全网,C++,详解,static,引用,权限
From: https://blog.csdn.net/2302_79341182/article/details/140361906

相关文章

  • 【C++】内联函数
    目录前言一、内联函数的概念二、内联函数的特征三、总结:四、如何在vs2022查看反汇编以及debug模式下查看inline反汇编需要调整的配置。4.1查看反汇编4.1debug模式下查看inline反汇编需要调整的配置结尾前言各位友友好,我们又见面了!本节我们将进入C++基......
  • GESP编程能力等级认证C++编程真题解析 | 2024年3月五级
    学习C++从娃娃抓起!记录下CCF-GESP备考学习过程中的题目,记录每一个瞬间。附上汇总贴:GESP编程能力等级认证C++编程真题解析|汇总单选题第1题唯一分解定理描述的内容是()?A.任意整数都可以分解为素数的乘积B.每个合数都可以唯一分解为一系列素数的乘积C.两个不同的......
  • 白骑士的C++教学实战项目篇 4.3 多线程网络服务器
    系列目录上一篇:白骑士的C++教学实战项目篇4.2学生成绩管理系统        在这一节中,我们将实现一个多线程网络服务器项目,通过该项目,我们将学习套接字编程的基础知识以及如何使用多线程处理并发连接。此外,我们还将实现一个简单的客户端来与服务器进行通信。项目简介......
  • c++ primer plus 第16章string 类和标准模板库,16.2.1 使用智能指针
    c++primerplus第16章string类和标准模板库,16.2.1使用智能指针c++primerplus第16章string类和标准模板库,16.2.1使用智能指针文章目录c++primerplus第16章string类和标准模板库,16.2.1使用智能指针16.2.3uniqueptr为何优于autoptr16.2.3unique......
  • c++ primer plus 第16章string 类和标准模板库,16.2.2 有关智能指针的注意事项
    c++primerplus第16章string类和标准模板库,16.2.2有关智能指针的注意事项c++primerplus第16章string类和标准模板库,16.2.2有关智能指针的注意事项文章目录c++primerplus第16章string类和标准模板库,16.2.2有关智能指针的注意事项16.2.2有关智能指针的......
  • [Redis]字典详解
    字典是Redis服务器中出现最为频繁的复合型数据结构,除了hash结构的数据会用到字典(dict)外,整个Redis数据库的所有key和value也组成了一个全局字典还有带过期时间的key集合也是一个字典。zset集合中存储value和score值的映射关系也是通过字典结构实现的。structR......
  • C++ 面向对象程序设计 ---- 类2重点
    1.构造函数,代替Init()函数构造函数的特点1.函数名与类名相同2.无返回值,void也不需要写3.对象实例化时,系统会自动调用构造函数4.构造函数可以重载classDate{public://函数名与类名相同,无返回值Date()//函数重载,无参{_year=1;......
  • [php命令执行函数]详解各种php命令执行函数
    如下几种命令执行函数:目录systemexcpassthrushell_exec反引号``popensystemsystem函数简介:用于执行命令语法形式:system(string$command,int$return_var=?)command:必选参数,字符类型,被system函数执行的命令,如lsreturn_var:可选参数,整数类型,如果提供此参数,则com......
  • Java版本jdk8的特性Lambda表达式详解
    面向对象编程思想和函数式编程思想的区别面向对象编程:重点是对象,强调的是对象的状态和行为。面向对象编程使用类和实例来封装数据和行为,这可以让代码更加模块化和易于维护。函数式编程:重点是函数,强调的是函数的输入和输出,而不是对象的状态。函数式编程通常使用纯函数,即没......
  • 【C语言】结构体,枚举,联合超详解!!!
    目录结构体结构体声明结构体成员的访问结构体自引用 结构体变量定义,初始化,传参 结构体内存对齐 位段枚举联合(共用体)结构体结构体声明1.概念1.结构体是一些值的集合,这些值称为成员变量。2.结构体的每个成员可以是不同类型的变量。3.数组:一组相同类型......