【c语言】深入理解指针3——回调函数

一、回调函数

回调函数:通过函数指针调用的函数.

当把一个函数的地址传递给另一个函数,通过该地址去调用其指向的函数,那么这个被调用的函数就是回调函数.

示例:
在【深入理解指针2】中结尾写了用函数指针实现计算器的功能,如下:

void menu()
{printf("1.add   2. sub \n");printf("3.mul   4. div \n");printf("0.exit \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 a, b;printf("请输入两个参数:");scanf_s("%d %d", &a, &b);int ret = pf(a, b);printf("%d \n", ret);
}
int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;do {menu();printf("请选择:");scanf_s("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);case 4:calc(Div);break;case 0:printf("退出\n");break;default:printf("请重新输入:");break;}} while (input);return 0;
}

这里加入想实现加法函数的功能,并没有直接在主函数中直接调用加法函数Add,而是通过另一个函数calc中函数指针变量来接受Add函数的地址,然后通过函数指针变量再去调用Add函数,因此,这里用函数指针去调用的那个函数就是回调函数,即Add、Sub、Mul、Div是回调函数.

二、qsort函数使用

官网对qsort函数的说明:

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));      

qsort函数有四个参数:

  1. void* base : base是一个指针,指向待排序数组的首元素地址
  2. size_t num : num是指待排序数组中元素的个数
  3. size_t size :size是指待排序数组中每个元素的大小
  4. compar : 函数指针,指向两个元素的比较函数,此函数需要自己实现

在这里插入图片描述
假设两元素为p1,p2,若p1<p2,则返回小于0的值;若p1=p2,则返回等于0的值;若p1>p2,则返回大于0的值.

排序整型数据

#include <stdlib.h>
#include<stdio.h>
int cmp(void* e1, void* e2)//比较函数
{return *(int*)e1 - *(int*)e2;//void* 不能解引用,要强制类型转换//此方式默认升序排列,若想实现降序,可用e2-e1,将逻辑改反
}
void test()
{int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]),cmp );int i = 0;for (i = 0;i < sz;i++){printf("%d ", arr[i]);}}
int main()
{test();return 0;
}

排序结构体数据

#include <string.h>
struct stu
{char name[20];int age;
};
int cmp_name(const void* e1,const void* e2)
{return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void test()
{struct stu arr[3] = { {"zhangsan",18},{"lishi",25},{"wangwu",22} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_name);}
int main()
{test();return 0;
}

其中,strcmp函数比较时,比较的是对应字符的ASCII码值,并不比较字符串长度,比如:

  1. “abc"和"abd” : a和a的ASCII码值相等,比较b和b,也相等,再比较c和d,d的ASCII码值大,则"abc"小于"abd"
  2. “aef"和"abcde” : a的ASCII码值相同,e的ASCII码值大于b,则不再进行比较,认为"aef"大于"abcde".

在这里插入图片描述

可以看出 ’ l ’ < ’ w ’ < ’ z ',因此 " lishi " 排第一

三、qsort函数模拟实现

用冒泡排序实现qsort函数

冒泡排序算法:

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 - i - 1;j++)//交换次数{if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = arr[j];}}}
}

我们想排序的数据类型如果不是整型,是结构体类型、字符类型等,在bubble_sort函数参数部分就不可以用整型数组来接收,因此,参考qsort函数的定义:

void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2));
  1. 冒泡排序首先要知道要比较的数据的首元素地址,但是不知道具体是什么类型,因此用void* base来接收
  2. 需要知道数据的个数,类型为无符号的整型,用sz接收
  3. 在我们拿到首元素地址之后,向后找元素进行比较,需要知道每个元素的内存大小,不同类型元素的大小不同,但都是整型,因此用width接收,与qsort函数的size相似
  4. 最后需要传入对元素进行比较的函数的地址,类型为函数指针,因为并不知道要比较的元素类型,因此用void* 类型的指针进行接收,进行比较时根据不同的类型进行强制类型转换,然后用返回值来判断他们的大小关系,因此比较函数返回类型为 int

然后分析冒泡排序函数内部:其中排序的趟数和元素交换次数不需要变
在这里插入图片描述

  1. 在元素进行比较时,如果元素类型不是整型,如结构体类型、字符类型等就不可用大于小于这种方式来比较,因此应该用函数指针cmp去调用 用来比较的函数进行元素比大小
  2. 在元素交换部分,交换的元素类型不能确定,肯定不能使用 int 类型,因此将其封装成函数,在其内部进行操作,具体思路如下:

在这里插入图片描述

在这里插入图片描述

拿到元素地址的目的是为了进行元素的交换. 首先,base是无符号指针,应该先对其进行强制类型转换成char*类型,那么base+4就拿到下一个元素的地址,base+8拿到第三个元素的地址,以此类推…
每个元素的内存大小为width,那么第一个元素地址为 (char*) base + j * width , 第二个元素地址为 (char*) base + (j+1) * width ,拿到两个元素地址后进行交换,交换完成后 j++,这样继续取得第二个和第三个元素的地址,继续进行操作

有人会问,为什么base要强转成char*类型,转换成int*不行吗?

我们不知道要排序的元素类型是什么,可能是整型,可能是结构体类型,可能是字符类型等等,每个元素的大小可能是1、4、7、9等,那么在解引用,进行交换元素的时候,由于整型一次只能交换4个字节的内存,在交换7、9字节的内存时候就不行了,char*类型进行解引用是char类型,每次只交换一个字节,这样无论元素是什么类型都可以交换完成

因此,由于排序元素的类型不确定性,选择一个字节一个字节进行交换的方式完成排序,进而选择(char*)base
具体实现代码如下:

//比较函数由自己编写,只有使用者知道要排序什么类型的元素
int cmp_int(const void* e1, const void* e2)
{return strcmp((char*)e1 , (char*)e2);
}
void Swap(char* buf1, char* buf2,size_t width)
{char 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 - i - 1;j++){if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}int main()
{char arr[] = "aksjdu";int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for (i = 0;i < sz;i++){printf("%c ", arr[i]);}return 0;
}

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

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

相关文章

HTTP 核心概念

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

VidBot:从野外 2D 人体视频中学习可泛化的 3D 动作,实现零样本机器人操控

25年3月来自慕尼黑工大、瑞士 ETH 和微软的论文“VidBot: Learning Generalizable 3D Actions from In-the-Wild 2D Human Videos for Zero-Shot Robotic Manipulation”。 未来的机器人被设想为能够执行各种家务的多功能系统。最大的问题仍然是&#xff0c;如何在尽量减少机器…

Linux 日常运维命令大全

Linux 作为一种开源操作系统&#xff0c;在服务器运维中扮演着重要角色。掌握常用的 Linux 命令对于运维人员而言至关重要。本文将整理一份 Linux 服务器运维常用命令大全&#xff0c;帮助你在日常工作中提高效率和准确性。 1. 基础命令 基础命令是Linux操作的起点&#xff0…

编程规范之枚举

编程规范之枚举 1.1 初始化枚举项 枚举平时用的也没有很频繁&#xff0c;今天看代码规范提到枚举类型初始化枚举项。并对初始化枚举项进行了归纳。包括下面三个 不进行显示初始化&#xff0c;交由编译器完成。 对第一个枚举项的显式初始化&#xff0c;这样可以强制整数值的…

《软件设计师》复习笔记(12.1)——范围管理、进度管理

目录 一、范围管理 1. 核心概念 2. 范围管理过程 WBS&#xff08;工作分解结构&#xff09;示例 真题示例&#xff1a; 二、进度管理 1. 核心过程 2. 关键工具与技术 真题示例&#xff1a; 一、范围管理 1. 核心概念 项目范围&#xff1a;为交付产品必须完成的工作…

过去十年前端框架演变与技术驱动因素剖析

一、技术演进脉络&#xff08;2013-2023&#xff09; 2013-2015&#xff1a;结构化需求催生框架雏形 早期的jQuery虽然解决了跨浏览器兼容性问题&#xff08;如IE8兼容性处理&#xff09;&#xff0c;但其松散的代码组织方式难以支撑复杂应用开发。Backbone.js的出现首次引入M…

中华传承-医山命相卜-梅花易数

梅花易数 灵活起卦&#xff08;如数字、声音、外应等&#xff09;和象数结合&#xff0c;准确率可达96.8%。其起卦方式摆脱传统龟壳、蓍草的繁琐&#xff0c;强调直觉与灵活性。 个人决策、事件预测等 尤其在短期、具体问题上表现突出。

如何用Brower Use WebUI实现网页数据智能抓取与分析?

作者&#xff1a;算力魔方创始人/英特尔创新大使刘力 Browser-use是一款能让AI智能体像人类一样操作网页的创新工具&#xff0c;与传统网络爬虫技术相比&#xff0c;Browser-use能模拟人浏览并操作网页&#xff0c;在采集网站数据时&#xff0c;不会被网站反爬机制识别和封禁&…

LIMS引领综合质检中心数字化变革,赋能质量强国战略

在质量强国战略的深入推进下&#xff0c;我国综合质检机构迎来了前所未有的发展机遇&#xff0c;同时也面临着诸多严峻挑战。随着检测领域从传统的食品药品监督向环境监测、新材料检测等新兴领域不断拓展&#xff0c;跨领域协同管理的复杂度呈指数级增长。作为提升产品质量的关…

简单好用的在线工具

用AI写了一些在线工具&#xff0c;简介好用&#xff0c;推荐给大家&#xff0c;欢迎大家使用并提议意见。 网址&#xff1a;https://www.bittygarden.com/ 目前已有以下功能&#xff1a; MD5SM3SHAUnicode 编码Unicode 解码Base32 编码Base32 解码Base64 编码Base64 解码URL …

阿里云服务器搭建开源版禅道

一&#xff0c;下载地址&#xff1a;禅道11.5版本发布&#xff0c;主要完善细节&#xff0c;修复bug&#xff0c;新增动态过滤机制 - 禅道下载 - 禅道项目管理软件 下载地址二&#xff1a; 禅道21.6.stable 实现旧编辑器撰写的文档无感升级至新版编辑器 - 禅道下载 - 禅道项目…

leetcode 309. Best Time to Buy and Sell Stock with Cooldown

目录 题目描述 第一步&#xff0c;明确并理解dp数组及下标的含义 第二步&#xff0c;分析并理解递推公式 1.求dp[i][0] 2.求dp[i][1] 3.求dp[i][2] 第三步&#xff0c;理解dp数组如何初始化 第四步&#xff0c;理解遍历顺序 代码 题目描述 这道题与第122题的区别就是卖…

嵌入式硬件常用总线接口知识体系总结和对比

0.前言 在嵌入式工程实现中,多多少少我们都使用过总线,各种各样的总线应用于不同场合,不同场景有不同的优势,但是我们在作为工程师过程中在如何选择项目合适的总线,根据什么来选?需要我们对项目全局和总线特征有所了解,本文目的就是对比多种总线的关键特征 我们在聊到…

数据分析处理库Pandas常用方法汇总

目录 一、基础操作 1.1 创建df对象 1.1.1 读入表格数据 1.1.2 手动创建df 1.2 .info() 1.3 df.index 1.4 df.columns 1.5 df.dtypes 1.6 df.values 1.7 .set_index() 1.8 df[xxx] 1.9 .describe() 1.10 .isin() 1.12 .where() 1.13 .query() 1.14 Series类型运算…

智慧大屏系统

延凡智慧大屏系统旨在打破数据壁垒&#xff0c;将海量、复杂的数据转化为直观易懂的可视化图形和信息&#xff0c;广泛应用于城市管理、企业运营、交通指挥、能源监控等多个领域&#xff0c;为管理者、决策者提供全面、实时、精准的信息展示和分析工具&#xff0c;助力高效决策…

树莓派超全系列教程文档--(32)config.txt常用音频配置

config.txt常用音频配置 板载模拟音频&#xff08;3.5mm耳机插孔&#xff09;audio_pwm_modedisable_audio_ditherenable_audio_ditherpwm_sample_bits HDMI音频 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 板载模拟音频&#xff08;3.5mm耳机…

23种设计模式全面解析

设计模式是解决软件设计中常见问题的经典方案。根据《设计模式&#xff1a;可复用面向对象软件的基础》&#xff08;GoF&#xff09;&#xff0c;23种设计模式分为以下三类&#xff1a; 一、创建型模式&#xff08;5种&#xff09; 目标&#xff1a;解耦对象的创建过程&#x…

AI 推理框架详解,包含如COT、ReAct、LLM+P等的详细说明和分类整理,涵盖其原理、应用场景及对比分析

AI 推理引擎 以下是关于 AI 推理引擎 的详细说明&#xff0c;涵盖其定义、类型、核心组件、技术实现、应用场景及挑战&#xff1a; 1. 推理引擎的定义 推理引擎&#xff08;Inference Engine&#xff09;是 AI系统的核心组件&#xff0c;负责根据输入数据、知识库或预训练模…

《探秘鸿蒙分布式软总线:开启无感发现与零等待传输新时代》

在数字化浪潮中&#xff0c;设备之间的互联互通成为构建智能生态的关键。鸿蒙系统中的分布式软总线技术&#xff0c;宛如一座桥梁&#xff0c;让各种智能设备紧密相连。尤其是其实现的设备间无感发现和零等待传输功能&#xff0c;更是为用户带来了前所未有的便捷体验&#xff0…

JDBC 与 MyBatis 详解:从基础到实践

目录 一、JDBC 介绍 二、使用 JDBC 查询用户信息 三、ResultSet 结果集 四、预编译 SQL - SQL 注入问题 五、预编译 SQL - 性能更高 六、JDBC 增删改操作 插入数据&#xff1a; 更新数据&#xff1a; 删除数据&#xff1a; 七、MyBatis 介绍 八、MyBatis 入门程序 引…