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

news2024/11/30 0:34:12

        前言:人们总说指针是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    4
		if (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.coloradmin.cn/o/991822.html

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

相关文章

同时安装python2和3解决方案

我先安装python3后&#xff0c;按照网上步骤&#xff0c;继续安装好python2&#xff0c;直接运行python -v只能显示python2&#xff0c;运行python3找不到此命令&#xff0c;通过https://blog.csdn.net/qq_64409509/article/details/131514944这篇文章找到了解决方案&#xff0…

MindFusion.Diagramming for ASP.NET MVC 4.2 Crack

ASP.NET MVC 4.2 的 MindFusion.Diagramming 添加对多个图表页面和选项卡式图表视图的支持。 2023 年 9 月 8 日 - 16:57新版本 特征 多个图表页面-添加了DiagramDocument 类&#xff0c;它表示图表页面或工作表的集合。 可以将新页面添加到文档中&#xff0c;并且可以删除或重…

功率放大器主要用作什么用途

功率放大器是一种用于增大输入信号功率的器件&#xff0c;有着广泛的应用。其主要作用是提供足够的功率输出&#xff0c;以满足各种系统和设备对信号放大的需要。下面西安安泰电子详细介绍功率放大器的主要应用领域。 音频放大&#xff1a;功率放大器在音频领域有着广泛的应用。…

9.8day58 单调栈

739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; 知识点&#xff1a;1.建栈 2.如果后面要加入的数小于栈顶元素就把数组的下标压进栈里 3.反之 就让该数于栈顶元素进行比较 如果该数大于栈顶元素&#xff08;while&#xff09; 就把栈顶元素下表对应的arr数组的值进行…

创建n维空间每个维度(轴)的刻度值numpy.ogrid[]

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 创建n维空间每个维度&#xff08;轴&#xff09;的刻度值 numpy.ogrid[] [太阳]选择题 下列代码y[1].shape输出的结果是&#xff1f; import numpy as np x np.ogrid[1:5:1] print("【显…

沿着管道或者巷道流动的烟雾或者液体的Shader参考

这个Shader正确其作用的前提是管道或者巷道和周围的物体要拉开一定距离&#xff0c;如果和周围的物体紧挨着是不行的。 首先看液体&#xff1a; 使用的贴图参考&#xff1a; 然后是气体&#xff1a; 使用的贴图参考&#xff1a;

如何将WPS设置为默认的办公软件

很多小伙伴的电脑中有好几种办公软件&#xff0c;每次打开文档表格都要进行选择&#xff0c;有小伙伴想要将WPS设置成默认的办公软件该怎么操作呢&#xff0c;下面小编就给大家详细介绍一下将WPS设置为默认的办公软件的方法&#xff0c;有需要的小伙伴快来和小编一起看一看吧。…

Tableau自学四部曲_Part3:基础图表制作

文章目录 一、 对比分析&#xff1a;比大小1. 柱状图2. 条形图3. 热力图&#xff08;突出显示表&#xff09;4. 气泡图5. 词云 二、变化分析&#xff1a;看趋势1. 折线图2. 基于连续时间序列的折线图预测接下来的数据走向3. 面积图 三、构成分析&#xff1a;看占比1. 饼图2. 【…

Unity 之 定时调用函数的方法

文章目录 1. **Invoke方法&#xff1a;**2. **InvokeRepeating方法&#xff1a;**3. **协程&#xff08;Coroutines&#xff09;&#xff1a;**4. **Time.deltaTime&#xff1a;**5. **使用Invoke延迟执行方法并取消&#xff1a;** 在Unity中&#xff0c;你可以使用多种方式来实…

基于Java+SpringBoot+Vue前后端分离餐饮管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

c++ 学习之 静态成员变量和静态成员函数

文章目录 前言正文静态成员变量初始化操作如何理解共享一份数据访问权限 静态成员函数访问方式静态成员函数只能访问静态成员变量访问权限 前言 静态成员分为 1&#xff09;静态成员变量 所有对象共享一份数据在编译阶段分配空间类内声明&#xff0c;类外初始化 2&#xff09…

C++编译底层

C/C编译底层 C内存管理LINUX进程区分段及存储数据GCC编译流程动态库静态库区别及LINUX加载库extern C的结果和CPP编译的区别重载的底层原理编译性语言和解释性语言的本质区别和优缺点 C内存管理 栈 存储函数的返回地址、参数、局部变量、返回值&#xff0c;从高地址向低地址增长…

Linux ifconfig只显示 lo 网卡,没有ens网卡解决方案

项目场景&#xff1a; 虚拟机中linux无网络问题 问题描述 之前在调试linux的时候&#xff0c;由于一些不太清楚的误操作&#xff0c;导致ubuntu linux出现无网络问题&#xff0c;现象如下 ifconfig 只显示了 lo 网卡 lo 网卡&#xff1a;它是本地环回接口。 这意味着您的虚…

Ubuntu系统下安装常用软件

安装MySQL 1、卸载清理残余 dpkg --list|grep mysqlsudo apt-get remove mysql-common2、升级apt sudo apt-get update3、安装MySQL sudo apt-get install mysql-server4、启动和关闭mysql的命令如下 #启动: sudo service mysql start#重启: sudo service mysql restart #…

Linux之查看so/bin依赖(三十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

亿发多门店商品管理信息化解决方案,可以帮助企业解决那些问题

在现代商业世界中&#xff0c;高效的管理是企业成功的关键之一。而进销存系统作为企业信息化管理的重要组成部分&#xff0c;对于加强企业的运营效率和竞争力具有至关重要的作用。许多企业正寻求专业的商品管理信息化 解决方案,支持其业务运营。亿发进销存管理系统,一个平台统一…

如何制作一个卡刷扩容补丁。用于扩容系统等分区 刷写第三方需要扩容才可以刷写的系统或者GSI GSI系统bug修复【二】

前面分享了一期手动扩容系统分区的博文&#xff0c;今天来系统解析下扩容卡刷补丁的制作。因为机型字库不同。挂载的分区方式也有所不同。操作前首先确定自己机型的字库芯片。以一款emmc芯片的扩容补丁为例来解析其操作原理&#xff1a; 安卓系列机型--软扩容“system分区扩容…

003微信小程序云开发API数据库-新增集合-删除集合-获取集合信息

文章目录 1.微信小程序云开发API数据库-新增集合案例代码 2.微信小程序云开发API数据库-删除集合案例代码 3.微信小程序云开发API数据库-获取集合信息案例代码 1.微信小程序云开发API数据库-新增集合 微信小程序云开发API数据库是一个方便快捷的数据库解决方案&#xff0c;可以…

快捷支付是什么?怎么申请支付接口?

快捷支付是什么&#xff1f;怎么申请支付接口&#xff1f; 快捷支付&#xff0c;又称电子支付或第三方支付&#xff0c;在行业中得到了广泛的应用。用户只需通过银行完成交易。方便快捷意味着银行可以在任何条件下支持用户之间的转账、支付和其他即时结算服务。快捷支付意味着…

阿里云APP备案操作流程_新手看过来

阿里云APP备案流程分为6步&#xff0c;APP备案成功后应用可以上架&#xff0c;登录阿里云账号填写APP信息&#xff0c;等待阿里云初审&#xff0c;初审通过后进行工信部短信核验&#xff0c;管局审核通过后APP即可备案成功&#xff0c;最后移动APP应用可以分发平台上架&#xf…