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

news2024/11/25 7:27:11

*欢迎来到博主的专栏——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.coloradmin.cn/o/1267820.html

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

相关文章

OSCP系列靶场-Esay保姆级

总结 getwebshell : ftp可匿名登录 → 发现隐藏文件夹 → 发现ssh密钥 → 猜解ssh用户名 → ssh密钥登录 提 权 思 路 : 发现suid权限文件 → cpulimit提权 准备工作 启动VPN 获取攻击机IP → 192.168.45.191 启动靶机 获取目标机器IP → 192.168.179.130 信息收集-端口扫…

基于SpringBoot实现的教务查询系统

一、系统架构 前端&#xff1a;html | js | css | jquery | bootstrap 后端&#xff1a;springboot | springdata-jpa 环境&#xff1a;jdk1.7 | mysql | maven 二、代码及数据库 三、功能介绍 01. 登录页 02. 管理员端-课程管理 03. 管理员端-学生管理 04. 管理员端-教师管理…

又有两大巨头官宣加入鸿蒙, 鸿蒙已成, 华为余承东说得没错

自从华为发布HarmonyOS 4系统后&#xff0c;宣布下一个鸿蒙版本将不再支持安卓应用&#xff0c;并启动鸿蒙原生应用&#xff0c;随后国内巨头纷纷响应&#xff0c;为鸿蒙系统开发原生应用。 如今&#xff0c;又有两大巨头官宣加入鸿蒙&#xff0c;一家是广汽传祺&#xff0c;M…

智慧工地解决方案,Spring Cloud智慧工地项目平台源码

智慧工地一体化信息管理平台源码&#xff0c;微服务架构JavaSpring Cloud UniApp MySql 智慧工地云平台是专为建筑施工领域所打造的一体化信息管理平台。通过大数据、云计算、人工智能、物联网和移动互联网等高科技技术手段&#xff0c;将施工区域各系统数据汇总&#xff0c;建…

Flutter创建TabBar

使用TabBar和TabBarView来创建一个包含"首页"、"分类"和"我的"的TabBar。每个Tab对应一个Tab控件&#xff0c;TabBarView中的每个页面对应一个Widget。 1.Tab使用自定义图标和颜色 一般UI设计的图会带渐变色之类的&#xff0c;应该保持图片的原…

由于找不到steam_api64.dll如何修复?steam_api64.dll丢失多种解决方法

steam_api64.dll文件介绍 steam_api64.dll是Steam平台的一个关键组件&#xff0c;主要用于支持Steam客户端和相关游戏的应用程序。这个文件缺失或损坏会导致Steam及相关游戏无法正常运行。它位于Steam安装目录的bin子文件夹中。 steam_api64.dll丢失的原因 系统误删&#xf…

品味丰富美食,羊大师温暖心灵

品味丰富美食&#xff0c;羊大师温暖心灵 冬季来临&#xff0c;寒冷的天气让人们渴望寻找一种温暖和满足感&#xff0c;这时候美食便成了一种心灵享受。冬季与美食的结合&#xff0c;使得人们在寒冷的冬日也能感受到温暖与欢乐。本文小编羊大师将带大家领略冬季与美食的完美结…

C库函数—sprintf

函数介绍&#xff1a; C 库函数 int sprintf(char *str, const char *format, ...) 发送格式化输出到 str 所指向的字符串。 参数&#xff1a; str -- 这是指向一个字符数组的指针&#xff0c;该数组存储了 C 字符串。format -- 这是字符串&#xff0c;包含了要被写入到字符串 …

网络入门---网络的大致了解

目录标题 网络发展的简单认识协议作用的理解协议的本质什么是协议分层网络通信所面对的问题OSI七层模型TCP/IP模型协议报头的理解局域网通信局域网通信基本原理报头的问题局域网的特点跨网的网络链接如何查看mac地址 网络发展的简单认识 通过之前的学习我们知道计算机是给人提…

九章正式推出『智能驾驶产业数据库』

为了更好地研究产业变化趋势&#xff0c;在定性分析之外增加更多定量分析的内容&#xff0c;从而帮助自动驾驶产业内的朋友们更快速、更精准地把握市场变化&#xff0c;2022年底&#xff0c;九章决定要做智能驾驶产业数据库。 历时将近一年后&#xff0c;从敲定数据库负责人&am…

【古月居《ros入门21讲》学习笔记】10_话题消息的定义与使用

目录 说明&#xff1a; 1. 话题模型 2. 实现过程&#xff08;C&#xff09; 自定义话题消息 Person.msg文件内容 Person.msg文件内容说明 编译配置 在package.xml文件中添加功能包依赖 在CMakeLists.txt中添加编译选项 编译生成语言相关文件 创建发布者代码&#xff…

【Android Jetpack】Hilt 依赖注入框架

文章目录 依赖注入DaggerHiltKoin添加依赖项Hilt常用注解的含义HiltAndroidAppAndroidEntryPointInjectModuleInstallInProvidesEntryPoint Hilt组件生命周期和作用域如何使用 Hilt 进行依赖注入 依赖注入 依赖注入是一种软件设计模式&#xff0c;它允许客户端从外部源获取其依…

高级IO—poll,epoll,reactor

高级IO—poll,epoll,reactor 文章目录 高级IO—poll,epoll,reactorpoll函数poll函数接口poll服务器 epollepoll的系统调用epoll_createepoll_ctlepoll_wait epoll的工作原理epoll的工作方式水平触发边缘触发 epoll服务器 reactor poll函数 poll函数是一个用于多路复用的系统调…

docker-compose Foxmic dt版

Foxmic dt 版前言 实现企业对资产的基本管理,包含对资产的登记、维修、调拨、转移等基本功能的支持,并提供对资产的耗材、库存进行管理,有完善的组织架构,非常适合中小企业的需求系统整体覆盖了基本的资产管理、合同管理、运维服务、运维服务、数据中心设备管理等多个模块。…

白盒测试 接口测试 自动化测试

一、什么是白盒测试 白盒测试是一种测试策略&#xff0c;这种策略允许我们检查程序的内部结构&#xff0c;对程序的逻辑结构进行检查&#xff0c;从中获取测试数据。白盒测试的对象基本是源程序&#xff0c;所以它又称为结构测试或逻辑驱动测试&#xff0c;白盒测试方法一般分为…

JVM执行引擎以及调优

1.JVM内部的优化逻辑 1.1JVM的执行引擎 javac编译器将Person.java源码文件编译成class文件[我们把这里的编译称为前期编译]&#xff0c;交给JVM运行&#xff0c;因为JVM只能认识class字节码文件。同时在不同的操作系统上安装对应版本的JDK&#xff0c;里面包含了各自屏蔽操作…

Linux下Docker 离线安装详细步骤,亲测成功

1.离线原因&#xff1a;公司新创不能使用开元linux&#xff0c;使用了一个变种centOS&#xff0c;致使yum被禁 2.步骤&#xff1a; 2.1 下载docker tar包&#xff0c;下载地址&#xff1a;Index of linux/https://download.docker.com/linux/ 2.2 新建自己的软件目录&am…

数字化转型的核心是数据,还是应用?_光点科技

数字化转型是当今世界各行各业的热门话题。它不仅仅是将传统的业务流程、产品和服务数字化&#xff0c;更是一种全面的业务战略转变。在这个转变过程中&#xff0c;数据和应用都扮演着至关重要的角色。但究竟哪一个是数字化转型的核心&#xff1f;这个问题值得深入探讨。 我们来…

【JavaEE初阶】死锁问题

目录 一、死锁的三种典型场景 1、一个线程&#xff0c;一把锁 2、两个线程&#xff0c;两把锁 3、N个线程&#xff0c;M把锁 死锁&#xff0c;是多线程代码中的一类经典问题。我们知道加锁是能解决线程安全问题的&#xff0c;但是如果加锁的方式不当&#xff0c;就可能产生死…

力扣:185. 部门工资前三高的所有员工(Python3)

题目&#xff1a; 表: Employee ----------------------- | Column Name | Type | ----------------------- | id | int | | name | varchar | | salary | int | | departmentId | int | ----------------------- id 是该表的主键列(具…