C语言进阶指南(15)(函数指针的创建与使用)

*欢迎来到博主的专栏——C语言进阶指南
博主id

文章目录

    • 函数指针
    • 函数指针的应用——回调函数
    • 函数指针数组

函数指针

函数也有地址(函数在调用的时候会占用内存空间,所以函数是有地址的),因此我们也可以用一个指针指向函数
1
函数指针的声明

type (*funcpointer)(element type……)

type是指向的函数的返回值,*与指针进行结合,声明这个标识符是指针而不是函数,element type是指针指向的函数的原型参数

函数也有地址,因此我们也可以用一个指针指向函数,数组名是数组首元素的地址,类似的,函数名也代表着函数的地址,因此函数的地址可以用函数名或者&函数名来代表

int add(int x, int y)
{
	return x+y;
}
int main()
{
	int(*pf)(int, int)=add;
	return 0;
}

pf是一个指向函数add的函数指针。通过对指针进行解引用,可以对函数进行调用

(*pf)(10,20);

函数指针的应用——回调函数

如果函数指针的作用仅仅是为了调用某个函数的话就有点脱裤子放屁的感觉了,事实上函数指针可以指向任何一个符合类型的函数。比如上述的pf可以指向任何一个返回类型为int,函数参数为(int,int)的函数。

由于这个特性,函数指针常常用于回调函数当中。

回调函数是函数参数有函数指针类型,且在程序的执行过程会调用传递的函数指针,根据传递的函数指针的不同,实现不同的效果的函数

回调函数的功能比一般的函数更加强大,这是因为回调函数能够用函数指针来完成自定义部分的内容(相当于是回调函数将部分的功能交由程序员自己定义)

以库函数qsort为例,qsort是一个在<stdlib.h>的库函数,他的功能是实现任何数据类型的数据的快速排序。

博主在先前文章中的自定义的快速排序和冒泡排序的函数都有一个不足之处,那就是只能被设定为排序固定的一中数据类型的数据

void bubble_sort(int* arr, int sz);

(用于排序int类型的冒泡排序)

void quick_sort(int arr[], int low,int high);

(用于排序int类型的快速排序)
如果想要程序排序float类型的数据,就需要在函数原型参数上修改数据类型

void bubble_sort(float* arr, int sz);
void quick_sort(int arr[], int low,int high);

而库函数qsort能够排序任何的数据类型,这就是回调函数功能更加强大的原因,首先来看看qsort的函数原型

在这里插入图片描述
函数原型参数base,base是指向待排序的数据的起始地址的void*指针。

为什么使用void*呢?因为qsort被设计成可以接收任何数据类型的函数,所以待排序的数据可以是char类型,int类型,float类型,它们对应的指针类型是char*,int*,float*。如果设置char*作为base的类型,那么它无法对其他指针类型进行操作
其他指针类型也是同理,只有void*被允许接收任意类型的指针参数

num是size_t类型的数据,是待排序的数据的个数
wideth是size_t类型的数据,是单个待排序的数据的字节数
compare是一个函数指针,这个函数指针指向一个返回值为int类型,参数类型为(void*,void*)的函数。

qsort函数的原理如下,第一个参数上传需要排序的数据的首元素地址,第二个参数上传数据的个数,第三个参数上传单个数据的字节。第四个函数传递的是自定义函数的函数指针,也是qosrt当中最主要的部分。

以排序int类型的数据为例,我们要定义compare函数是能够比较int类型数据的函数,根据MSDN中关于qsort的使用指南所说。
在这里插入图片描述

compare的第一个元素大于第二个元素。返回>0的值。
第一个元素等于第二个元素,返回0
第一个元素小于第二个元素,返回<0的值。

再根据函数指针的类型必须符合qsort中对于compare的函数指针的类型的要求是int*(void*,void*)。可以得出自定义compare函数的函数原型是

int compare(void*elem1,void*elem2);

为了使conpare函数能够实现比较int类型的数据。需要将elem1和elem2的数据类型改成int*。

这是因为void*类型的指针虽然可以接收任何类型的指针,但是不能对void*类型的指针进行操作,比如解引用,指针的加减算术运算等,解决方法就是将void*类型的指针根据需要,强制类型转换成其他类型的指针,再对指针进行操作

那么比较int的campare函数可以写成

int int_compare(void* elem1, void* elem2)
{
	return *(int*)elem1 - *(int*)elem2;
}

这样就满足了第一个元素大与第二个元素返回>0的值的效果。

如果想要一个比较float类型的compare函数可以写成

int float_compare(void* elem1, void* elem2)
{
	if (*(float*)elem1 > *(float*)elem2)
		return 1;
	else if (*(float*)elem1 == *(float*)elem2)
		return 0;
	else return -1;
}

这里不直接用elem1-elem2作为函数返回值。是因为conpare被qsort指定为一个返回类型为int类型的函数,两个浮点数相减的结果被转换成int类型可能会导致数据丢失,造成比较结果出现误差

前面提到了,函数名本身可以作为函数指针使用,那么使用qsort函数的例子为

int int_compare(void* elem1, void* elem2)
{
	return *(int*)elem1 - *(int*)elem2;
}
int main()
{
	int arr[10] = { 22,55,33,77,44,11,99,88,66,10 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), //qsort调用
	sizeof(arr[0]), int_compare);//qosrt调用
	return 0;
}

运行发现,arr被排序成升序了。
回调函数qsort的原理如下:

(1)qsort函数中只定义了排序部分,并没有定义比较元素的部分,比较元素的函数需要使用者提供,qsort根据使用者定义的比较函数来进行比较元素大小(通过函数指针来调用,因此称为回调函数)
(2)qsort中需要传入元素的个数和大小,是因为qsort接收的指针是void的,而void的指针不能进行加减算术运算,需要使用者提示元素的大小和个数来使指针定位元素的位置。

函数指针数组

函数指针数组是一个数组,数组中的元素都是函数指针
函数指针数组的声明形式如下:

type *iden[](element type)

和指针数组的声明一致,先让标识符与[]结合形成数组,再声明数组元素的类型(type*(element type))

函数指针数组的主要作用是将一些类型一致的函数的指针集合在一起,可以让程序变得简洁。

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个函数,如果我们想要根据不同的输入来调用不同的函数的话,可以用switch语句。

int main()
{
	int x = 0, y = 0;
	int input = 0;
	while (1)
	{
		printf("请选择:");
		scanf("%d", &input);
		if (input > 0 && input <= 4)
		{
			printf("请输入计算的两个数:");
			scanf("%d%d", &x, &y);
		}
		switch (input)
		{
		case 1:
			add(x, y);
			break;
		case 2:
			sub(x, y);
			break;
		case 3:
			mul(x, y);
			break;
		case 4:
			div(x, y);
			break;
		case 0:
			return 0;
		default:
		    printf("输入错误,请重试\n");
		}
	}
}

如果创建一个函数指针数组

int (*pf[5])(int,int)={NULL,add,sub,div,mul};
int main()
{
	int x=0, y=0,input=0;
	int (*pf[5])(int,int) = {NULL,add,sub,mul,div};
	do
	{
		scanf("%d", &input);
		if (input > 0 && input <= 4)
		{
			printf("请输入两个数");
			scanf("%d%d", &x, &y);
			pf[input](x, y);//使用函数指针数组来调用函数

		}
		else if (!input)
			return 0;
		else
			printf("输入错误,请重试\n");
	} while (input);
}

从这么多的语句。
在这里插入图片描述
变成了
在这里插入图片描述
如果需要调用更多的函数的话,函数指针数组的作用会更加明显。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

利用数据库的表,生成word文档的表结构注释说明

文章目录 1.场景说明2.解决办法3.生成文档3.1.实现思路3.2.引入Apache POI依赖3.3.获取表及表字段说明Mapper3.4.POI创建文档表格&#xff0c;并填充数据3.5.完整的接口下载代码3.6.效果展示 1.场景说明 在项目中表已经建立好了&#xff0c;但是现在想对外提供一个表的字段的描…

Kong处理web服务跨域

前言 好久没写文章了&#xff0c;大概有半年多了&#xff0c;这半年故事太多&#xff0c;本文写不下&#xff0c;就写写文章标题问题&#xff01; 问题描述 关于跨域的本质问题我这里不过多介绍&#xff0c;详细请看历史文章 跨域产生的原因以及常见的解决方案。 我这边是新…

连锁零售企业如何提高异地组网的稳定性?

随着数字化时代的到来&#xff0c;连锁零售企业面临着日益复杂和多样化的网络挑战。连锁零售企业是在不同地理位置拥有分支机构和零售店&#xff0c;可能同城或异地&#xff0c;需要确保各个地点之间的网络连接稳定和可靠。但由于不同地区的网络基础设施差异、网络延迟和带宽限…

【 C 语言经典100例】C 练习实例10

题目&#xff1a;打印楼梯&#xff0c;同时在楼梯上方打印两个笑脸。 程序分析&#xff1a;用 ASCII 1 来输出笑脸&#xff1b;用i控制行&#xff0c;j来控制列&#xff0c;j根据i的变化来控制输出黑方格的个数。 #include<stdio.h>int main() {int i,j;printf("\…

浙江省跨境电商产业联盟大会成功举办:开启探索数字贸易丝路电商之旅

11月26日上午&#xff0c;浙江省跨境电商产业联盟大会在杭州国际博览中心“丝路电商馆”成功举办。浙江省商务厅副厅长张钱江出席活动并致辞。39个省级跨境电商产业园代表&#xff0c;知名跨境电商平台、卖家、服务商企业代表、高校智库专家等参加本次活动&#xff0c;吸引大批…

基于PaddleOCR银行卡识别实现(三)

前言 基于PaddleOCR银行卡识别实现&#xff08;一&#xff09; 基于PaddleOCR银行卡识别实现&#xff08;二&#xff09; 前两篇文章讲了检测模型和识别模型的实现&#xff0c;这一篇文章姗姗来迟&#xff0c;将讲解下两个模型的串联应用和PaddleOCR的源码精简&#xff0c;下面…

全局异常处理类

全局异常处理类 创建步骤 定义一个自己的全局错误处理类GlobalExceptionHandler创建一个ExceptionHandler类&#xff0c;主要是用ControllerAdvice和 ExceptionHandler处理错误信息 以下说明各个注解的作用&#xff1a; ControllerAdvice(annotations {RestController.class…

【爬虫实战】最新python豆瓣热榜Top250

一.最终效果 豆瓣是大多数新手练习爬虫的 二.数据定位过程 对于一个目标网站&#xff0c;该如何快速判定页面上的数据来源&#xff1f;首先你需要简单web调试能力&#xff0c;对大多数开发者来说都chrome浏览器应该是不二选择&#xff0c;当然我选中的也是。F12打开调试面板&…

在PyCharm中配置PyQt5环境

在PyCharm中配置PyQt5环境 文章目录 1.安装第三方库2.PyQt5设计器3.PyUIC转换工具 &#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1f339;꧔ꦿ&#x1…

《On Java》

文章目录 一、Java概述1.JVM、JRE和JDK的关系2.什么是Java程序的主类3.Java和C的区别 三、面向对象3.1 面向对象三大特性封装继承多态 3.2 基本类型默认值3.3 和 equals 四、操作符4.1 比特和字节4.2 位操作&^ 4.3 运算符Math.round()loat f3.4;是否正确 4.4 实战小于n的最…

外包干了5个月,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

【刷题】DFS

DFS 递归&#xff1a; 1.判断是否失败终止 2.判断是否成功终止&#xff0c;如果成功的&#xff0c;记录一个成果 3.遍历各种选择&#xff0c;在这部分可以进行剪枝 4.在每种情况下进行DFS&#xff0c;并进行回退。 199. 二叉树的右视图 给定一个二叉树的 根节点 root&#x…

DDoS高防IP到底是什么?

DDoS高防IP是提供一个带防御的IP&#xff0c;主要是针对网络中的DDoS攻击进行保护&#xff0c;是针对互联网服务器遭受大流量的DDoS攻击后&#xff0c;导致服务不可用的情况下&#xff0c;用户可以通过配置高防IP&#xff0c;将攻击流量引流到高防IP上&#xff0c;从而确保源站…

【浅尝C++】运算符重载(含类的3大默认成员函数:赋值、取地址、const对象取地址运算符重载)

&#x1f388;归属专栏&#xff1a;浅尝C &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;记录一句&#xff1a;在Linux与C中来回横跳&#xff0c;哪个学累了&#xff0c;就去学另外一个~~ 文章前言&#xff1a;本篇文章简要介绍C的运算符重载&#xff0c;同时接着…

如何用CHAT写“科技探索者”视频号运营方案

问CHAT&#xff1a;生成一篇“科技探索者”视频号运营方案&#xff0c;要求内容&#xff1a; &#xff08;1&#xff09;视频号的定位、面向的人群、主要发布哪方面的内容 &#xff08;2&#xff09;视频号的内容设计&#xff08;用什么样的方式来体现、最好有内容创意&#xf…

Java大型智慧工地APP云平台源码带AI智能识别功能

智慧工地为建筑全生命周期赋能&#xff0c;用创新的可视化与智能化方法&#xff0c;降低成本&#xff0c;创造价值。 一、智慧工地APP概述 智慧工地”立足于互联网&#xff0c;采用云计算&#xff0c;大数据和物联网等技术手段&#xff0c;针对当前建筑行业的特点&#xff0c;…

Spark local模式的安装部署

安装与配置Spark开发环境。 相关知识 Apache Spark是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab(加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行框架&#xff0c;Spark拥有Hadoop MapReduce所具有的优点&#xff1b;但…

Linux 进程(二)

1.当前工作目录 Linux 下使用 ls /proc 查看程序中的进程&#xff0c;其中这些蓝色的数字代表的就是进程。 其中cwd(current working directory)就是当前工作目录&#xff0c;那么为什么cwd 和 exe 是在同一级目录下呢因为 进程需要依赖可执行程序&#xff0c;可执行程序需要依…

Reactor模式

Reactor模式有点类似事件驱动模式。在事件驱动模式中&#xff0c;当有事件触发时&#xff0c;事件源会将事件分发到Handler&#xff08;处理器&#xff09;&#xff0c;由Handler负责事件处理。Reactor模式中的反应器角色类似于事件驱动 模式中的事件分发器&#xff08;Dispatc…

操作系统原理-作业一-进程同步

1.某理发店可同时供 10 人理发&#xff0c;当店中顾客少于 10 人时&#xff0c;则店外的顾客可立即进入&#xff0c;否则需在外面等待。请定义所需信号量并写出信号量各种取值( 大于 0 、等于 0 、小于0)分别代表的含义&#xff0c;并用 P 、 V 操作编程实现完成多个顾…