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

        前言:人们总说指针是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.mzph.cn/news/73542.shtml

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

相关文章

嵌入式Linux驱动开发(LCD屏幕专题)(二)

一、结合APP分析LCD驱动程序 1、open app: open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0 -------------------------------------------------------------- kernel:fb_open // fbmem.cstruct fb_info *info;info get_fb_info(fbidx);if (info->fbop…

VB:顺序查找

VB&#xff1a;顺序查找 Private Sub Command1_Click()Dim i%, m%Dim x(1 To 10) As SingleFor i 1 To 10x(i) Val(InputBox("请输入"))Next im seqSearch(x, 10)If (m 1) ThenPrint "已找到"ElsePrint "未找到"End If End Sub Function se…

Altium 高级技巧 在扁平原理图中创建多个ROOM

Altium 的 ROOM 这个特性非常有用&#xff0c; 使用ROOM在编辑PCB时&#xff0c;可以很方便的整体拖动局部器件和电路图&#xff0c;为模块化电路设计提供便利 常规的设计模式应该采用垂直模式&#xff0c; 由顶层-中间层-底层&#xff0c; 顶层设计整体的框图连接&#xff…

JavaSE【 String 类】(2)(

一、字符串的不可变性 1.String本身不可变 字符串修改 注意&#xff1a;尽量避免直接对 String 类型对象进行修改&#xff0c;因为 String 类是不能修改的&#xff0c;所有的修改都会创建新对象&#xff0c;效率 非常低下。 public static void main(String[] args) {/*** S…

sublime编辑latex 出现参考文献无法编译报错:citation “...” undefined

问题描述 使用sublime编译latex文件时&#xff0c;参考文献按照常规的方式放好&#xff0c;ctrl B 编译的时候&#xff0c;显示找不到参考文献&#xff0c;编译出的pdf文件也没有references&#xff1a; 但是把文件放到overleaf上就可以直接编译出来&#xff0c;说明是本地编…

向量数据库,能让AI再次起飞吗?

9月7-8日&#xff0c;深圳国际会展中心18号馆 来了&#xff0c;来了&#xff0c;腾讯面向产业互联网领域规格最高、规模最大、覆盖最广的年度科技盛会 -——- 腾讯全球数字生态大会。 9 月 7 日&#xff0c;我们将聚焦产业未来发展新趋势&#xff0c;针对云计算、大数据、人工…

滴滴:二季度中国出行营收同比增长57%,6月日均单量超3000万单

9月9日&#xff0c;滴滴在其官网发布2023年第二季度业绩报告&#xff0c;二季度滴滴实现总收入488亿元&#xff0c;同比增长52.6%&#xff1b;归属于滴滴普通股股东的净亏损为3亿元&#xff0c;经调整EBITA亏损1000万元。 分业务来看&#xff0c;二季度滴滴中国出行&#xff0…

fastjson漏洞复现

文章目录 启动环境漏洞复现下载bp插件漏洞扫描dnslog测试是否向外请求资源用工具构造rmi服务器 反弹shell 启动环境 到vulhub目录下 cd vulhub/fastjson/1.2.24-rce安装环境并启动&#xff1a; sudo docker-compose up -d && sudo docker-compose up -d启动成功&…

详解TCP/IP的三次握手和四次挥手

文章目录 前言一、TCP/IP协议的三次握手1.1 三次握手流程 二、TCP/IP的四次挥手2.1 四次挥手流程 三、主要字段3.1、标志位&#xff08;Flags&#xff09;3.2、序号&#xff08;sequence number&#xff09;3.3、确认号&#xff08;acknowledgement number&#xff09; 四、状态…

MySQL——事务

一、事务的开始与结束 一个数据库事务由一条或多条sql语句构成&#xff0c;它们形成一个逻辑的工作单元。这些sql语句要么全部执行成功&#xff0c;要么全部执行失败。 1.1.事物的开始 1.对于DDL&#xff08;create&#xff0c;alter&#xff0c;drop&#xff09;和DCL&…

Java 中如何实现序列化?

什么是序列化&#xff1f;Java 中如何实现序列化&#xff1f; 在 Java 编程中&#xff0c;序列化是一种将对象转换为字节流的过程&#xff0c;可以将对象在网络中传输或者保存到磁盘中。序列化可以将对象的状态保存下来&#xff0c;以便在需要时重新创建对象。Java 中提供了一…

Android 10.0 禁用adb shell input输入功能

1.前言 在10.0的产品开发中,在进行一些定制开发中,对于一些adb shell功能需要通过属性来控制禁止使用input 等输入功能,比如adb shell input keyevent 响应输入事件等,所以就需要 熟悉adb shell input的输入事件流程,然后来禁用adb shell input的输入事件功能,接下来分…

【数据结构--顺序表】移除元素

题目描述&#xff1a; 代码实现&#xff1a; 1、指针实现 int removeElement(int* nums, int numsSize, int val) {int* dst nums, * src nums;int n1 0,n20;while (n1n2 < numsSize){if (*src ! val){*dst *src;dst;src;n1;//表示src走的步数}else{src;n2;//表示src走…

【C++心愿便利店】No.5---构造函数和析构函数

文章目录 前言一、类的6个默认成员函数二、构造函数三、析构函数 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到我的乱七八糟小星球&#x1f31d; &#x1f4cb;专栏&#xff1a;C 心愿便利店 &#x1f511;本章内容&#xff1a;类…

GO远程构建并调试

GO远程调试 之前写C&#xff0c;一直习惯了本地IDERemote CMake/GDB编译调试的模式。 因为6.824课程需要用GO&#xff0c;好像没有特别好的支持。记录一下如何配置调试的。 IDE: Goland 操作系统&#xff1a;Windows 远程服务器&#xff1a;Ubuntu 首先配置SSH,让其可以连接到…

source insight keil 中文乱码

1. 乱码的根本原因就是编码的方式太多了&#xff0c;你用这种编码&#xff0c;他用那种编码&#xff0c;就变成鸡同鸭讲了&#xff0c;对牛弹琴就要用牛语&#xff0c;如果全世界只有一种编码方式&#xff0c;就肯定不会有乱码问题&#xff0c;但这是不可能的。keil 设置编码格…

SpringMVC入门篇

目录 1.SpringMVC工作流程 2.SpringMVC核心组件 2.1 DispatcherServlet 2.2 HandlerMapping 2.3 Handler 2.4 HandlerAdapter 2.5 ViewResolver 2.6 View 3.SpringMVC的入门 3.1 添加相关依赖 3.2 创建Spring-mvc.xml 3.3 配置web.xml 3.4 效果演示 4.静态资源处…

二叉搜索树

如何搜索构建一颗二叉搜索树插入删除 如何搜索 在二叉搜索树里搜索值&#xff1b;搜索非常类似于二分查找 //查找key是否存在public TreeNode search(int key) {TreeNode cur root;while(cur ! null) {if(cur.key key) {return cur;}else if(cur.key > key){cur cur.lef…

【Python】Python运算符/部分函数对应的双下划线魔法方法

先说下Python版本&#xff1a;【Python 3.7.8】 以下用图片表格展示&#xff0c;一是防扒&#xff0c;二是没精力改成md格式。 还有就是内容肯定没有完全包含(而且也很难做到)&#xff0c;像是__reduce__与py自带模块pickle有关(pickle用于对象序列化/反序列化)、sys.getsizeo…

SQLAlchemy 封装的工具类,数据库pgsql(数据库连接池)

1.SQLAlchemy是什么&#xff1f; SQLAlchemy 是 Python 著名的 ORM 工具包。通过 ORM&#xff0c;开发者可以用面向对象的方式来操作数据库&#xff0c;不再需要编写 SQL 语句。 SQLAlchemy 支持多种数据库&#xff0c;除 sqlite 外&#xff0c;其它数据库需要安装第三方驱动。…