前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】 夜的尽头不是 引用,是天空没有极限

【C++】 夜的尽头不是 引用,是天空没有极限

作者头像
The sky
发布2023-04-12 14:27:24
2760
发布2023-04-12 14:27:24
举报
文章被收录于专栏:C++的逆袭之路

引用目录

在C++中呢,引用这一个概念可谓是非常的重要,对后面C++的学习有非常紧密的关系,所以在接下来,我们会详细的于大家分享  引用,揭开它神秘面纱,看看到底长什么样子!


前言

C++的引用细节可能较多,所以大家可能要处处留心,多思考,多回顾哦!



一、引用是什么?

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

间,它和它引用的变量共用同一块内存空间。

简而言之呢,引用就是 取别名,给你取一个绰号!

                                                    邓紫棋 ->解解 ->金鱼嘴 

这就是引用的意思,不会再创造另外的你,还是你自己本身!!不开辟额外的内存空间。


二、初识引用

1.初始引用

//定义引用类型  int&   类型& 引用变量名(对象名) = 引用实体;     int a = 0;     int& ra = a;   //帮a取别名 ra     int& rra = a;  //帮a取别名rra     一个变量可以取多个别名 int a = 10; int& ra = a; // 引用 int& x = a; int& y = x; x++; y++; a++; a最后的结果是  13.

比如在家你妈叫你大宝,在外面别人叫你头铁,在社会别人叫你 牛哥,这是一个意思,你还是你,只不过别名多了。    但要注意的是!! 引用类型必须和引用实体同种类型的!    double a=8.88;    int& ra=a; 这就是错误的!!

那先问一下,引用出现的好处和优点是什么呢??

为什么要有引用????

    引用做参数好处:

以前我们交换两个数是这样交换的 

代码语言:javascript
复制
void Swap(int *m,int*n)
{
   int temp=*m;
   *m=*n;
   *n=temp;
}
int main()
{
   int a=1;
   int b=2;
   Swap(&a,&b);
   return 0;
}

1.    之前交换两个数,需要通过指针,即访问变量的地址,指针变量解引用来交换a,b的内容。

但是有了引用的话,只需这样即可

函数的形参类型为引用类型,m和n就是a和b的别名,相当于传过去的就是a和b,直接改变的就是a和b的值。

 2.  在学数据结构的时候,我们经常会改变指针变量,涉及到二级指针和一级指针,会有些许的困难,但如果使用引用的话,就会方便很多,容易理解!

代码语言:javascript
复制
typedef struct ListNode
{
	struct ListNode* next;
	int val;
}LTNode, *PLTNode;

//改变指针变量,需要通过传它的地址,间接通过 解引用 来改变其内容,这就会涉及到二级指针
void SlistPushBack(struct ListNode** pphead, int x)

//但是如果使用引用,我们就可以直接取别名struct ListNode*& phead,别名phead,来直接改变它
void SlistPushBack(struct ListNode*& phead, int x)

//PLTNode为结构体指针,所以引用可以直接PLTNode& phead,这就是一些书上为了方便理解而这样写
void SlistPushBack(PLTNode& phead, int x)
{}


int main()
{
   struct ListNode* plist = NULL;
   SlistPushBack(&plist,1);
   return 0;
}

所以,我们可以体会到,引用出现的一个原因就是为了淡化指针的使用,便于理解和使用,当然引用对C++的学习是非常的重要,不仅于此!!


2.引用特性

1.引用在使用定义前必须初始化。

2.一个变量可以有多个引用。

3.引用一旦引用一个实体,再不能引用其他实体。         就是说,你在家里叫宝宝,你妈妈不可能给你弟弟也叫宝宝吧,那样的话,就分不清你两谁是谁了,所以引用只能引用一个实体! int a=10; int&ra=a; int b=20; ra=b;//    这一步ra是b的引用吗? 当然不是,引用只能引用一个实体,所以这里仅是简单的赋值。 a=20这就是结果了!


3.引用的使用场景和const修饰的引用

1.做返回值

      1.传值返回:

代码语言:javascript
复制
int Count()
{
    int n = 0;
	n++;
	// ...
	return n;
}

int main()
{
	int ret = Count();
	cout << ret << endl;

	return 0;
}

在调用函数的时候,会开辟函数栈帧,栈帧是向下生长的,如图所示:

我们观察发现,在将n返回时,会创建一个临时变量来将n的值拷贝到临时变量中,等到Count函数栈帧销毁时,变量n自然会销毁,导致无法返回,造成越界访问。所以需要一个临时变量提前在调用Count函数的main函数栈帧中,创建一个临时变量。

当然:临时变量的创建与否,需要看除了函数作用域以后,返回变量是否存在

若将返回变量用static修饰,则变量在静态区,不会随着栈帧销毁而销毁,则无需创建临时变量。

      2.引用返回

        返回值为引用类型:

代码语言:javascript
复制
int& Count()
{
    int n = 0;
	n++;
	// ...
	return n;
}

int main()
{
	int ret = Count();
	cout << ret << endl;

	return 0;
}

此时,临时变量所存的是n的别名,因为是引用返回。在n销毁后,相当于通过别名直接访问n,但n已经销毁,这就会造成非法访问。

销毁后,n的内存空间也销毁了吗? 还可以继续访问吗?

答案是ok的:n的内存空间没有销毁,仍然可以访问!!

举个例子,比如你住酒店,你退房了,不代表那个房间就炸了,而是你对它没有了访问权限,但是你仍然可以通过别的刑的方式去访问它。

那为什么第一次打印,是1,第二次是随机值,第三次是100呢?

因为n的内存空间里的内容可能没有来得及改变,没有人去使用它,所以你非法访问时,内容可能不变,仍是你之前留下的值。

第二次为什么是随机值呢?

因为cout也是函数的调用,Count栈帧销毁,但调用cout建立栈帧,之前的空间就可能会被重复利用,就会产生随机值。

第三次为什么是100呢?那可能func函数栈帧大小刚好与count一样,在重复利用时,直接覆盖,那么n的内存空间可能就是x的内存空间了,只不过更换了值。

当然一切皆有可能,这些都可能时随机值也不为过!哈哈哈

所以,总结:

1.出了函数作用域,返回变量不存在了,不能用引用作返回值,因为引用返回的结果是未定义的

2.出了函数作用域,变量还在,可以用引用。

3.正确使用用引用做返回值,可以减少拷贝,提高效率;还可以修改返回值。

返回数组中的值,是偶数的数,一律乘二,很方便的修改了返回值!


2.做参数

    作为普通参数:

请看本文章刚开头,引用做参数的好处!

  那么我们会发现,交换两个数时,我们需要将变量地址传过去,创建指针变量拷贝变量地址。

  但若用引用做参数,就不需要拷贝,直接就可以修改,别名也就是自己本身嘛!

好处是:减少拷贝,提高效率!

作为输出型参数:

代码语言:javascript
复制
int& func(int m,int&p)
{
    p=p+m;
    return p;
}

int main()
{
   int a=10;
   int b=20;
   func(a,b);
   cout<<a<<endl;
   return 0;
}

int& p作为输出型参数,使用引用作为参数时,无需拷贝,还可以直接改变形参,进而改变实参。


 3.const修饰的引用

 先看下面的一段代码,从中体会const修饰引用注意要点:

指针和引用赋值中,权限可以缩小,但是不能放大

代码语言:javascript
复制
int main()
{
   // 指针和引用赋值中,权限可以缩小,但是不能放大


	int a = 0;    //可读可写
	// 权限平移 ,权限没有改变
	int& ra = a;  //可读可写


	const int b = 1;   //可读不可写
	
	a = b;     // 拷贝   读取b的值给了a,可以

	int& rb = b;  // 我引用你,此时 int& rb 就是可读可写,将权限放大 不行


	const int& rra = a;   // 我引用你,我的权限的缩小 可以

	//rra++;

	a++;

	const int& rb = b;   // 权限平移,可以
}

所以在我们使用引用作为函数参数时,一般都要加const修饰,防止权限被放大。

比如:const int&......

有人会想,那既然const了,那就改变不了变量了。  

但既然有防止权限被放大,那么定义变量时,他的权限就是小权限,根本不需要将其改变。


4.常量的引用 

              1.类型转化时

doubule b=9.88; int a=(int)b;    //可以 int a=b;         //可以

 int& rb=b;    //不可以

在发生类型转化时,不管   强制类型转化  还是  隐式类型转化,都会产生临时变量,而非改变变量本身。

double d = 9.88;     cout << (int)d << endl;

    此时,d被强转为int,但并没有改变d本身的值,是在类型转化     的时候,会创建临时变量,然后把临时变量的值拷贝到了i中。     int i = (int)d; // 可以     //int& ri = d; // 不可以,因为在类型转化时,是借助中间的临时变量,int&ri是在给中间的临时变量取别名,就会把临时变量的权限的扩大,所以不可行!!     const int& ri = d; // 可以,平移权限

注意:临时变量具有常性,不可以被修改!!

             2.返回函数时:

代码语言:javascript
复制
int Count()

{

 int n = 0;

 n++;

  ...

 return n;

}

int main()
{
  int& ret = Count();  //权限增大
  const int& ret = Count(); //权限平移,可以
}

临时变量具有常性

当返回函数时,临时变量会存在上一个调用它的函数栈帧中,用引用来接收,会扩大临时变量的权限,所以只有在const 引用时,才可以使用!


 三.引用和指针的区别

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

底层实现上实际是有空间的,因为引用是按照指针方式来实现的,都是先通过传地址,解引用来改变实体内容的。

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

总结

引用这一节中,有太多的细节值得我们去反复思考琢磨了,有一个个很细的知识点相互联系的,所以我们要踏踏实实的学习,并反复去复习!!

有几点:

我们下期不见不散!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引用目录
  • 前言
  • 一、引用是什么?
  • 二、初识引用
    • 1.初始引用
      •     引用做参数好处:
    • 2.引用特性
      • 3.引用的使用场景和const修饰的引用
        • 1.做返回值
        •       1.传值返回:
        •       2.引用返回
        • 2.做参数
        •     作为普通参数:
        • 作为输出型参数:
        •  3.const修饰的引用
        • 4.常量的引用 
        •               1.类型转化时
        •              2.返回函数时:
    •  三.引用和指针的区别
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档