[C语言]指针的详解与应用(应用)--江科大

声明:我是跟着B站江科大的视频的学习过程中记录下来作者的文案,记录下来是为了方便自己日后复习。如果你也是跟着江科大的视频学习的,可以一起学习。

我把其中一些白话进行了修改,并且添加了自己的一些理解。我只有一些python基础,所以可能有错误,学起来也比较吃力,就把自己的一些理解加上去了,方便大家有和我一样没有基础的人进行学习,如果有不对的地方欢迎指正

理论部分大家可以看上一节。

[C语言]指针的详解与应用(理论)--江科大-CSDN博客

接下来结合实际应用的例子告诉大家指针是怎么用的。

我们平常的传递是值传递,来看看值传递与地址传递有什么区别。

正常情况下不会说主函数定义了一个变量,非要用指针取引用它,这不是没事找事吗?那指针间接访问通常应用在什么场景呢?

就是函数传递参数。

以上是最普通的函数的数据传递。

我们来看一下它在内存中是怎么运行这个过程的。

首先,int a=0x66;会申请内存,这四个字节就是a,地址是多少不需要管。

第二步,调用子函数fun(),将数据a传进去。在这个调用子函数的过程中,实际上是经历了一个定义变量,数据复制的过程

可以看到在子函数的中有个形参int param,一旦调用这个子函数,它就会再定义一个局部变量,也就是说它就会再找一块内存,也是int型的,这个参数它会再定义一个内容,然后将a复制过来,也就是说它调用子函数的时候,经历了一个重新申请内存,然后复制数据的过程,只不过我们没有看见,它实际上是发生了。这就是值传递

由上图可以看出来,在子函数中引用的这个变量和主函数的变量实际上是存在两个地方的。这么做可以隔离主函数和子函数的数据,防止出现一些安全问题。也就是说子函数里打印的数据是下面这个局部变量的内存里的数据,和上面主函数里的变量的内存没有关系。

为什么可以隔离主函数和子函数的数据,这样做就比较安全呢?可以看下图,在子函数里将变量的值改变,当子函数返回之后,主函数的值会不会变化呢?结果是不会的,主函数的a仍然是0x66,这个子函数的变量实际上是新申请的一块内存。这就起到了隔离的作用。

举个例子,主函数是学霸,子函数是个学渣,主函数向子函数进行传参,也就是学渣需要向学霸借份作业抄,那这个值传递就相当于什么呢?这个a就相当于是学霸的作业,我想把学霸的作业传递给学渣,但是我害怕学渣在我的作业上乱涂乱画,所以我就新找来了一个本子(一块新的内存),把学霸的作业在这个新本子上抄一遍,再给学渣,这样和主函数学霸的作业完全没有关系,无论子函数学渣怎么弄这个作业,都无所谓,当子函数运行结束后,这个复制作业(局部变量)就作废了,充分保护了主函数变量的安全。

但是这里面会有个问题。就是学霸借给学渣抄作业,如果都是选择题的话,那复制抄一份的话还是很轻松的,但是如果是一篇作文,学霸需要先把作文抄一遍在给学渣,但学霸懒得抄,既费时又费力。

所以就引出来这个指针传递,地址传递,这样就不用复制了。

比如说主函数想传递一个数组,这个数组占用100个字节,或者10000个字节,再进行值传递就会出现问题。值传递还需要再申请10000个字节的内存,然后再把这10000个数据复制过去,再把这个数据给子函数,这样数据太多了,得不偿失,既浪费时间又浪费空间。

那解决这个问题就需要用到指针传递。那指针传递是如何来解决这个问题的呢?举一个例子。

写一个函数,寻找数组中最大的数据并返回。

根据这个函数的功能来看的话,传入的参数应该是一个数组,返回值应该是数组中的最大一项。所以说参数里按理说是要写入一个数组。但数组是一个大容量数据。

C语言其实并没有提供数组的值传递,没有设计这个功能,数组只能用地址传递。那这个地址传递是如何来传递数组的呢?

下面这个子函数是用来获取传递进来的数组中的数据的最大值,用int *array来接这个参数

主函数里先定义一个数组和一个变量,找到这个数组里的最大值,并返回给Max。

因为数组名就是一个指针,所以将数组名a传递进去。

还需要加一个Count,来说明数组内有多少个数。这个函数的逻辑自己可以分析一下,还是比较简单的。

最终运行Max=5,实现了功能。

穿插一个小知识:C语言中,函数的数组形参并不保存数组的实际元素个数,因此在子函数内部使用sizeof运算符计算数组形参时,得到的结果是数组形参在内存中的大小,而不是实参数组的大小

然后我们来分析一下,他的内存经历了什么呢?

这个数组总共有6×4个字节,这么多的内存。所以要把这个数组复制传递过去是很浪费空间和时间的。

这个a就是它的首地址,然后再定义一个Max,调用一下FindMax()函数,将a的首地址传递过去,

这个子函数会定义一个局部变量,这个int *array就相当于一个局部变量,

array就会再申请一个内存,8个字节,这个int *占8个字节。

array是形参,a是实参。形参定义一个指针变量,并且把a值赋给形参作为初始值,所以array就等于a这个值。

当应用的时候,max=array[0]究竟经历了什么呢?这就用到了我们的间接访问,为什么是间接访问呢?其实是我们数组传参的时候,我们需要间接访问,而不是我们定义一个变量就间接访问,那不没事找事是吧。

首先array[0]是取出array当作指针地址下的第一个数据。当array执行到int max=array[0];,array是多少呢,是a这个值。因为a是个指针,所以我们并不把a取出来,而是把a当作一个地址,去找这个地址下的内容,所以就去蓝色箭头这里找。找到这里之后,将他第0个偏移位置找出来,他就找到了“13”这个数据,就是主函数数组里的“13”。所以max就是“13”。array[1]、array[2]……同理。

那这个参数传递过程中发生了什么呢?也就是说在子函数中只创建了一个指针变量,8个字节,而没有把这整个数组进行复制,只新建了8个字节的额外开销。它访问的还是主函数的数组,也就是说主函数和子函数共用一个数组。主函数访问的是它,子函数通过指针间接访问的也是它,他俩共用一个数据(数组a),数据并没有复制。

我们最开始为什么使用值传递呢?是为了隔离主函数和子函数的数据,防止子函数乱动数据将主函数里的数据改了,就是让学渣不要乱动学霸的作业,不要乱涂乱画,不然学霸如何交作业。

但我们传递大量数据的时候,我们不得不做一个妥协了,数据量实在是太大了,我没法隔离了,我就不隔离了,我就把数据给你了,但是你不要瞎搞,你不要在子函数里更改我的数组,要不然你返回过来我的数组被更改了,我怎么办。

我们先来看一下更改子函数的数组会不会影响主函数的数组。

先将子函数里的array的第一个值进行更改,

然后再来看一下更改后主函数的数组会怎么变化。

可以看到a[1]是66,而原来是2。也就是说这个学渣坏,把学霸的作业都改了。因为数据实在是太多了,只能冒风险把作业直接给学渣。

但是C语言还有一种可以用警告的方式来告诉它不要更改学霸的作业。

可以加一个const常量来修饰,来告诉子函数array只能被读,不能被写。加了这个const,如果你尝试在子函数中修改这个值的话,那就会报错

来讲一下值传递和地址传递有什么优缺点?

值传递隔离数据安全,但是费事,如果是一个单独的变量用值传递很方便,费不了多少事。但是对于数组这样的数据太多了,不好复制,最好用指针传递。但是为了防止你修改,不安全,加一个const进行修饰。

那既然这样的话,我定义一个全局变量,主函数和子函数都是用这个全局变量不行吗?这样也可以。但是耐不住别人用指针传递,我们要用别人的库和代码,所以还是得学一下。这样写有个坏处就是不利于程序的封装,增加了程序的耦合性。

还有一种传递参数是使用指针传递输出参数。之前说参数都是输入的,作为输入变量来输入的。那使用指针还可以实现一个功能,输出参数。

利用主函数和子函数使用同一套数据的特性,实现数据的返回,可实现多返回值函数的设计。我们参数传递的时候要避免这个问题,就是避免主函数和子函数使用同一套数据,要不然子函数更改了会有危险。

第一种是避免,第二种我们就是利用它,利用这个特性,利用主函数和子函数使用同一套数据的特性,实现数据的返回。

C语言的设计有一个弊端,就是只能返回一个数据,当我们想返回多个数据的时候怎么办呢?

比如说有个新任务,找到这个数组里的最大值并返回这个数,并返回这个数出现的次数。那他的返回值就会有两个了。C语言不支持两个返回值,但是使用指针就可以返回两个值了。

怎么返回呢?利用的就是主函数定义变量,然后使用地址传递,子函数可以修改这个变量。然后子函数返回,主函数的变量也跟着修改了。

将原来的返回值删掉,然后加入两个形参int *,用指针来传递参数。

这是直接将Max赋值进行行不行呢?这是不行的,这是跨级赋值。Max是普通变量,相当于0级指针,而int *max相当于一级指针,所以要在Max前面加个取地址。

这是应用的什么特性呢?就是子函数里的max和主函数里的Max使用的是同一个地址下的数据,然后在子函数里进行更改,主函数里的数据也会随之更改。

因为max是个指针,所以要用*max取出地址下的数据,count同理。这样在意义上就实现了多个返回值。

画一个内存图来表述一下这个过程。主要关注Max。

Max是int型变量,在内存中占了4个字节,它的地址是系统分配的,由于没有给Max赋初值,所以里面的值是随机的。

然后调用FindMaxAndCount()函数将Max的地址传进去,然后子函数的int *max会申请内存,占8个字节,里面存的数据内容是Max的地址。

然后*max就找到这个地址,就找到了Max的值。

所以*max=array[i];这个语句给*max赋值就相当于给Max赋值。假如*max=1,那Max是不是就等于1了。这是不是就实现了指针传递返回值的功能。

而且子函数的参数数量可以随便设置,这样就实现了返回多个返回值的功能,打破了C语言只有一个返回值的限制。

在标准C语言库中也可以找到一些这样的例子。

比如strcpy();就是把右边的string赋值给左边的。可以看到左边没有const,右边有,说明左边应该是会修改这个变量,右边是输入参数,不允许修改。

直接将str1和str2传递进去,这是因为数组名就是指针,所以前面不用加&符号。

可以看到输出两个“HelloWorld”

接下来看 传递返回值 这个内容。有时我们可以看到它的返回值也是一个指针类型的变量。

举个例子,用51单片机做个时钟。

我们为什么用指针不用全局变量呢,就是为了封装。

假设我们封装了一个模块,这个模块里有个数组叫做time数组,我想读取一下这个数组,那怎么办呢?

就要用指针作为返回值,然后持Time的句柄,句柄就是把手的意思,我只要捏住了你的把手,我就可以操纵你。

我们就可以写一个访问的函数,一般我们不直接把这个变量公开出去,变成大家共同访问的,这样不利于封装,所以我们会用一个函数间接访问。比如说写个函数GetTime(void)。

要注意这样写要把“int *”作为一个整体。

然后这样就可以间接访问了。

那在主函数里就可以这样访问Time,用指针间接访问。

GetTime()返回的是Time数组的地址,我用pt去接他,那我pt是不是也指向了Time这个数组。是不是我就拿到了这个数组的句柄,我就可以访问这个数组了。尽管他在模块里无法直接访问,那我可以通过指针间接访问。

这里有个注意事项:不能把局部变量的指针返回。

因为函数结束之后,局部变量会被销毁。即使还是指向原来的地址,但是内容被销毁了。

可以看到第二个59和第三个55没有了。所以说不要把一个函数结束后会被销毁的变量给返回出去,这样即使被返回了也是没有意义的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值