一篇文章教会你如何降低代码的冗余度——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙

        前言:人们总说指针是c语言的灵魂,是因为指针的使用技巧是“千姿百态”的,程序员可以通过指针来直接访问内存,这就赋予了它功能的多样性以及更多意想不到的编程技巧与方式,在本篇文章中,笔者就给大家带来指针使用的更多高级技巧

目录

一.指针数组

指针数组的用处

二.数组指针

一般形式

三.函数指针

一般形式

四.函数指针数组 

一般形式

五.回调函数 

什么是回调函数

进阶技巧使用(降低代码冗余度) 

方法一:函数指针数组法

完整代码

方法二:回调函数法

完整代码


一.指针数组

首先我们得明确指针数组究竟是指针还是数组

整形数组     ——  装有整形数据的数组

浮点型数组  ——  装有浮点型数据的数组

字符型数组  ——  装有字符型数据的数组

指针数组      ——  ?

按照上面的逻辑,我们就可以判断出指针数组是数组,只不过里面装的是指针类型的数组 

	int* arr[10] = { NULL };//整形指针数组,里面放的是空指针int* arr1[10]; //整形指针的数组char* arr2[4]; //一级字符指针的数组char** arr3[5];//二级字符指针的数组

指针数组的用处

        指针数组最大的一个用处就是可以用来构造二维数组,试曾设想,我们创造了一列数组,这个数组中每一个元素都是一个地址,而数组也是有地址的,换言之,我们可以将这一列中的的每一个元素放入不同数组的地址,每个元素又代表了一行数组,这样就构造成了一个二维数组。大概图示如下:

        我们创建了一个名为 Arr 的指针数组,其中放了6个地址,分别是6个数组的地址,那我们就相当于完成了一个二维数组的构建 

二.数组指针

同样,我们也得明确数组指针到底是数组还是指针

整形指针     —— 是一个指针,指向一个整形变量

浮点型指针  —— 是一个指针,指向一个浮点型变量

结构体指针  —— 是一个指针,指向结构体变量

数组指针      —— ?

我们推断出数组指针是一个指针,指向一个数组

一般形式

返回类型 * 指针名[数组大小] 

        数组指针可以指向一整个数组,而不在局限与访问数组中一个元素的地址,这样说起来可能有点不明确,那我们写个程序如下,观看一个输出的结果

#include <stdio.h>
int main()
{int arr[10] = { 0 };printf("arr = %p\n", arr);printf("&arr= %p\n", &arr);printf("arr+1 = %p\n", arr + 1);printf("&arr+1= %p\n", &arr + 1);return 0;
}

在研究这个问题之前,我们必须知道一下的基础知识 

在上述代码中,直接使用数组的名字,是在访问数组的首元素的地址

	printf("arr = %p\n", arr);printf("arr+1 = %p\n", arr + 1);

而使用 &数组名,则是在访问整个数组的地址,整个值与首元素的地址是一样的,但是意义完全不一样 

	printf("&arr= %p\n", &arr);printf("&arr+1= %p\n", &arr + 1);

上述代码输出结果如下:

我们可以观察出,使用取值符的时候,访问地址加一,地址值加了40,也证明了上述结论

         而指针数组的使用就可以让我们更好的实现上述对整个数组操作的需求,方便了后续诸如函数指针数组的更高级的使用技巧(后文会介绍到,这里就不再继续赘述)

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p//但是我们一般很少这样写代码return 0;
}

三.函数指针

        诸如上述数组指针的推理判断,函数指针也是指针,只不过指向的是函数,常量,变量,数组我们都已经充分了解到这些数据都是有地址的,那函数有地址吗?我们做以下测试

#include <stdio.h>
void test()
{printf("this is a test\n");
}int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}

        输出结果如下,我们可以看见函数是有地址的,我们也可以取到这个地址,同时函数名也代表了地址

一般形式

返回类型  ( * 指针名) (函数参数类型)

既然函数有地址,那我们就可以使用指针来保存,我们具体实现如下:

void test()
{printf("this is a test\n");
}int main()
{printf("%p\n", test);printf("%p\n", &test);void (*p)() = &test;printf("%p\n", p);return 0;
}

我们试运行一下看看,可以看见第三行的输出确实是使用了指针p对函数进行了访问 

四.函数指针数组 

        对于函数指针数组,主语是数组,所以他的本质就是一个数组,只不过在这个数据结构中,我们需要用到上述铺垫的知识,我们将不同函数的地址放进同一个数组中,可以帮助我们更高效的完成程序。

一般形式

返回类型  ( * 指针名[数组大小] )(函数参数类型)

        在使用函数指针数组的情况下,我们就可以用数组来调用函数了,在部分程序中,可以有非常高的效率,以下笔者仅做简单示例

void F1()
{printf("F1\n");
}void F2()
{printf("F2\n");
}void F3()
{printf("F3\n");
}int main()
{void (*pf[3])() = { F1,F2,F3 };for (int i = 0; i < 3; i++){pf[i]();}return 0;
}

输出结果中显示我们确实成功的使用数组,通过下标的方式对函数进行了调用

五.回调函数 

什么是回调函数

         回调函数就是一个通过函数指针调用的函数 。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

         如下图所示,在程序 x 中放有函数 f,并且函数 f 中又有函数指针,而函数 f 又可以将函数 1,函数 2,函数 3,函数 4的地址作为参数,当我们通过函数 f 中的函数指针进行调用函数 1~4 的时候,我们就叫做回调函数


进阶技巧使用(降低代码冗余度) 

接下里,笔者就通过计算器小程序,来对以上进阶技巧进行演示

        首先,我们按照一般的算法思路进行编程,设计个计算器小程序,那么最基本的加减乘除是肯定要包含的,那我们就分别写出加减乘除的函数,然后再分别调用他们

        诸如下面代码,我们可以发现,这个程序是非常的臃肿的,代码冗余度非常高,有很多重复且效率不高的代码,目前还只是4个功能,要是以后需求变大变多,我们就又要加入 case 语句,程序会变的越来越长

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add           2:sub \n");printf(" 3:mul           4:div \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("输入操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输入操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输入操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输入操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

        那有没有什么办法可以降低代码的冗余度呢?其实答案就在于上述的指针的高级使用技巧,我们可以使用俩种方法,都能达到降低代码冗余度的目的

方法一:函数指针数组法

首先,基本的加减乘除的功能我们保持不变

int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}

        然后我们将这4个函数封装在数组里面,数组中之所以第一个元素放空指针,是为了和菜单中的数字对应起来

int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
//                           0     1    2    3    4
void menu()
{printf("****************************\n");printf("***  1. add      2. sub  ***\n");printf("***  3. mul      4. div  ***\n");printf("***  0. exit             ***\n");printf("****************************\n");
}

         在运算部分,我们使用函数指针数组,将输入的数字作为数组的下标来访问对应的函数,并且将值赋给 ret 

printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("ret = %d\n", ret);

完整代码

void menu()
{printf("****************************\n");printf("***  1. add      2. sub  ***\n");printf("***  3. mul      4. div  ***\n");printf("***  0. exit             ***\n");printf("****************************\n");
}int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);//函数指针数组 - 转移表int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};//                          0     1     2   3    4if (0 == input){printf("退出计算器\n");}else if (input >= 1 && input <= 4){printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else{printf("选择错误,重新选择!\n");}} while (input);return 0;
}

在完整代码中,我们明显会发现,尤其是在主函数中,省去了很多冗余的代码,看起来精简了很多

方法二:回调函数法

我们可以使用下面这种结构来完成设计

我们建立一个函数,将加减乘除的函数地址作为参数传进来调用

void calc(int (*pf)(int,int))
{int x = 0;int y = 0;int ret = 0;printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}

完整代码

void menu()
{printf("****************************\n");printf("***  1. add      2. sub  ***\n");printf("***  3. mul      4. div  ***\n");printf("***  0. exit             ***\n");printf("****************************\n");
}int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}void calc(int (*pf)(int,int))
{int x = 0;int y = 0;int ret = 0;printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}int main()
{int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误, 重新选择\n");break;}} while (input);return 0;
}

我们可以看出来,函数冗余度还是降低了不少的
 

本次分享就到此为止了,如有错误,欢迎积极指出,感谢您的支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/126102.html

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

相关文章

基于Vgg-Unet模型自动驾驶场景检测

1.VGG VGG全称是Visual Geometry Group属于牛津大学科学工程系&#xff0c;其发布了一些列以VGG开头的卷积网络模型&#xff0c;可以应用在人脸识别、图像分类等方面,VGG的输入被设置为大小为224x244的RGB图像。为训练集图像上的所有图像计算平均RGB值&#xff0c;然后将该图像…

指针-矩阵变换

任务描述 给定一个矩阵&#xff0c;请编程将其按照以下约定的操作方式变换后输出。 相关知识 参考之前的关卡。 编程要求 根据提示&#xff0c;在右侧编辑器的Begin-End区域内补充代码。 测试说明 输入&#xff1a;第一行三个正整数 n&#xff0c;m 和 q 分别表示矩阵 A…

深入探究数据结构与算法:构建强大编程基础

文章目录 1. 为什么学习数据结构与算法&#xff1f;1.1 提高编程技能1.2 解决复杂问题1.3 面试准备1.4 提高代码效率 2. 学习资源2.1 经典教材2.2 在线学习平台2.3 学习编程社区 3. 数据结构与算法的实际应用3.1 排序算法3.2 图算法3.3 字符串匹配算法 4. 结论 &#x1f389;欢…

企业架构LNMP学习笔记26

通过Keepalived可以实现主服务器整机不可用&#xff0c;实现VIP的切换。保证用户可以通过VIP进行访问服务。但是实际上&#xff0c;往往不是服务器整机不可用&#xff0c;只是对应的服务或者软件不可用。 比如说&#xff0c;nginx提供的web的服务&#xff0c;nginx进程关闭。 …

3D印刷电路板在线渲染查看工具

从概念上讲&#xff0c;这是有道理的&#xff0c;因为PCB印制电路板上的走线从一个连接到下一个连接的路线基本上是平面的。 然而&#xff0c;我们生活在一个 3 维世界中&#xff0c;能够以这种方式可视化电路以及相应的组件&#xff0c;对于设计过程很有帮助。本文将介绍KiCad…

Unity设置TextMeshPro文本超出范围显示...

TextMtshPro文本超出范围&#xff0c;展示省略。选择Overflow为Ellipsis。

Redis缓存预热、缓存雪崩、缓存击穿、缓存穿透

文章目录 Redis缓存预热、缓存雪崩、缓存击穿、缓存穿透一、缓存预热1、问题排查2、解决方案&#xff08;1&#xff09;准备工作&#xff08;2&#xff09;实施&#xff08;3&#xff09;总结 二、缓存雪崩1、解决方案 三、缓存击穿1、解决方案&#xff08;1&#xff09;互斥锁…

2023数学建模国赛B题完整论文来啦!(含一二问求解代码及三四问仿真模拟代码)

大家好呀&#xff0c;从昨天发布赛题一直到现在&#xff0c;总算完成了全国大学生数学建模竞赛B题完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 说实话团队通宵一直到现在做…

Matlab 如何选择采样频率和信号长度

Matlab 如何选择采样频率和信号长度 1、概述 在实际信号分析中经常会遇到要分辨出频率间隔为 的两个分量&#xff0c;在这种情形中如何选择采样频率和信号的长度呢&#xff1f; 2、案例分析 设有一个信号由三个正弦信号组成&#xff0c;其频率分别为 &#xff0c;即&#xf…

合宙Air724UG LuatOS-Air LVGL API控件-图片 (Image)

图片 (Image) 图片IMG是用于显示图像的基本对象类型&#xff0c;图像来源可以是文件&#xff0c;或者定义的符号。 示例代码 -- 创建图片控件 img lvgl.img_create(lvgl.scr_act(), nil) -- 设置图片显示的图像 lvgl.img_set_src(img, "/lua/luatos.png") -- 图片…

2023国赛数学建模C题思路代码 - 蔬菜类商品的自动定价与补货决策

# 1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…

中科驭数携DPU系列创新产品亮相2023服贸会

9月2日至6日&#xff0c;由商务部、北京市政府主办的2023年中国国际服务贸易交易会&#xff08;简称“服贸会”&#xff09;在北京举行。在电信、计算机和信息服务专题展馆&#xff0c;中科驭数等一批国家专精特新“小巨人”携最新数据处理器DPU亮相&#xff0c;展示了多项创新…

【LeetCode75】第五十题 无限集中的最小数字

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 这是我们在LeetCode75里遇到的第二道设计类题目&#xff0c;难度比上一次的设计题目要难上一些。 题目假设我们拥有一个从1开始的无限集…

跨境电商产业链,服务商的“霸道”你见识过吗?(测评补单)

跨境电商行业的服务商众多&#xff0c;涉及到从前期培训和店铺注册准备到中期选品软件、营销服务、流量投放和支付等多个环节。然而&#xff0c;行业乱象也日益严重&#xff0c;出现了一些不良现象&#xff0c;如恶意竞争、高价要求、割韭菜等。 卖家在选择服务商时应谨慎&…

Matlab图像处理-最大类间方差阈值选择法(Otsu)

基本思想 最大类间方差阈值选择法又称为Otsu 算法&#xff0c;该算法是在灰度直方图的基础上用最小二乘法原理推导出来的&#xff0c;具有统计意义上的最佳分割阈值。它的基本原理是以最佳阈值将图像的灰度直方图分割成两部分&#xff0c;使两部分之间的方差取得最大值&#x…

win10/11安裝時 OOBE強制跳過登入Microsoft賬戶

Windows11官方正式版ISO&#xff0c;目前版本号为22621.963&#xff0c;微软维持每个月发布一次ISO的节奏。每周还会有一些补丁在线推送&#xff0c;目前更新到了22621.1105。同时&#xff0c;还有Beta和Dev通道推送预览版。Dev通道的更为激进还不稳定&#xff0c;目前版本号为…

安装SAPGUI 8.0

SAP_GUI_for_Windows_8.00_Comp.64\PRES1\GUI\Windows\Win64

苹果计划2024年AirPods引入新接口,后续升级体温测量、听力测试

据彭博社马克・古尔曼&#xff08;Mark Gurman&#xff09;报道&#xff0c;苹果公司计划在最早2024年为其旗舰无线耳机AirPods和AirPods Max引入USB-C端口&#xff0c;并试图将其所有基于Lightning接口的配件改用USB-C接口。 据古尔曼表示&#xff0c;AirPods Pro将率先改用新…

Docker部署Springboot项目

Docker部署Springboot项目 在学习的若依的过程中&#xff0c;想尝试学习使用Docker实现SpringBoot项目的部署&#xff0c;于是实践如下&#xff1a; 项目打包完成后&#xff0c;放到服务器的目录结构如下&#xff1a; cd / mkdir ruoyidockerfile文件内容 笔者这里是配置读取…

微信小程序的开发---tabBar的介绍

目录 一、tabBar的介绍 二、tabBar的6个组成部分 三、tabBar节点的配置项 四、tab项的配置选项 五、tabBar的使用 一、tabBar的介绍 tabBar是移动端应用常见的页面效果&#xff0c;用于实现多页面的快速切换。小程序中通常将其分为&#xff1a; &#xff08;1&#xff09;…