【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.pswp.cn/bicheng/88569.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88569.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在HTML中CSS三种使用方式

一、行内样式在标签<>中输入style "属性&#xff1a;属性值;"。(中等使用频率)不利于CSS样式的复用&#xff1b;违背了CSS内容和样式分离的设计理念&#xff0c;后期难以维护。<p style"color: red">这是div中的p元素</p>二、内部样式在…

汽车功能安全-软件单元验证 (Software Unit Verification)【用例导出方法、输出物】8

文章目录1 软件单元验证用例导出方法2 测试用例完整性度量标准3 验证环境要求4 软件单元验证的工作产品1 软件单元验证用例导出方法 为确保软件单元测试的测试案例规范符合9.4.2要求&#xff0c;应通过表8所列方法开发测试用例。 表8 软件单元测试用例的得出方法&#xff1a; …

MySQL内置函数(8)

文章目录前言一、日期函数二、字符串函数三、数学函数四、其它函数总结前言 其实在之前的几篇中我们也用到了内置函数&#xff0c;现在我们再来系统学习一下它&#xff01; 一、日期函数 函数名称描述current_date()获取当前日期current_time()获取当前时间current_timestamp(…

苍穹外卖项目日记(day04)

苍穹外卖|项目日记(day04) 前言: 今天主要是接口开发, 涉及的新东西不多, 需要注意的只有多表联查和修改的逻辑,今日难点: 1.菜品的停起售状态设置 2.套餐的停起售状态设置 3.动态sql中的 useGeneratedKeys 与 keyProperty 两个参数 一. 菜品的停起售状态设置 ​ 在菜品的停售中…

React之旅-05 List Key

每个React的初学者&#xff0c;在调试程序时&#xff0c;都会遇到这样的警告&#xff1a;Warning: Each child in a list should have a unique "key" prop. 如下面的代码&#xff1a; const list [Learn React, Learn GraphQL];const ListWithoutKey () > (&l…

[特殊字符] 人工智能技术全景:从基础理论到前沿应用的深度解析

&#x1f680; 人工智能技术全景&#xff1a;从基础理论到前沿应用的深度解析 在这个AI驱动的时代&#xff0c;理解人工智能的核心技术和应用场景已成为技术人员的必备技能。本文将带你深入探索AI的发展脉络、核心技术差异以及在各行业的创新应用。 文章目录&#x1f680; 人工…

Go语言教程-环境搭建

前言 Go&#xff08;又称 Golang&#xff09;是由 Google 开发的一种 开源、静态类型、编译型 编程语言&#xff0c;于 2009 年正式发布。它旨在解决现代软件开发中的高并发、高性能和可维护性问题&#xff0c;尤其适合 云计算、微服务、分布式系统 等领域。 Go 语言国际官网…

windows指定某node及npm版本下载

下载并安装 nvm-windowshttps://github.com/coreybutler/nvm-windows/releases&#xff08;选择 nvm-setup.zip&#xff09;。打开命令提示符&#xff08;管理员权限&#xff09;&#xff0c;安装 Node.js v16.15.0&#xff1a; nvm install 16.15.0 nvm use 16.15.0 验证node版…

每天一个前端小知识 Day 28 - Web Workers / 多线程模型在前端中的应用实践

Web Workers / 多线程模型在前端中的应用实践&#x1f9e0; 一、为什么前端需要多线程&#xff1f; 单线程 JS 的瓶颈&#xff1a;浏览器主线程不仅负责执行 JS&#xff0c;还要负责&#xff1a; UI 渲染&#xff08;DOM/CSS&#xff09;用户事件处理&#xff08;点击、输入&am…

python:ImportError: cannot import name ‘ParameterSource‘ from ‘click.core‘

浏览器访问网站抛错&#xff1a;ImportError: cannot import name ParameterSource from click.core (E:\environment\python\Lib\site-packages\click\core.py)问题分析&#xff1a;1. click 版本问题ParameterSource 可能是在某个特定版本的 click 库中引入的&#xff0c;而你…

flink 去重

LOCALTIMESTAMP as time_stamp ts as case when time is null then CURRENT_TIMESTAMP else TO_TIMESTAMP_LTZ(time, 0) end , watermark for ts as ts - interval ‘60’ second PARTITION BY 的都有回撤流 group by 的没有回撤流 因为算的是指标 开窗又慢 SELECT * FROM (…

【音视频】TS协议解析

参考博客&#xff1a;https://blog.csdn.net/rell336/article/details/38109621?utm_mediumdistribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_sourcedistribute.pc_relevant_t0.none-task-blog-BlogCommendFromMac…

uniapp 日期组件可选择年月

month-picker 月份选择器组件 组件介绍 month-picker 是一个用于选择年月的自定义组件&#xff0c;基于 uni-app 开发&#xff0c;提供了简洁的月份选择功能。 解决弹框底部出现底部页面区域 safe-area属性设为true时&#xff0c;即可解决这个问题效果如图功能特点 支持选择年份…

从真人到数字分身:3D人脸扫描设备在高校数字人建模教学中的应用

在影视、动漫、游戏等数字创意产业蓬勃发展的当下&#xff0c;超写实虚拟数字人凭借其高度逼真的形象&#xff0c;成为行业关注的焦点。无论是影视特效中栩栩如生的角色&#xff0c;还是游戏里精致的NPC&#xff0c;超写实虚拟数字人的制作都离不开先进的技术支撑。而3D人脸扫描…

你以为大数据只是存?其实真正的“宝藏”藏在这招里——数据挖掘!

你以为大数据只是存&#xff1f;其实真正的“宝藏”藏在这招里——数据挖掘&#xff01; 曾经我也天真地以为&#xff0c;搞大数据就是会写几个SQL、部署个Hadoop集群&#xff0c;结果真到项目现场&#xff0c;甲方爸爸一句&#xff1a;“给我挖掘一下用户的购买意图”&#xf…

LeetCode经典题解:128、最长连续序列

“最长连续序列”是一道极具代表性的数组处理问题&#xff0c; 本文将带你从直观思路出发&#xff0c;逐步推导出最优解法&#xff0c;并通过场景化记忆技巧掌握核心逻辑。 一、题目描述 题目&#xff1a;给定一个未排序的整数数组 nums&#xff0c;找出数字连续的最长序列&…

电力分析仪的“双语对话”:CCLinkIE与Modbus TCP的无缝连接

在工业自动化领域&#xff0c;协议兼容性问题如同“方言壁垒”&#xff0c;让不同品牌、不同系统的设备难以高效协同。对于电力分析仪这类关键设备而言&#xff0c;如何打破CCLinkIE与Modbus TCP协议的“语言障碍”&#xff0c;已成为工程师优化系统集成的核心课题。 为何需要协…

暑假复习篇之文本编译器

一、知识点补充【在此次示例代码上显示的关键用法】知识点1、JMenuBar&#xff1a;菜单栏的容器&#xff0c;通常添加到JFrame的顶部。关键用法&#xff1a;add&#xff1a; 添加菜单到菜单栏2、JMenu&#xff1a;菜单条目&#xff08;“文件” “编辑” 等&#xff09;&#x…

Linux自动化构建工具(一)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录Li…

目标检测流程图绘制

目标检测流程图绘制作为一个长期科研的苦命人&#xff0c;我一般采用Processon。 一、目标检测流程图绘制的 “量身定制” 体验 Processon 的绘图元素库对目标检测领域极度友好&#xff0c;从基础模块到复杂结构都能精准匹配&#xff1a;   核心组件一键调用&#xff1a;在右…