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

【C语言进阶】带你由浅入深了解指针【第四期】:数组指针的应用、介绍函数指针

前言

上一期讲了数组指针的原理,这一期接着上一期讲述数组指针的应用以及数组参数、函数参数。

         首先看下面的代码进行上一期内容的复习,pc应该是什么类型?

char* arr[5] = {0};
xxx pc = &arr;

分析

①首先判断arr是一个数组数组的每一个元素类型是char*,所以arr是指针数组

&数组名表示整个数组的地址,pc一定是一个数组指针

③既然是数组指针,可以先写(*pc)表明是一个指针指向的是一个元素为char*类型的数组,一共有五个元素,即char* (*pc)[5] pc = &arr;

        尝试使用数组指针遍历数组每一个元素;这里着重理解:若pc是数组指针,那么*pc是数组名,数组名其实就是数组首元素的地址,所以若需要打印数组元素,这里必须两次解引用

        但是正常的人类不会这么来遍历数组,直接使用一级指针就能完成遍历的操作,不建议这样去用

1. 数组指针常见的用法

1.1 作为参数遍历二维数组:

        使用数组指针遍历一维数组确实有些脱裤子放屁,但是使用数组指针却可以很轻易的遍历二维数组。

分析

        3*5的二维数组其实是三个int数组拼接而成,所以我们只需要使用数组指针指向第一行,数组指针+1就会跳转到下一行,每一行中需要对数组指针解引用,可以获得一维数组名即一维数组首元素的地址,即类型为int*,此时再加上列j,最后再进行解引用就能获得每一个元素了。  

      

1.2 下列是指针还是数组?

 // 指针大全
int main()
{int i = 10;int* p1 = &i;int** p2 = &p1; char arr1[5] = { 0 };// p3,p5等价char* p3 = arr1;char p4 = arr1[0];char* p5 = &arr1[0];int* arr2[5] = { 0 }; char(*p6)[5] = &arr1;  // ?int* (*p7)[5] = &arr2;// ??int(*parr[10])[5];  // ???return 0;
}

   答案揭晓:

 // 指针大全
int main()
{int i = 10;int* p1 = &i;int** p2 = &p1; // 二级指针,存放一级指针变量的地址char arr1[5] = { 0 };// p3,p5等价char* p3 = arr1;char p4 = arr1[0];char* p5 = &arr1[0];int* arr2[5] = { 0 }; // 指针数组,每一个元素都是int*类型的char(*p6)[5] = &arr1; // 数组指针,指向数组char arr1[5]int* (*p7)[5] = &arr2;// 数组指针,指向数组int* arr2[5]int(*parr[10])[5]; // parr首先和[]结合,所以这是一个数组,去掉这两个部分,剩下int (*) [5]是一个数组指针// 所以这是一个可以容纳10个数组指针的数组return 0;
}

        最后一个 int(*parr[10])[5];有些难理解,我们可以采取的策略是:先定性,再去掉,最后判断

①先定性:parr和【】首先结合,所以这一定是一个数组。

②parr[]去掉。

③判断剩下的部分:int(*)[5],这很显然是一个数组指针。

④下结论:这是一个可以存放10个数组指针的数组

2. 数组参数和指针参数

        写函数的时候,难免会把指针或者数组名传递给参数,我们该如何设计函数呢?

2.1 一维数组传参

下面的参数正确吗?

① 正确。一维数组传参的时候可以不指定数组长度。

②正确。形参和实参一致。

③正确。传入数组名,本质上就是首元素的地址,每一个元素是int*,可以使用指针接收。

④正确。形参和实参一致。

⑤正确。arr2是一个存放一级指针的数组,直接传入数组名,就是传递首元素的地址,首元素为一级指针,要存放一级指针的地址可以使用二级指针。

2.2 二维数组传参

下面的参数正确吗?

①、③正确,②错误原因图中已经标识。

        二维数组传入数组名,在之前的例子已经讲过了,代表了首元素的地址,而首元素是一个数组,那么就是整个数组的地址,这个数组有五个元素,每一个元素是int类型,这里需要使用数组指针来接收地址。所以只有③正确。

2.3 一级指针传参

非常简单,下图可以直接概括。

2.4 二级指针传参

①正确。二级指针传参使用二级指针接受肯定是没有问题的。

②正确。使用一级指针的地址传参,当然也是没有问题的。

反过来想,如果形参使用二级指针,那么实参能传什么呢?除了刚刚讲的前两种情况,这里可以存储指针数组名。这是因为指针数组名是数组首元素的地址,首元素是一级指针,换言之也是一级指针的地址。

3. 函数指针介绍

顾名思义指向函数的指针。

        这要牵扯到如何求函数的地址,我们知道&数组名是取出数组的地址,以此类推,&函数名可以取出函数的地址。

        如何存放函数的地址呢?这就用到了函数指针,函数指针的定义也非常简单,首先确定这是一个指针*p,然后在后面加上大括号,确定这是一个函数指针,在大括号里面输入形参类型,在整个表达式前面标明返回类型。

        利用函数指针调用函数也就顺理成章了,直接解引用之后按照函数的方法直接调用即可。

        看似这一切都是脱裤子放屁,我为什么不直接调用函数呢?这是因为我们视野所限,后面还有更高级的玩法,例如将函数指针作为函数传递传给另外一个函数......

        需要注意的一点是,这里的p可以不使用*解引用,因为add本质上是函数的地址,那么p存放的也是函数的地址,如果能够这样使用:add(),那么为什么不能这样使用:p();这里加上*只是为了明确这是一个指针。如果要加上*号,记得要加上括号。

3.1 函数指针的简单使用

作为另外一个函数的形参,在另外一个函数进行使用。

        看到这里,你心中仍然觉得这是多此一举,那我为什么不直接调用呢?其实这涉及到面向对象的内容,试想一下:如果有四个方法,分别是加减乘除的功能,这四个方法除了方法名和方法体不一样,形参和返回值都是一样的,这时候我们只需要传入不同的方法名给这个函数指针,就能实现不同的方法,这就是多态

3.2 函数指针的题目

首先来看一段来自《C陷阱和缺陷》这本书中非常有意思的代码。

①首先看void(*)(),如果这是void(*p)(),这就是函数指针,此时把变量名去掉,这就是变量的类型;类比int* p去掉p,int* 就是变量的类型一样。

②整体对这个变量类型大括号,后面再放一个0,这就是将0强制类型转换为函数指针类型,地址为0的地方存在一个函数,这个函数不需要返回值也不需要形参。

③然后对整体函数指针进行解引用,再进行函数的调用。

总结以上代码就是一次函数的调用,调用的是0作为地址的函数,这个函数没有返回值没有形参

以下是这本书的原话:

        继续看一段来自这本书的一段代码。

分析

①从signal入手,signal首先和()结合,()内部一个是整型类型,一个是函数指针类型,这是函数的声明,函数的声明的形参只需要注明形参的类型即可,例如:int add(int,int)。

②将signal(int,void(*)(int))去掉之后,看剩下的部分,void(*)(int),这毫无疑问是声明函数的返回值

③所以总结一下,这个就是一个函数的声明,返回值是一个函数指针

我们可以使用typedef简化一下:如此一来,一眼就能看出这是一个函数声明。

3.3 函数指针的用途

给出一个需求,计算两个数字的四则运算。

 

        我们发现,在swich语句中代码存在大量冗余,只有一行不一样,这一行只是调用的方法不一样,剩下的部分都一样,我们该如何解决?        

        使用函数指针就可以完美解决,创建一个函数,形参是函数指针,根据传入的具体实参的不同,调用不同的方法,这个其实就是面向对象的多态

回调函数也是这么做的,实现定义好函数指针,适时地进行调用函数。

        今天的内容就到这了,下一期是最后一期关于指针的内容,如果对你有帮助,可以点赞收藏评论,一键三连,你的支持是我更新的最大动力!! 

http://www.dtcms.com/a/273869.html

相关文章:

  • 【Spring Boot】Spring Boot 4.0 的颠覆性AI特性全景解析,结合智能编码实战案例、底层架构革新及Prompt工程手册
  • mysql的LIMIT 用法
  • 1 APP-OneNET 生成token密钥
  • Ubuntu2404修改国内镜像
  • 我的第一个开源项目|Geex:道阻且长的开源之路
  • docker的学习
  • React中Redux基础和路由介绍
  • 将手工建模模型(fbx、obj)转换为3dtiles的免费工具!
  • threejs案例开发-中国3D国旗动画
  • PostgreSQL 查询库中所有表占用磁盘大小、表大小
  • [Meetily后端框架] 多模型-Pydantic AI 代理-统一抽象 | SQLite管理
  • 共享储能电站在工业用户经济调度中的matlab仿真
  • 需求升级,创新破局!苏州金龙赋能旅游客运新生态
  • Go中使用wire进行统一依赖注入管理
  • 【JavaScript高级】构造函数、原型链与数据处理
  • 3 OneNET-调试器模拟上报数据
  • 深入理解Spring声明式事务的同步管理机制
  • C++ 面向对象 - 对象定义方法汇总
  • MySQL:分析表锁的常见问题
  • Flowable 使用遇到问题
  • Redis Sentinel哨兵集群
  • 碳中和目标下的全球产业链重构:深度解析与未来路径
  • Maui劝退:用windows直接真机调试iOS,无须和Mac配对
  • 单片机显示Unicode字符介绍
  • PDXP、UDP与HDLC协议技术解析:架构、应用与对比研究
  • SpringBoot 拦截器和过滤器的区别
  • 如何高效验证代理IP的可用性与稳定性
  • 瀚高数据库提交数据后,是否需要COMMIT(APP)
  • oracle
  • 从代码学习深度学习 - 针对序列级和词元级应用微调BERT PyTorch版