当前位置: 首页 > article >正文

对指针和引用的深入理解

1 引言

经常在函数的定义中,看到有些参数是指针,有些参数是引用。
指针还好理解,但引用的使用不大理解。既然,引用跟变量本身是一回事,为何要使用引用呢?不是多此一举么?
带着这个问题,开始探索二者之间的差别,以及定义引用的目的。

2 函数参数中指针的含义

查资料说,指针是对变量的拷贝
这句话有几个意思。
一是,函数调用时,会先创建一个地址空间(一般为4字节),然后把变量的地址值拷贝过来
例如:

void test(int *p)
{
    int a = 1;
    p = &a;
    cout << p << "" << *p << endl;
}
int main(void)
{
    int *p = NULL;
    test(p);
    if(p == NULL)
        cout << "指针p为NULL" << endl;
    system("pause");
    return 0;
}

按照常规理解,main函数中的p指针为NULL,传递到test函数以后,该指针值似乎被修改了。

p = &a;

那么在main函数中,理论上下面的语句不会执行到。

if(p == NULL)

但,实际结果与我们想象的不一样,这段代码会执行到。
刚开始我也不理解。
后来,当我知道了“指针是变量地址的拷贝”这个意思后,才明白了。
原来,函数被调用的时候,对于指针参数,首先会在堆栈中创建一个地址,test函数中的p,就是一个新的地址,也是新的内存空间。
这个内存空间,与main函数中的指针p,不是同一个内容。
也就是说:
这两个p,是两个不同的地址。
main函数中的地址p,是在main函数堆栈中的一块内存,只是因为它为NULL,所以这块内存的值为0(多数编译系统中,NULL值都为0,但不一定,所以,这里就算是示意吧)。
打个比方,我们电脑上的内存是一间超大的酒店,有好多好多房间,内存空间就是这些房间(主流的8G内存,就好比有80亿个房间)。
房间的作用有两个:
一些房间用于客人居住,这里,客人就好比实际的数据。
另外一些房间不住人,只用来放一块大牌子,这个牌子上写着,xx客人住在yy房间。
按照这个说法,main函数中定义指针p,相当于服务员给开了一个房间,假设房间号为A,放标牌,只是,标牌啥也没写(或者是打叉,这个跟酒店的管理规定有关)。
当调用test函数时,参数为p指针,该函数告诉服务员又开一个房间,假设这个房间号为B,这个房间也放标牌,标牌上的内容,跟调用该函数之前定义的标牌的内容完全一致,也就是说,目前也是空(或者打叉)。
好了,这里是关键。
在test函数中,p值会修改,变成a变量的指针,也就是说,B房间里的标牌上不再是空的,而是写有内容的了。
但是,但是,A房间里标牌,还!是!空!的!
到这里,终于明白“指针是拷贝”的真正含义了。
其实,test函数定义也让人稍微有些容易混淆。
如果test函数是下面的这个样子。

void test(int* ptr)
{
    int a = 1;
    ptr = &a;
    cout << ptr << "" << *ptr << endl;
}

定义成ptr,跟main函数中的p不一样,有可能会更好理解一点。
但是,关键还在于要彻底理解上面所讲的“拷贝”的含义。
第二个意思是,既然要开新的房间,那么,费时(虽然这点时间对于电脑来说不算什么),费空间(虽然也不算什么,不就4个字节么)。

3 引用的含义

又查资料,得到如下信息:
引用,就是变量的别名,操作引用就是操作变量本身。
举例。

int a = 1;
int *pA = &a;
int &b = a;

a是一个实实在在的变量,按照上边的例子,a是住在酒店的客人,&a表示该客人所在的房间号,pA也是一个房间,里面住的不是客人,而是有一块牌子,上面写明了客人a所在的房间号。
![[Pasted image 20240620112255.png]]
源内容参考:
![[240531-1656-指针和引用示意图.pptx]]
于是,pA就应该是301房间号,pA本身也在房间201中。
b是a的引用,相当于别称,就像,张明明是大名,小明是他的小名,妈妈说小明赶紧去上学,老师说张明明你来回答问题,其实说的是同一个人。
好了,据此再来理解函数参数中的引用。

//形参是引用类型
void swap(int &a,int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
int main()
{
    int i = 3, j = 5;
    swap(i,j);
    cout << "i=" << i << "" << "j=" << j << endl;
    return 0;
}

这段代码,把变量i和j的值交换。

4 函数参数中为什么要引用?

还是以交换两个变量的值为例。

4.1 第一种情况,直接使用形参,试试看

//交换变量的值
void swap(int a,int b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
int main()
{
    int i=3,j=5;
    swap(i, j);
    cout << i << "" << j << endl;
    return 0;
}

执行的结果,与我们的想象不一样。
还按照上边酒店房间的例子。
假设,客人i是一名女客人,小红,住在301房间。
客人j是一名男客人,小明,住在302房间。
函数swap在被调用时,在栈里创建两份内存。相当于服务员开了两个空房间,假设为401和402,并把小红和小明各克隆一份,叫做小红1号和小明1号,并分别安排到401和402房间。
注意:这里i(小红1号)和j(小明1号)不是原来的,是克隆人。
把401和402房间里的人交换,变成402住着小红1号,401住着小明1好。
但是,但是,原来的301和302房间的人并不变化,小红还在301,小明也还在302住着。
任务失败。

4.2 第二种方式,使用指针

void swap(int* p1,int* p2)
{
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
int main()
{
    void swap(int*, int*);
    int i = 3, j = 5;
    swap(&i, &j);
    cout << i << "" << j << endl;
    return 0;
}

房间301住着小红,房间302住着小明。
在swap函数中,p1是个指针,就是个标牌,标明301房间,即小红住的房间号。同样地,p2表示小明住的房间号。
*p1,取地址里的值,相当于说,先找301的客人,就是小红。同样地,*p2是客人小明。
第一步,把小红克隆一份到temp(想起了阿凡达),外表、思想完全一样的。
再把小明克隆一份,放到小红身上,这个时候,小红也不再是小红,而是小明了。
同样地,把temp(克隆后的小红)又克隆一份,放到原来的小明身上,这个后来者就是小红。
克隆后的小红temp在swap函数调用完毕就销毁了。
最后的结果就是,房间还是那两个房间,但通过神秘的克隆和销毁技术,神不知鬼不觉的把两个房间里的人更换了。
于是,301房间不再住着小红,而是住着小明。302房间则住着小红。
任务完成。甭管克隆有没有副作用,反正是完成预定的目标。
美中不足的是,这中间总是得通过房间号来完成。有没有更好的方式呢?

4.3 第三种方式,通过引用来完成

这就是上上上边的例子了。
不用通过房间号(指针)来操作,直接指示。
克隆小红(a)到阿凡达(temp),克隆小明(b)到小红身上,再把阿凡达克隆一份到小明身上。
完成任务。
是不是简洁多了?
这,就是引用的用途。

5 再来一个引用的例子

void test(int* &ptr)
{
    int a = 1;
    ptr =& a;
    cout << ptr << "" << *ptr << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
    int *p = NULL;
    test(p);
    if(p != NULL)
    {
        cout << "指针p不为NULL" << endl;
    }
    system("pause");
    return 0;
}

test参数,*&ptr又是如何理解?
我的理解是,如果没有&符号,如下:

void test(int* ptr)

则函数在调用的时候,ptr就是拷贝出的一个新地址,就是服务员开的新房间,结果前边已经分析过。
带&符号,如下:

void test(int* &ptr)

可以把&ptr整体当成一个指针,但是,注意了,这个指针不再是创建出来的一个新内存地址,而是跟原来的地址一模一样。
或者说,只是表明参数是个指针而已。

int* p = NULL;
test(p);

操作ptr,就是操作main函数中的p。
继续酒店房间的例子。
main函数中的p,假设是501房间里的标牌,标牌上目前啥也没写。
调用test函数,其参数是*&ptr,表示要操作的是房间的标牌(指针类型),而且是,只能是501房间的那块标牌,因为有&引用符号。
好了,下面的代码:

p = &a;

就是改写了501房间的那块标牌,这个时候,标牌上写着905。
于是,改写了。
于是p不是NULL。


http://www.kler.cn/a/372449.html

相关文章:

  • Unity3D 逻辑服的ECS框架设计具体实现详解
  • @tarojs/components 和 taro-ui 中的组件之间的区别
  • 18 Docker容器集群网络架构:一、etcd 概述
  • 使用Python和OpenCV实现火焰检测
  • 免费可离线的AI抠图工具(鲜艺AI抠图 )免费下载
  • 探熵科技|以科技赋能销售,创造卓越业绩。
  • uni-app 运行HarmonyOS项目
  • 【YApi】接口管理平台
  • MacOS M1在CentOS8下安装Docker遇到的问题
  • 如何写出爆款脚本,很多人都忽略了这一项——口语化
  • python json模块介绍
  • WPF+MVVM案例实战(九)- 霓虹灯字效果控件封装实现
  • 宠物空气净化器哪个好?希喂、352真实测评分享
  • 鸿蒙系统:核心特性、发展历程与面临的机遇与挑战
  • LLAMAFACTORY:一键优化大型语言模型微调的利器
  • LeetCode115:不同的子序列
  • 浅析正交投影矩阵和透视投影矩阵的推导
  • OpenJudge:找和为K的两个元素
  • Flutter 自定义组件继承与调用的高级使用方式
  • 重构代码之提取子类
  • 聚水潭商品信息集成到MySQL的高效解决方案
  • 蓝海创意云入选中国夏衍电影学会工业与科技影视专业委员会成员单位
  • PyTorch distributions模块介绍
  • Mybatis-09.基础操作-删除(预编译SQL)
  • 从零学习大模型(八)-----P-Tuning(上)
  • 【大数据学习 | kafka】kafka的shell操作