回调函数是什么

回调函数是什么

回调函数就是⼀个通过函数指针调⽤的函数

如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。

回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

回想一下我们在设计一个计算器的时候:

需要写加减乘除函数如下:

int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{eturn a * b;
}
int div(int a, int b)
{return a / b;
}

在使用回调函数改造前:

  • 可以发现存在很多冗余的地方,在每个分支都需要书写相同的scanfprintf语句
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 (int,int)类型的,我们可以把调⽤的函数的地址以参数的形式传递过去,使⽤这样类型的函数指针接收,函数指针指向什么函数就调⽤什么函数,这⾥其实使⽤的就是回调函数的功能。

void calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}

使用回调函数改造后

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: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;
}

注意区分这和我们在中实现的转移表,这里使用的是回调函数,但在转移表中我们使用的是函数指针数组


qsort函数介绍和使用举例

qsort函数介绍

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

void qsort(void* base, //指向待排序数组的第一个元素的指针size_t num, //base指向数组中的元素个数size_t size,//base指向的数组中一个元素的大小,单位是字节int (*cmp)(const void*, const void*) //函数指针 - 传递函数的地址    );
  • 头文件为stdlib.h

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 参数四个介绍如上

  • 对最后一个参数特别介绍一下:

    • 函数指针,指向的函数是用来比较待排序数组的元素大小的
    • 由使用qsort函数的用户来实现
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
      • 如果p1指向的元素小于p2,则返回小于0的数字
      • 如果二者相等,则返回0
      • 如果p1指向的元素大于p2,则返回大于0的数字
  • qsort函数默认是排升序,如果想排降序,则在compare函数里将上述规则反一下即可,即当p1指向的元素小于p2时返回大于0的数字

qsort函数排序整型数据

#include <stdio.h>
#include <stdlib.h>//qosrt函数的使⽤者得实现⼀个⽐较函数int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(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;
}

使用qsort排序结构数据

struct Stu //学⽣
{char name[20];//名字int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{test2();test3();return 0;
}

qsort函数的模拟实现

使用回调函数,模拟实现qsort

注意:

  • qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,对快排想要了解更多的读者可以看看
  • 这里使用void*的指针,以实现泛型编程

在实现前我们先温故一下冒泡排序

  • 总共n个数据,要排n-1趟
  • 第i(i从0开始取)趟要比较n-1-i次

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

void bubble_sort(int arr[], int sz)
{//趟数int i = 0;for (i = 0; i < sz - 1; i++){//一趟内部的两两比较int j = 0;for (j = 0; j < sz-1-i; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}
void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}int main()
{int arr[] = { 3,1,7,9,4,2,6,5,8,0 };//排序 - 升序int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);print_arr(arr, sz);return 0;
}

冒泡排序只需要两个参数,待排序数组和数组元素个数

我们要实现的qsort是可以针对任何数据进行排序,那想一下我们知道用户使用这个函数的时候是拿来排序什么数据吗?显然是不知道的,所以在内部实现时,我们需要更改什么呢?分析如下:

  • 首先是趟数和一趟之内的比较次数,这是冒泡算法,无论什么数据都不需要改变整体的大框架

重点在于以下两点:

  1. 比较的方式
  • 由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,是不能进行解引用的,且数据类型是不能传参的,那我们该怎么找到相邻元素比较呢?

于是我们在参数中添加了数组元素的大小(即宽度,一个元素占几个字节,这是用户可以传参的),这样就能找到相邻元素了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(char*)base + j * width
(char*)base + (j + 1) * width) 

这样在内层循环中就能依次找到两个相邻元素了

接下来就是如何比较,由于我们不知道用户排序什么数据,所以没办法实现两个数据的比较,例如整数可以直接使用关系操作符,而字符串需要strcmp函数等等,于是我们把比较两个数据大小的函数交给用户去实现,所以在参数中使用了一个函数指针

这样比较两数的方式就更改完毕了

if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
  • 这里我们默认还是qsort的比较规则,用户实现compare函数时如果遵守:当第一个元素大于第二个元素时,就返回大于0的数字,此时我们交换,按这个规则排序出来为升序,反之为降序

  1. 交换数据的方式
  • 同样的是,我们不知道数据类型,但我们知道数据的大小,所以我们可以一个一个字节的交换

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

void Swap(char* buf1, char* buf2, size_t width)
{int i = 0;char tmp = 0;for (i = 0; i < width; i++){tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}

这样我们就完成了qsort函数的模拟实现

void Swap(char* buf1, char* buf2, size_t width)
{int i = 0;char tmp = 0;for (i = 0; i < width; i++){tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{//趟数int i = 0;for (i = 0; i < sz - 1; i++){//一趟内部的两两比较int j = 0;for (j = 0; j < sz - 1 - i; j++){//if (arr[j] > arr[j + 1])//比较两个元素if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){//交换两个元素Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}

总结

qsort函数是很典型的回调函数的例子,因为不知道用户排序数据的类型,所以qsort函数的实现方把比较两个数据的函数交给用户自己去实现,这个函数通过函数指针传递给qsort,在qsort函数内部发生比较时再根据函数指针调用这个比较函数,这种就是回调函数

两个元素
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换两个元素
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}


-----# 总结==`qsort`函数是很典型的回调函数的例子,因为不知道用户排序数据的类型,所以`qsort`函数的实现方把比较两个数据的函数交给用户自己去实现,这个函数通过函数指针传递给`qsort`,在`qsort`函数内部发生比较时再根据函数指针调用这个比较函数,这种就是回调函数==

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

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

相关文章

Linux驱动开发(速记版)--GPIO子系统

第105章 GPIO 入门 105.1 GPIO 引脚分布 RK3568 有 5 组 GPIO&#xff1a;GPIO0 到 GPIO4。 每组 GPIO 又以 A0 到 A7&#xff0c;B0 到 B7&#xff0c;C0 到C7&#xff0c;D0 到 D7&#xff0c;作为区分的编号。 所以 RK3568 上的 GPIO 是不是应该有 5*4*8160 个呢&#xff1…

Semantic Communication Meets Edge Intelligence——构造终端共享的知识图谱指导无线物联网通信中文本的传输

论文链接&#xff1a; IEEE Xplore Full-Text PDF:https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber9979702 1. 背景 随着自动驾驶、智能城市等应用的发展&#xff0c;移动数据流量将大幅增加。传统的香农信息论&#xff08;CIT&#xff09;通信系统已接近其带…

SpringBoot MyBatis连接数据库设置了encoding=utf-8还是不能用中文来查询

properties的MySQL连接时已经指定了字符编码格式&#xff1a; url: jdbc:mysql://localhost:3306/sky_take_out?useUnicodetrue&characterEncodingutf-8使用MyBatis查询&#xff0c;带有中文参数&#xff0c;查询出的内容为空。 执行的语句为&#xff1a; <select id&…

decltype推导规则

decltype推导规则 当用decltype(e)来获取类型时&#xff0c;编译器将依序判断以下四规则&#xff1a; 1.如果e是一个没有带括号的标记符表达式(id-expression)或者类成员访问表达式&#xff0c;那么decltype(e)就是e所命名的实体的类型。此外&#xff0c;如果e是一个被重载的函…

k8s 中存储之 NFS 卷

目录 1 NFS 卷的介绍 2 NFS 卷的实践操作 2.1 部署一台 NFS 共享主机 2.2 在所有k8s节点中安装nfs-utils 2.3 部署nfs卷 2.3.1 生成 pod 清单文件 2.3.2 修改 pod 清单文件增加 实现 NFS卷 挂载的 参数 2.3.3 声明签单文件并查看是否创建成功 2.3.4 在 NFS 服务器 创建默认发布…

思维导图工具,轻松搞定复杂问题!

一提到思维导图&#xff0c;想必大家都不会陌生&#xff1b;它能帮助我们更好地梳理思路&#xff0c;让复杂的想法变得清晰可见&#xff1b;而随着互联网的普及&#xff0c;在线思维导图工具更是成为了我们日常工作和学习的得力助手&#xff1b;今天&#xff0c;我就来给大家推…

小红书算法岗面试,竞争太激烈了

最近已有不少大厂都在秋招宣讲了&#xff0c;也有一些在 Offer 发放阶段。 节前&#xff0c;我们邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对新手如何入门算法岗、该如何准备面试攻略、面试常考点、大模型技术趋势、算法项目落地经验分享等热门话题进行了…

【Kubernetes】常见面试题汇总(五十八)

目录 127.创建 PV 失败&#xff1f; 128. pod 无法挂载 PVC&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 69-113 属于【Kubernetes】…

一个月学会Java 第4天 运算符和数据转换

Day4 运算符和数据转换 今天来讲运算符&#xff0c;每个运算符的作用和现象&#xff0c;首先我们先复习一下数据类型&#xff0c; day2讲过基本数据类型有八种&#xff0c;int、short、long、byte、char、boolean、float、double&#xff0c;分别为四个整型、一个字符型、一个布…

基于springboot vue3 在线考试系统设计与实现 源码数据库 文档

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

电脑断网或者经常断网怎么办?

1、首先&#xff0c;按一下键盘的win R &#xff0c; 在打开的运行框内输入&#xff1a;cmd 然后按一下回车 或者 点击一下【确定】 2、在命令窗口输入&#xff1a;ipconfig/release , 然后按一下回车 作用&#xff1a;IP释放&#xff0c;相当于把网线拔了重新插上 3、接着…

佳能基于SPAD的监控摄像机MS-500入选《时代》2023最佳发明

一、产品概述 佳能MS-500是一款采用SPAD(Single Photon Avalanche Diode,单光子雪崩二极管)传感器的监控摄像机。SPAD传感器以其极高的灵敏度和在低光环境下的卓越表现而闻名,使得MS-500能够在夜晚或极暗光条件下拍摄到清晰、彩色的画面。此外,MS-500还配置了高性能的镜头…

Python异常处理中的9个常见错误及其解决办法,建议收藏

在Python编程中&#xff0c;异常处理是确保程序健壮性和可靠性的重要部分。然而&#xff0c;在使用异常处理时&#xff0c;开发者可能会犯一些常见的错误。以下是9个常见的异常处理错误及其解决办法&#xff1a; 1. 语法错误 (SyntaxError) 语法错误是最常见的错误之一。它通…

【梯级水电站调度优化】基于线性递减策略优化粒子群算法

课题名称&#xff1a; 基于改进粒子群算法的梯级水电站调度优化 改进方向&#xff1a; 线性递减策略优化粒子群PSO 代码获取方式&#xff08;付费&#xff09;&#xff1a; 相关资料&#xff1a; 1. 粒子群算法的基本原理 2. 梯级水电站调度优化模型 3. 代码注释 4. 代码…

第十一章 缓存之更新/穿透/雪崩/击穿

目录 一、什么是缓存 二、缓存更新策略 2.1. 缓存主动更新策略 2.1.1. Cache Aside模式&#xff08;主流&#xff09;‌ 2.1.2. Read/Write Through模式‌ 2.1‌.3. Write Behind模式‌ 2.1.4. 总结 三、缓存穿透 四、缓存雪崩 五、缓存击穿 本文中的图片内容部分来源…

【笔记】神领物流Day1.1.20权限管家

传智权限管家是一个通用的权限管理中台服务&#xff0c;在神领物流项目中&#xff0c;我们使用权限系统管理企业内部员工&#xff0c;比如&#xff1a;快递员、司机、管理员等。 在权限管家中可以管理用户&#xff0c;管理后台系统的菜单&#xff0c;以及角色的管理。 权限管家…

视频加字幕用什么软件最快?12款工具快速添加字幕!

对于大多数同学来讲&#xff0c;剪辑中比较头疼的就是如何给视频加字幕和唱词啦&#xff0c;特别是用Pr或者FCXP等专业剪辑软件&#xff0c;加字幕也是特别费时的&#xff0c;哪怕是有批量添加的功能orz... 虽然关于这方面的内容已经很多啦&#xff0c;但是真正全面的内容还特…

哈希闭散列的实现与机制

目录 哈希的介绍 哈希冲突 原因 影响 解决方法 实例 哈希函数 哈希函数设计原则&#xff1a; 常见哈希函数 闭散列 线性探测的实现 代码解读 1. 命名空间和枚举定义 2. 哈希表节点结构体 3. 哈希函数模板 4. 哈希表类 5. 插入、查找和删除逻辑 二次探测 哈希的…

【React】事件机制

事件机制 react 基于浏览器的事件机制自身实现了一套事件机制&#xff0c;称为合成事件。比如&#xff1a;onclick -> onClick 获取原生事件&#xff1a;e.nativeEvent onClick 并不会将事件代理函数绑定到真实的 DOM节点上&#xff0c;而是将所有的事件绑定到结构的最外层…

latex本地运行(MiKTeX+VScode)-20241006

1、安装 LaTex 主流的分发版本应该就是 TeXLive 和 MikTeX 了,这里使用 MikTex(只有几百M)—— TeXLive 太大了、默认安装全部包,可选自选部分安装单实在有些许麻烦,MikTeX 则方便得多,需要的时候可以自动安装全部包 点击跳转到 MiKTeX 官网,直接下载即可:不用担心什…