【C语言】进阶指针(二)—>函数指针与回调函数

news2024/9/20 14:31:43

目录

前言:

一、函数指针

代码1分析:

代码2分析:

二、函数指针数组

三、指向函数指针数组的指针

四、回调函数(模拟实现库函数qsort)

(一)void*类型指针的作用

(二)模拟实现库函数qsort()


前言:

 今天我们继续学习指针的进阶,本篇内容主要围绕函数指针与回调函数进行,并且会模拟实现库函数qsort来深入理解回调函数。

你可以解释下面的代码么?

//代码1

(*( void(*)() ) 0) ();

//代码2

void(* signal(int , void(*)(int) )) (int);

一、函数指针

函数指针是指针,本质是指针,比如:

void (*pfun1)();
void* pfun2();

 我们知道函数指针本质是指针,因此pfun应首先与*结合变为指针

明显pfun1为函数指针,其指向的函数没有参数,返回值类型为void,而pfun2是返回值类型为void*的函数。

 了解了函数指针的概念我们看文章开头的两端复杂代码。

//代码1
(*(void(*)()) 0) ();
//代码2
void(*signal(int, void(*)(int))) (int);

代码1分析:

1、将0强制类型转换为void(*)()类型的函数指针。

2、调用0地址处的函数。

代码2分析:

signal是一个参数类型为整型int,函数指针类型void(*)(int),返回类型为函数指针类型void(*)(int)的函数。

 但是这样的写法太过复杂,我们可以利用typedef重定义。如下:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

二、函数指针数组

函数指针数组是数组,本质是数组。

因为[]的优先级高于*,那么只需要让变量(数组名)先与[]结合变成数组,其数组内容为函数指针即可。

int (*parr[10])();//数组中元素的数据类型为int(*)()

下面给出一个函数指针应用实例:

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;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    while (input)
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);//此处*可省略
        }
        else
            printf("输入有误\n");
        printf("ret = %d\n", ret);
    }
    return 0;
}

在应用时,利用函数指针运行函数时,函数指针前的解引用操作符*可省略,此处主要是方便读者理解,对于编译器而言,此处的解引用符号无意义。

三、指向函数指针数组的指针

指向函数指针数组的指针是指针,本质是指针,它指向一个数组,数组中的元素都是函数指针。

 为了更好的区分,我把函数指针,函数指针数组和指向函数指针数组的指针在这里统一在进行区分讲解。

int (*pf)(int, int);             //函数指针
int (*pfArr[])(int, int);        //函数指针数组
int (*(*p)[])(int, int) = &pfArr;//指向函数指针数组的指针

 其实我们只需要知道我们要定义的是指针还是数组这一基本原则就可以很好的区分,搞清楚变量是先与*结合还是先与[]结合

对于函数指针来说,pf与*结合就决定了他是指针,指针类型就是去掉pf,即int(*)(int ,int),指针指向的就去掉*p,是int (int,int)一个参数为int,int,返回值为int的函数。

对于函数指针数组来说,pfArr先与[]结合就决定了他是数组,数组中元素的数据类型为去掉pfArr[],即int (*)(int, int)。

对于指向函数指针数组的指针来说,p先与*结合就决定了他是指针,指针类型就是去掉p,即int (*(*)[])(int, int),指针指向的就去掉*p,是int (*[])(int, int)函数指针数组。

四、回调函数(模拟实现库函数qsort)

回调函数就是一个通过函数指针调用的函数。

通俗的讲回调函数是将自己的地址作为参数传递给另一个函数,由这个函数调用使用的,回调函数不由该函数实现方直接调用,而是间接的需要另外一个函数调用使用。

接下来我会通过对库函数qsort的模拟实现讲解回调函数。

(一)void*类型指针的作用

在模拟实现库函数qsort之前,我们先来讲解以下void*的作用。

void*类型的指针不能直接进行解引用操作,也不能直接进行指针运算。

但是void*类型的指针可以接收任意类型的地址,所以它广泛应用于函数参数。

比如:

int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;//err
	void* pd = &a;//ok
	pd++;         //err
	*pd;          //err
}

(二)模拟实现库函数qsort()

模拟实现qsort的目的是为了更好的理解回调函数,所以这里模拟的qsort排序方法我们使用冒泡排序。

首先要实现qsort我们需要了解qsort的返回值,参数以及功能等信息,我们进入cplusplus.com - The C++ Resources Network查询qsort函数。

 根据查询的内容,我们了解到,该函数有四个参数,分别为base、num、size、compar,大致的意思我已经标在图中,这里我着重说一下compar,以及回调函数为何要利用qsort来讲解。

qsort的优点在于,它可以排序任意数据类型的数据,可以是整型也可以是字符型还可以是结构体等等,而它如此灵活的关键就在与它利用的回调函数,它将排序依据交给使用者,利用函数指针compar传参,并利用其余三个参数的灵活配合就能在不知道待排序元素数据类型的情况下实现排序功能。

完整代码如下: 

int int_cmp(const void* p1, const void* p2)//排序依据
{
    return (*(int*)p1 - *(int*)p2);
}

void _swap(void* p1, void* p2, int size)//交换元素
{
    int i = 0;
    for (i = 0; i < size; i++)
    {
        char tmp = *((char*)p1 + i);
        *((char*)p1 + i) = *((char*)p2 + i);
        *((char*)p2 + i) = tmp;
    }
}

void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//冒泡函数主体
{
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++)
    {
        for (j = 0; j < count - i - 1; j++)
        {
            if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
            {
                _swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
            }
        }
    }
}

int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

在这里再理解一下回调函数的概念:

回调函数就是一个通过函数指针调用的函数。

通俗的讲回调函数是将自己的地址作为参数传递给另一个函数,由这个函数调用使用的,回调函数不由该函数实现方直接调用,而是间接的需要另外一个函数调用使用。

需要注意的是我们需要将base强制转换为char*,令该指针加减整数的步长设为1,这是qsort可以不考虑数据类型排序的原理。


第二部分进阶指针就讲到这,下一篇内容我会引入笔试题实战为大家带来更加优质的内容,关注博主不迷路🔥🔥🔥

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

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

相关文章

Spark—Shell命令对WordCount案例的基本操作(统计、去重、排序、求平均值及join)

一、统计、去重 1、案例数据介绍 WordCount统计&#xff1a;某电商网站记录了大量的用户对商品的收藏数据&#xff0c;并将数据存储在名为buyer_favorite的文本文件中。文本数据格式如下&#xff1a; 2、启动spark-shell 配置好spark环境&#xff0c;若还没有环境可以参考…

windows下环境问题总结

nacos 启动后在spring 项目中无法加载yml配置文件 spring.datasource.platform mysql 注意一定要放开这行&#xff0c;不放的话&#xff0c;可能会导致服务可以成功注册&#xff0c;但是&#xff0c;我们无法使用局部的 nacos里yml配置文件的属性

Linux:项目自动化构建工具——make/Makefile

文章目录 一.make与Makefile的关系1.Makefile2.make 二.项目清理1.clean2. .PHONY 前言&#xff1a; 本章主要内容有认识与学习Linux环境下如何使用项目自动化构建工具——make/makefile。 一.make与Makefile的关系 当我们编写一个较大的软件项目时&#xff0c;通常需要将多个…

js实现图片压缩

创建一个type"file"的input标签&#xff0c;用于文件上传。 <input type"file" name"" id"upload" value"" />通过js实现图片压缩 window.onload function () {const upload document.getElementById("upload…

9.10UEC++生成、销毁actor

BeginPlay&#xff1a; 1.SpawnActor&#xff1a;<模板类>&#xff08;模板::staticclass&#xff08;&#xff09;&#xff0c;FVector const class&#xff0c;FRotation const class&#xff09; 生成一个actor 2.Destory&#xff08;&#xff09;从世界中销毁一个a…

SSM学习笔记-------Spring(一)

SSM学习笔记-------Spring&#xff08;一&#xff09; Spring_day011、课程介绍1.1 为什么要学?1.2 学什么?1.3 怎么学? 2、Spring相关概念2.1 初识Spring2.1.1 Spring家族2.1.2 了解Spring发展史 2.2 Spring系统架构2.2.1 系统架构图2.2.2 课程学习路线 2.3 Spring核心概念…

【zabbix 代理服务器】

目录 一、部署 zabbix 代理服务器1、设置 zabbix 的下载源&#xff0c;安装 zabbix-proxy2、初始化数据库1、创建数据库并指定字符集2、创建 zabbix 数据库用户并授权 3、导入数据库信息4、修改 zabbix-proxy 配置文件5、启动 zabbix-proxy6、在所有主机上配置 hosts 解析7、在…

Maven高级(四)--私服

一.作用 我们所拆分的模块是可以在同一个公司各个项目组之间的项目组之间进行资源共享的&#xff0c;这就需要Maven的私服来实现。 二.场景 两个项目组之间如何基于私服进行资源的共享的呢&#xff1f; 例如A开发了一个模块tlias-utils,B团队进行项目开发&#xff0c;要想使用…

CentOS7 主机万兆网卡配置端口聚合后如何与华为交换机连接并上网

环境: 组装机测试服务器 CentOS 7 CentOS Linux release 7.7.1908 (Core) 网卡1:瑞昱普通千兆板载网卡 网卡2:EB-LINK intel 82599芯片PCI-E X8 10G 光模块千兆单模1310 交换机 HW-S1730S-S48T4S-A Version 5.170 (S1730 V200R021C01SPC200) 光模块千兆单模1310 …

Python学习笔记(十七)————模块相关

目录 &#xff08;1&#xff09;模块 &#xff08;2&#xff09;模块的导入方式 ①import 模块名 ②from 模块名 import 功能名 ③from 模块名 import * ④as定义别名 &#xff08;3&#xff09;自定义模块 &#xff08;4&#xff09;测试模块 &#xff08;1&#xff09…

List移除元素的四种方式

List 移除某个元素 四种方式&#xff1a; 方式一&#xff0c;使用 Iterator &#xff0c;顺序向下&#xff0c;如果找到元素&#xff0c;则使用 remove 方法进行移除。方式二&#xff0c;倒序遍历 List &#xff0c;如果找到元素&#xff0c;则使用 remove 方法进行移除。方式…

使用TypeScript实现贪吃蛇小游戏(网页版)

本项目使用webpackts所编写 下边是项目的文件目录 /src下边的index.html页面是入口文件 index.ts是引入所有的ts文件 /modules文件夹是用来存放所有类的 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"…

VoxaMech 的机甲装备 — NFT 系列

一套新的机甲装备即将诞生&#xff1a;帝国甲虫套装是无与伦比的权威和威望的象征&#xff0c;体现了古代帝国甲虫的雄伟壮观。其华丽的铠甲散发着帝王般的优雅气息&#xff0c;其威严的外观在战场上令人肃然起敬。 每套装备由手臂部件、胸甲、头盔、腿部件和剑组成。每件装备单…

SpringBoot——业务层测试事务回滚

事务回滚 关于事务回滚的概念我们之前在学习数据库的时候已经提到过了&#xff0c;这里我们再次强化一下记忆。所谓的事务回滚就是在执行多条SQL语句的时候&#xff0c;如果其中一条SQL出现了异常导致执行失败&#xff0c;则数据库的状态回滚到执行多条SQL语句之前的状态&…

第六章:YOLO v1网络详解(统一的实时目标检测)

(目标检测篇&#xff09;系列文章目录 第一章:R-CNN网络详解 第二章:Fast R-CNN网络详解 第三章:Faster R-CNN网络详解 第四章:SSD网络详解 第五章:Mask R-CNN网络详解 第六章:YOLO v1网络详解 第七章:YOLO v2网络详解 第八章:YOLO v3网络详解 文章目录 系列文章目录技…

一起学SF框架系列5.7-模块Beans-BeanDefinition定义

在SF下&#xff0c;开发人员用xml或注解模式定义bean&#xff0c;框架把这些定义转化为内部BeanDefinition类&#xff0c;然后通过BeanDefinition类实现Bean的管理&#xff08;包括初始化、依赖注入及生命周期管理&#xff09;&#xff0c;因此了解Bean的定义、解析、使用过程非…

[kafka] windows下安装kafka(含安装包)

[kafka] windows下安装kafka&#xff08;含安装包&#xff09; 目录 前言 一、下载kafka安装包 1&#xff09;下载安装包 2&#xff09;解压安装包 二、运行zookeeper 1.运行zookeeper&#xff08;因为kafka必须要和zookeeper一起运行&#xff09; 三、运行kafka 四、使用fafka…

web3创业有哪些机会及具体案列(二)

目录 1. 去中心化金融&#xff08;DeFi&#xff09;&#xff1a;2. 去中心化身份验证和数字身份&#xff1a;3. 去中心化市场和电子商务&#xff1a;4. 区块链游戏和虚拟资产&#xff1a;5. 数据隐私和安全&#xff1a;6. 去中心化社交媒体&#xff1a;7. 去中心化能源交易&…

Spring容器扩展点在微服务中的使用

(20230306)Spring容器扩展点在微服务中的使用 文章目录 1. Spring扩展点梳理2.Spring扩展点应用场景2.1 整合NacosApplicationListener扩展场景——监听容器中发布的事件Lifecycle扩展场景——管理具有启动、停止生命周期需求的对象 2.2 整合RibbonSmartInitializingSingleton扩…

定时器中断实验(stm32)

目录 TIME的代码time.ctime.h main.c TIME的代码 time.c void TIM3_Int_Init(u16 arr,u16 psc) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能//定时器TIM3初始化…