C++:2.5新增的访问方式——引用

本文详细讲解了C++中变量的访问方法,包括变量名、引用名和地址指针。重点讨论了引用的概念,与指针的区别,以及在大对象传参时的选用依据。还涉及了引用的用法要点和常引用的应用,强调了引用在效率和可读性方面的优势。
摘要由CSDN通过智能技术生成

目录

一,变量的访问方法

    1.变量名

    2.引用名                 

    3.对地址解引用访问   *(地址指针)

二,概念——给变量定义别名

三,实现格式

四,引用的本质

五,指针,引用和变量名在功能上的区别

引用与指针的选用依据(指向跨域以改实参时,该选谁)

六,C++中&的用法

七,引用的用法要点

九,应用

  1,(大对象)传参数——兼顾传值与传址优势

        使用的条件:看对象的大小

        传引用的本质:

        传引用的优势:

       传值,传址,传引用三者区别及选用场景

        多级指针以引用代替以实现降级,增强指向的明确性

2,(大对象)引用返回

本质:

引用返回与传值返回区别:

传值返回

引用返回的实现:

传址返回的意义:


一,变量的访问方法

        我们在对一个已经创出造的事物进行标记,以便于对该变量进行访问。这标记通常为变量的名字和(变量在内存上的住址)变量的地址 ,这二者就是我们找到该变量的依据。

    1.变量名

        开等大的空间,不跨域访问,直观性强               

    2.引用名                 

        不开空间,直接跨域访问,直观性强 

    3.对地址解引用访问   *(地址指针)

        开4/8bit的空间,解引用间接跨域访问,直观性强 

二,概念——给变量定义别名

         给变量取别名,得到一种不开空间跨域访问直观性强的访问变量的方式,应用在大对象拷贝时不开空间。

不开空间:指向原空间,没有独立空间,共享空间;这使得大对象拷贝不开空间,效率高。

跨域:变量在本域,但指向另一域的变量的空间,空间共享:这就使得,指针级别大大降低了。

直观性强:在应用层我们看到的是变量本身,使得可读性强

三,实现格式

类型名& 引用名 = 被引用的变量名

以下以int类型的变量n为例;

int& a = n;     //意义:对n取别名      

引用必须初始化定义,不能改变指向(所以C++有指针)

引用不改变直接引用的变量的权限

        变量在引用时,变量的读写权限不能放大,只能平移缩小

同类型间引用权限平移

        int这个类型取决于n的类型及权限的平移,即引用此时不读写权限 。(无隐式类型转换)

不同类型间引用权限平移/缩小,尤其是常量及常量类型取别名(常引用),应用在传参数

        若要使不同类型间引用就要隐式类型转换,因为隐式类型转换规则,拷贝时右值必要有常性,而引用是临时变量的别名故也要有常性,左值类型应使用const修饰;

        权限缩小:左值为常性,右值为非常性

        权限平移:左值为常性,右值为常性          (因为中间临时变量,左值是拷贝中间变量)

注意区别:

int    a = n;//本质是值拷贝

Int& a = n; //本质是定义一个指向n地址的变量;

四,引用的本质

        以下是个人拙见,仅供参考:

        上文中,提到引用的使用格式:类型A& 引用名c = 变量名b,(b的类型为A)接下来逐一剖析:

类型A& 引用名c = 变量名b //定义引用变量并初始化(这是一个过程,便于理解,我们分开它)

类型A

         //这是引用变量的类型,也是b的类型,但是这类型是由b的类型决定的;

类型A& 引用名c

         //定义引用变量,即定义一个类型为A,需要接受地址而非内容的变量,定义引用变量         不开空间而其他开。

= 变量名b 

        //初始化,在这里是将b的地址赋给引用变量,而不是像普通的变量赋值一样,赋地址对应内容。

上文所提与指针是有区别的,同样是接受地址,指针是在已经开辟空间存储地址,而引用是像通过变量名来了访问内容那样进行访问的,具体咋实现的得看看编译过程,后续补充吧。

五,指针,引用和变量名在功能上的区别

变量名

引用名指针(接受地址)
做形参时传参方式传值传引用传址
开空间开,等大字节数不开开,4/8bit
跨函域改实参不改
直观性
指向的改变不能不能(因为是通过内容解引用实现指向的)
多级指向不能不能
层序结构—————

底层:

指针实现,开空间4/8;

使用层 :

不开空间,大小同类型

底层:

指针实现,开空间4/8;

使用层 :

开空间,大小4/8;

引用与指针的选用依据(指向跨域以改实参时,该选谁)

                大部分选传引用:大部分情况下,两者的应用场景相同,但引用因为直观性,不开空间,使得引用优于指针。

                变量的指向改变时选传指针:因为引用是定义后不改变的,所以指向改变时就只能用指针了。

三者的效率问题:后补

六,C++中&的用法

       因用法不同而使得功能不同。(可能像是运算符重载)

        做引用

类型&  应用名   =   被引用变量名:

        取地址

&变量   

七,引用的用法要点

        1.引用只能定义一次,指向不能被改变(这点java是可以改变的,所以其没有指针);

        2.引用不是对变量改名,而是加别名。

        3.引用可使变量有多个名,这些名都指向同一个地址;

                就像是C中的变量名a与*(&a)指向同一地址。

        4.引用在C++中是不能完全替换指针的,两者只能优势互补

九,应用

  1,(大对象)传参数——兼顾传值与传址优势

        使用的条件:看对象的大小

          对象较小时,传址与传引用几乎无差别

          对象较大时,传引用更快     

        传引用的本质:

        形参是实参的引用

        传引用的优势,意义

        不开空间,效率高;跨域改值,形参改实参且应用端直观(可读性好);

       传值,传址,传引用三者区别及选用场景

           区别                               选用场景
传值传内容,(不改原值)不需要实参改变而改变形参,参数较小时选择看参数大小
传引用传地址,跨域改原值实参改变but指向改变时看指向改变
传址传地址,跨域改原值

实参改变but指向不变时

or实参不变,实参较大时

常量传参的形参类型权限

        我们设计函数要考虑到接受常量及常量类变量的情况,这就涉及到隐式类型转换,权限的平移和缩小,也就是考虑到中间变量的常性,形参加上const

        总结:只要用常量传参的可能,形参就要加const。已满足权限平移缩小

           多级指针以引用代替以实现降级,增强指向的明确性

必要性:利用引用的直观性,来增强应用层和实现层的可读性。

1.一级指针

#include<iostream>

using std::endl;
using std::cout;

void input1(int&p)
{
	cout << p << endl;//易知道p就是一个int类型变量的引用
}
void input2(int* p)
{
	cout << *p << endl;//我们知道p是一个int*类型的指针,而*p是对int*类型的地址进行解引用。
}
int main()
{
	int num = 10;
	input1(num);//由变量名+实参的组合可知这是打印处num
	input2(&num);//这是给input函数num的地址。

	return 0;
}

        引用增强可读性体现在应用层函数名+实参本身,使得在调用时我们能清晰明了的知道要对实参/形参执行的操作;

        引用增强可读性体现在实现层:形参名,我们是直接对形参/实参本身进行操作的,而不用像对指针解引用那样是对实参/形参指向的内容进行操作(这就要求我明白,实参指向谁)。

试着比较,*p与p

       *p:这是一个地址为变量。

        p:这是一个名字叫p的变量。

        即使如此也有可能感觉不出两者的区别,就拿张三来说

        “听说张三被捕了”//这样你是不是立刻就知道了是谁被捕了

        “听说三号楼355室有上铺的家伙被捕了”//这是不是要求我们知道'355室有上铺的'是谁,才好明白该句话。(可读性差)

2.二级指针:

typedef int nodedata;
typedef struct slistnode
{
	nodedata a;
	struct slistnode* next;
}slistnode ,*pslistnode;//plistnode实际上是指针,
                        //该重命名操作是将struct slistnode类型更名为*plistnode,
                        //而plistnode自然就是struct slistnode*类型指针了
void pushback(pslistnode& plist)//注意是对指针进行引用
{
	assert(plist == nullptr);
	if (plist->next == nullptr)
	{
		plist->next = slistnewnode();
	}
	else
		//...
}

        上述代码可总结为:

typedef 类型名 *该对象指针名;//指针名为*类型名,为一级指针重命名

//接受一级指针时
返回类型  函数名 (指针名& 形参名....);

此处要注意两点

        1,对一级指针进行重命名,即将struct slistnode*类型更名为plistnode.

        2.   用引用接受一级指针的地址,(引用是可以跨域的,即直接改变一级指针(因为指向需要改变,故存在一级指针),不用对指针的指针解引用)

然而上述情况是不推荐用的

        为啥,本来引用就是占了不开空间效率高,跨域改实参,直观性强的优势击败传址,你还非得弄得花里胡哨的,代码只有你自己懂,使直观性变差。

        一般情况下不会改变一级指针名的,即

typedef int nodedata;
typedef struct slistnode
{
	nodedata a;
	struct slistnode* next;
}slistnode;
void pushback(slistnode*& plist)//注意是对指针进行引用
{
	assert(plist == nullptr);
	if (plist->next == nullptr)
	{
		plist->next = slistnewnode();
	}
	else
		//...
}

2,(大对象)引用返回

本质:

引用接收返回常量/静态区的变量地址(传址返回

引用返回与传值返回区别:

传值返回

        在函数返回返回值时,先是将返回值拷贝到寄存器上,函数在此后一般是执行销毁栈桢命令,这时候函数已经不存在了,也就是退出函数,再将返回值拷贝给给返回值接收变量(想一想,该寄存器在该过程中起着中间变量的作用,因为返回的变量已经不在了,已无法赋值了,不然接收返回值得对象只会是得到随机值);

        注意:上述过程中,实现了两次拷贝,第一次是寄存器拷贝返回变量;第二次是在返回值接收变量拷贝寄存器内的变量

        那莫问题来了,返回一个极大的对象该怎么办呢?寄存器容量极小,对象大返回时慢or直接返回不了,解决方案如下:

(传址返回,不拷贝呗)

        返回对象大,拷贝代价大,那就不拷贝公用地址,反正返回变量的作用域仅为函数域

        共用地址就考虑指针(一变量有多地址,就有多可解引用的变量,P与*(P)就公用地址)和引用。

         在返回过程中,返回的变量的内容一共要拷贝两次,就要开辟两个等大的空间,可考虑在这方面进行优化,以下是两种方案

        返回返回变量的地址+接受指针从拷贝处接受返回的地址

#include<iostream>    
  using std::cout;    
  using std::endl;    
      
  int* add1 (int i = 1,int j = 2)    
  {    
     int c = i+j;    
     return &c;    
  }    
  int main ()    
  {    
    int a = 2;    
    int b = 3;    
    int* add = add1(a,b);
    cout<<add<<endl;
  } 

               

​​​​        返回返回变量的地址+接受指针从拷贝处接受返回的地址   

        

 #include<iostream>       
  using std::cout;    
  using std::endl;    
      
  int& add1 (int i = 1,int j = 2)    
  {    
     int c = i+j;    
     return c;    
  }    
  int main ()    
  {    
    int a = 2;    
    int b = 3;    
    int& add = add1(a,b);    
    cout<<add<<endl;    
  }

         我们将两次的结果进行比较:

发现两种用法出现相同的警告,不能返回函数域的地址,会使得返回接受变量指向不明确

为解决这个问题,我们不见他放入栈区,而是放入函数调完了不会销毁的空间——静态区,常量区,动态区。

1,静态区

        定义一个static修饰值为返回值的返回变量+引用返回/指针返回

2,常量区

        因为常量区具有右常性,即定义后值不变,所以是不适做返回值,不然就没法改返回变量了

3,动态区

        适合自定义类型的返回变量

以上说明在传址返回时,指针与引用都能实现,but指针是返回地址,引用是返回值,指针在使用时需要解引用,在使用的便捷性上和直观上都不好,所以大对象的传址返回时使用引用

        在使用引用返回时,接受变量,拷贝变量,拷贝变量都是静态区变量。

引用返回的实现:

char& swap (形参)
{
  static 返回变量;
  return 返回变量;
}

char&返回类型是返回变量的拷贝份的类型,也就是说返回时不拷贝

static使得返回变量和拷贝份共用的地址在静态区,保证不被清理or覆盖

char& string = swap(实参);

char&是接收返回值的变量的类型,说明其是指向备份的地址,不进行拷贝。

传址返回的意义:

        1,不开空间,效率高,尤其是大对象

         2,域外改值,应用在数据机构中,应用层可读性强。

十,常引用

概念:

        对常量及常量类型的引用。(本质是不同类型的引用,这就必然涉及到隐式类型转换+权限匹配原则)

常量及常量类型的对象:eg.const修饰常变量;100;"a";"qwee";#define a 11;

作用:

        1.给常量取别名;

        2.传参时接受常量及常量类型传参

  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值