C语言 指针(三)

上篇文章我们对指针进行了深入了解,接下来我们继续学习指针。

一、二级指针

前面我们学习了指针变量,我们知道只要是变量就会有地址,那么指针变量的地址又存放在哪呢?下面我就要介绍如何存放指针变量的地址——二级指针

我们先来看一段代码:6410fe5bcf334e0db3f91f1e1563c211.png

我们可以看到变量ppa存的是变量pa的地址:

0d387c185855428fac3384960c37e4db.png

前面我们对 int* 进行了理解,现在我们再对 int* * 进行理解一下:

94678ec81b8542b6a8ef2622cd3a861b.png

向其他的类型也是一样的如float* *,char* *等等。

在理解二级指针后,我们在学习一下二级指针的运算。

我们在学习一级指针的时候对pa解引用找到的就是a。*pa=a

那么*ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa。

**ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a。

6f238b0644444186a342580123541d8a.png

当然二级指针的运算也和一级指针相似,除了二级指针还有三级指针,四级指针等等,不过用的非常少。

二、指针数组

下面我们学习学习一个新知识——指针数组

1.什么是指针数组

我们先来思考一个问题,指针数组是指针还是数组?

我们不妨来类比一下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?是存放指针的数组。注意:指针数组是数组不是指针!

我们知道整型数组存放的是整型数据,字符数组存放的是字符数据,那么指针数组存放的就是指针(数组中的每个元素是指针类型),也就是说指针数组的每个元素都是⽤来存放地址(指针)的。

b0c8ace99d474639a45d3114e734b6c8.png

那么指针数组是怎么表示的呢?我们只需要把数组名前面的类型定义为指针类型就可以啦!

如:int* arr1[5]       float* arr2[10]   

9c20dd5ea246416d8483f7a5f27d5616.png038c802613f947eaac0e52644e899b86.png

指针数组的每个元素是地址,⼜可以指向⼀块区域。

2.指针数组模拟二维数组

在学习完指针数组后我们就可以用其模拟一下二维数组啦,不过模拟出的是二维数组的效果,并不是真的二维数组。我们先来看代码:

890aa652db56409f85bf984609db6491.png

我们可以看到上段代码输出的效果和二维数组是一样的,arr[i]是访问arr数组的元素,arr[i]找到的数组元素指向了整型⼀维数组,arr[i][j]就是整型⼀维数组中的元素。也就是说int*arr[3]中存放的元素都是数组,我们有知道数组名是数组首元素的地址,所以又通过对每个数组进行解引用找到每个数组中的元素。

4297f17b52064d1599c2c7cd32ec2893.png

上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。

三、字符指针变量

在之前的学习中我们知道有⼀种指针类型为字符指针 char*,它的用法我相信大家都比较熟悉了,下面我个大家介绍另外一种使用方式,我们先来看代码:

6bcc6e07b1ac4158b71e8a6ab85890c6.png

这里我们看到好像是把字符串abcdef,放进了字符指针pa中,而且这里也并没有解引用,但其实这里本质是把字符串abcdef的首字符的地址放在了pa中。

936c65ac5d4e4bebb0d23d2501a163b0.png

在这里我们可以把字符串想象成一个字符数组,但是这个数组是不能被修改的,我们称作常量字符串,当它出现在表达式中时,它的值是第一个字符的地址。f9c5892ff15542379cfd248eb6a25c21.png

e0f3bae9a05d466a93b5090106f45388.png

好了,在介绍完这些后我们来看一段有趣的代码:8d449ba52a7c4c1e8adcecb3dc7e07cd.png这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域, 当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

四、数组指针变量

1.什么是数组指针变量

之前我们学习了指针数组,我们知道它是一种数组,那么数组指针变量又是什么呢?我们不难猜出它其实是指针变量,不过它存放的是数组的地址,能够指向数组的指针变量。

那么它有是怎么表示的呢?17d52e26b77c43f5875df1ab2d38849e.png

注意:arr2先和*结合,说明arr2是⼀个指针变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 arr2是⼀个指针,指向⼀个数组,叫数组指针。[]的优先级要⾼于*号的,所以必须加上()来保证arr2先和*结合。

2.数组指针变量的初始化

数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的&数组名,如果要存放个数组的地址,就得存放在数组指针变量中

a8e9397e9ee14303bae79e81c5957f3e.png

我们调试也能看到 &arr 和 pa 的类型是完全⼀致的。

172d2eb340af4e148afbd4076279359f.png

571bc9133fe240c2a13f1d2b892ffcfd.png

3.数组指针的使用

上面我们了解了数组指针变量,那么该怎么用呢?我们直接来看代码:

b6d2fc1480104ca485694963f64f840d.png

这里pa==&arr, *pa==*&arr==arr   (对pa解引用就等价于*&arr,&和*就等于抵消了,所以*pa找到的就是arr)。当然它的用法不仅仅这一种哦,我们继续往下看。

4.⼆维数组传参的本质

我们了解数组指针后,就可以了解⼆维数组传参的本质了。过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:056d4e34e5784137ac080b1ce1584f04.png

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

88b5492129c14e208bcf9d7a2c96b459.png

总结下来:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

五、函数指针变量

1.函数指针变量的创建

在函数指针变量的创建之前,我们先要了解什么是函数指针变量,根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论: 函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

那么函数有没有地址呢?我们可以来测试一下:da6228cce4bd4b2782dcfc4495e812f2.png

我们可以看到确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的⽅式获得函数的地址。如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针⾮常类似。如:

d58d5273d3c549bc842edab55e49b7ba.png

0af3216c3c604114a6e81075d3324cba.pngea428d39ca2e4a2d927f39ec1a946030.png

2.函数指针变量的使⽤

在了解函数指针变量的创建后,我们来学习怎么使用函数指针变量。使用·函数指针变量无非就是通过函数指针调⽤指针指向的函数。

47b68f78c4c94b60bcea0fb60308c6a3.png

是不是很简单。

3.回调函数

回调函数就是⼀个通过函数指针调⽤的函数。如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条 件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。我们先来写一个简易版的计算器代码:

0128045987704e69a62dfbae30e470fe.png

54daa44425d94a60adc12f07864c7e55.png

c99d9f6a4f26491db711dee1476da7c1.png

e2b4f74ab52f4133b45b7012a2e8c4b7.png

上面代码中红色框出来的我们可以发现有许多代码都是一样的,这样就显得有些冗余,我们有没有办法简化代码呢?当然可以。因为红⾊框中的代码,只有调⽤函数的逻辑是有差异的,我们可以把调⽤的函数的地址以参数的形式传递过去,使⽤函数指针接收,函数指针指向什么函数就调⽤什么函数,这⾥其实使⽤的就是回调函数的功能。

我们可以中间一段代码改造一下:

518120306e7c441996bb166b46da80a3.png

b32273868f444d77be219e2166cd96aa.png

我们来理解一下这段代码

f0234570dd2142d88ee2d6191f6bb8e9.png

以上就是函数回调的使用啦。

六、函数指针数组

1.函数指针数组的定义

前面我们学习了指针数组,它是存放指针的数组,而函数指针数组也是存放地址的数组,只不过它存放的是函数的地址。那么它又是怎么定义的呢?我们只用把函数指针和数组结合行了。如:

int (*parr[10]) ()

在这里parr先和 [] 结合,说明 parr是数组,数组的内容是 int (*)() 类型的函数指针。

下面我们就来学习一下它怎么用。

2.转移表

我们还是用上面的计算器代码来举例:

我们再来改造一下这个代码66593f60f83a48d5a4efcfb19017b519.png

12304bd539d64fac9299c08b7f8c2a55.png

9224b4ce8dd24716ac1b06d4104afa0a.png

下面我们就解释一下这段代码:3b922cb2a59c4938a9c0c0aad4dabc62.png

上面代码当input=1时,就找到函数指针数组的第二个元素(因为第一个元素存的是空指针)也就是add函数的地址,对其进行解引用找到的就是add函数然后去调用这个函数。这种方式就叫做转移表。

好了,以上(以及前两篇文章)就是指针的所有内容了,感谢大家的观看,希望能给大家带来帮助,如果哪里有不对的地方,感谢指正(您的指正对我就是莫大的帮助!)。

6942ea32227a4bd98893e0d253978e76.gif

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值