目录
一,变量的访问方法
我们在对一个已经创出造的事物进行标记,以便于对该变量进行访问。这标记通常为变量的名字和(变量在内存上的住址)变量的地址 ,这二者就是我们找到该变量的依据。
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.传参时接受常量及常量类型传参