【C语言】带你完全理解指针(四)函数指针的应用sqort函数的实现

前言:

本文主要是函数指针的重要应用,介绍qsort函数以及模拟实现这样一个不限制使用数据类型的快速排序函数。

回调函数

函数指针有一个非常大的作用就是实现回调函数。非常重要

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

 以上面的计算机的代码为例子观察代码可以发现 ,只有调用函数部分不一样,那么能不能定义一个cacl()函数,通过将加减乘除函数作为参数传入到calc函数中,达到在calc函数中调用加减乘除函数?

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;
}void menu()
{printf("*******************************\n");printf("******   1.add   2.sub    *****\n");printf("******   3.mul   4.div    *****\n");printf("******   0.exit           *****\n");printf("*******************************\n");
}void calc(int(*pf)(int, int))
{int a = 0;int b = 0;int ret = 0;printf("请输入2个操作数:");scanf("%d %d", &a, &b);ret = pf(a, b);printf("%d\n", ret);
}int main()
{int input = 0;do{menu();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函数

介绍一下冒泡排序

冒泡排序
有一组整数,需要排序为升序
1. 两两相邻的元素比较
2. 如果不满足顺序就交换当前的代码只适合于整型数据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;}}}}

这个冒泡排序其实是有缺陷的,它的参数是限制为只能是int类型,限制了它只能够排序整型数据!而qsort函数是一个可以用来排序任意类型数据的函数。

qsort函数介绍

qsort函数是C语言中的一个标准库函数,用于快速排序一个数组。它的原型如下:

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

参数解释:

  • void *base:指向待排序数组的起始位置的指针。也就是需要排序的数组的第一个元素
  • size_t nitems:数组中的元素个数。
  • size_t size:每个元素的大小(以字节为单位)。
  • int (*compar)(const void *, const void *):比较函数指针,用于比较两个元素的大小。

qsort函数使用了快速排序算法,它会根据比较函数的返回值对数组进行排序。比较函数的定义如下:

int compar(const void *a, const void *b)

比较函数需要返回一个整数值。如果返回值小于0,表示a应该在b之前;如果返回值大于0,表示a应该在b之后;如果返回值等于0,表示a和b的顺序不变。这个函数需要两个参数两个要比较的值。

快速排序是一种基于比较的排序算法,它的平均时间复杂度为O(nlogn),是常用的排序算法之一。通过不断地分割数组并以一个基准值为中心进行排序,快速排序可以高效地将数组排序。

使用qsort函数时,你只需要提供一个比较函数,qsort函数会自动根据这个比较函数对数组进行排序。这使得排序变得非常简单和方便。你可以使用qsort函数对任何一种数据类型的数组进行排序,只需要提供相应的比较函数。 

 比较函数的形参中为什么用的是void*

void* 是无具体类型的指针,不能直接进行解引用操作符(但是可以强制类型转换之后可以解引用),也不能进行+-整数的操作,它是用来存放任意类型数据的地址(可以理解为一个仓库,什么都能装,当需要用时再强制类型转换为需要的类型)。只有void*被允许存放任意类型数据的地址,如果是其他类型的指针编译器会报错。正是因为定义qsort函数时用的是void*,qsort函数才可以排序任意类型的数据。

 个用于排序整型数据比较函数cmp_int

int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}

 做差是因为这个比较函数需要返回值,如果返回值小于0,表示e1应该在e2之前;如果返回值大于0,表示e1应该在e2之后;如果返回值等于0,表示e1和e2的顺序不变

想要排成降序把这个比较函数的两个参数减法的位置变一下就好了

 同理,qsort函数排序结构体类型数据(下面例子以结构体中的年龄来排序)

struct Stu{char name[20];int age;};int cmp_stu_by_age(const void* p1, const void* p2){return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;}void test2(){struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);}int cmp_stu_by_name(const void* p1, const void* p2){return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);}void test3(){struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);}int main(){//test1();//test2();test3();return 0;}

按年龄比较的函数中: 同样这里的指针不能直接解引用,先强制类型转换。

按名字比较的那个函数中:两个字符串比较使用strcmp函数(专门用来比较字符串的大小的)

模拟实现qsort函数 

使用【冒泡排序】的算法,模拟实现一个排序函数 bsort ,可以用来排序任意类型的数据。

在冒泡排序的基础上来实现。

冒泡排序
有一组整数,需要排序为升序
1. 两两相邻的元素比较
2. 如果不满足顺序就交换当前的代码只适合于整型数据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*的指针,指向需要排列的数组的第一个元素因为它是用来存放任意类型数据的地址

参数二:对照库函数qsort的定义中,第二个参数为待排序数组的元素个数,因此我们也用个 size_t num 存放待排序数组的元素个数。

当前,我们可以通过参数一和参数二知道起始位置地址(void* base)和元素个数(num),但是仅仅知道起始地址和元素个数是不够的,因为不知道一个元素有多大的,一次需要跳过多少个字节

参数三:因此还需要一个参数记录一个元素的大小 size_t size

函数内部循环的趟数和一趟的次数是不需要改造的

问题:对于不同类型的数据要有不同的比较方式,比如整型可以通过><来比较但是结构体就不行

解决方法·:将2个元素的比较方法以函数参数的形式传递

问题:不同的数据交换方式也有差异

 因为是void*类型不能够直接加减整数来移动所以要强制类型转换才行,转换为char*最为合适因为步长最短,可以满足所有类型的交换。base是下标,想要找到下一个元素的地址应该加上一个元素的大小也就是size。交换第j个元素和第j+1个元素,应该起始地址加上j*size和起始地址加上(j+1)*size

cmp((char*)base + size * j

 完成使用cmp来比较两个元素的大小关系了接下来就是完成交换的代码创建一个函数来实现交换,需要的参数肯定要这两个元素的地址,同时还要这两个元素的大小

交换函数要完成一个字节一个字节的交换因为可以实现任意类型的交换。

void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{int i = 0;char tmp = 0;for (i = 0; i < size; i++){tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}

 比较函数cmp和主函数是由程序员B来完成的,冒泡函数是由程序员A完成的用于完成排序(任何类型)

 

int cmp_int(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{int i = 0;char tmp = 0;for (i = 0; i < size; i++){tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{int  i = 0;//趟数for (i = 0; i < num - 1; i++){int j = 0;//一趟内部比较的对数for (j = 0; j < num - 1 - i; j++){//假设需要升序cmp返回>0,交换if (cmp((char*)base+j*size, (char*)base+(j+1)*size)>0)//两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp{//交换Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}}

 swap函数中临时建立一个变量用于两个变量的交换

 同理想要实现按照什么来排序就i完成什么样的比较函数cmp,比如结构体变量按照名字,按照年龄

int cmp_int(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}//测试bubble_sort 排序整型数据
void test1()
{int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}

int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}void test3()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sizeof(struct Stu));bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

struct Stu
{char name[20];int age;
};int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}测试bubble_sort 排序结构体数据
void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}

 如果想要进行降序排序的话,只需要将用户自行创建并封装的 cmp函数 中return的p1和p2交换即可

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

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

相关文章

python如何画奥运五环

绘制奥运五环主要涉及到Python中的turtle绘图库运用&#xff1a; 程序源代码为&#xff1a; import turtle turtle.width(10) turtle.color(black) turtle.circle(50) turtle.penup() turtle.goto(120,0) turtle.pendown() turtle.color(red) turtle.circle(50) turtle.penup()…

基于springboot酒店管理平台

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于酒店管理平台系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了酒店管理平台系统&#xff0c;它彻底改变了过…

坚持刷题|分发饼干

文章目录 题目思路代码实现实现总结主要步骤时间复杂度 扩展问题 Hello&#xff0c;大家好&#xff0c;我是阿月。坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天刷第一个贪心算法&#xff1a;分发饼干 题目 455.分发饼干 思路 要解决这个问题&#xff0c;可以使用…

Autodesk Maya 2025 mac玛雅三维动画特效软件

Autodesk Maya 2025 for Mac是一款功能强大、操作简便的三维动画软件&#xff0c;适用于电影、电视、游戏、建筑、工业设计、虚拟现实和动画等领域。无论是专业设计师还是初学者&#xff0c;都可以通过Maya 2025实现自己的创意和想法&#xff0c;创作出高质量的三维作品。 软件…

多尺度变换(Multidimensional Scaling ,MDS)详解

一、基本思想 MDS&#xff08;Multidimensional Scaling &#xff0c;MDS多维尺度变换&#xff09;是一种经典的降维算法&#xff0c;其基本思想是通过保持数据点之间的距离关系&#xff0c;将高维数据映射到低维空间中。 具体来说&#xff0c;MDS算法的基本步骤如下&#xff…

栈————顺序栈和链式栈

目录 栈 顺序栈 1、初始化顺序栈 2、判栈空 3、进栈 4、出栈 5、读栈顶元素 6、遍历 链式栈 1、初始化链式栈 2、断链式栈是否为空判 3、入栈(插入) ​编辑​编辑 4、出栈(删除) 5、读取栈顶元素 6、输出链式栈中各个节点的值&#xff08;遍历&#xff09; 栈 …

Express.js项目实战(1)—— 我的藏书馆

首先新建文件夹——myLibrary 在vscode中点击文件>点击 Duplicate Workspace(以工作区的方式打开文件夹myLibrary) 点击duplicate Workspace&#xff08;打开工作区&#xff09; 之后&#xff0c;会出现以下界面 点击打开文件夹&#xff0c;选择新建的文件夹&#xff0c;会出…

Vscode + PlatformIO + Arduino 搭建EPS32开发环境

Vscode PlatformIO Arduino 搭建EPS32开发环境 文章目录 Vscode PlatformIO Arduino 搭建EPS32开发环境1. Vscode插件安装2. 使用PlatformIO新建工程3.工程文件的基本结构4.一个基本的测试用例Reference 1. Vscode插件安装 如何下载vscode这里不再赘述&#xff0c;完成基本…

超低代码智能音频开发来了:XMOS宣布与DSP Concepts建立合作伙伴关系推动AIoT音频应用

人工智能和芯片供应商 XMOS 宣布与嵌入式音频软件专家 DSP Concepts 建立合作伙伴关系。该合作协议将允许音频开发人员将 XMOS 的高度确定性、低延迟的 xcore.ai 平台与 DSP Concepts 的 Audio Weaver 软件结合起来。该软件使用户能够利用多核算力以图形方式设计和调试音频和语…

EXCEL VBA与CSV的结合使用,提高多个CSV的处

EXCEL VBA与CSV的结合使用&#xff0c;提高多CSV的处理 1、首先创建一个文本文件&#xff0c;然后将copy *.csv 合并.csv粘贴&#xff0c;并重命名后缀未bat 2、将bat和多个CSV放一个路径下 3、双击运行合并.bat&#xff0c;运行完成会后生成合并.csv 4、将csv用Excel打开&…

ALPHA开发板上PHY网络芯片LAN8720

一. 简介 正点原子的开发板 ALPHA开发板&#xff0c;有线网络硬件方案所使用的也是最常用的一种方案&#xff0c;IMX6ULL芯片内部是自带 MAC网络芯片的&#xff0c;所以&#xff0c;也就是采用 "SOC内部集成网络MAC外设 PHY网络芯片方案"。 ALPHA开发板使用的PHY网…

算法第三十七天-旋转链表

旋转链表 题目要求 解题思路 题意&#xff1a;将链表中每个元素向后移动k个位置&#xff0c;相当于把链表的后面k%len节点移到链表的最前面。 所以本题步骤为&#xff1a; 1.求出链表长度&#xff1b; 2.找出倒数第k1个节点 3.链表重整&#xff1a;将连败哦的倒数第k1个节点和…

讲解pwngdb的用法,以csapp的bomb lab phase_1为例

参考资料 Guide to Faster, Less Frustrating Debugging 什么情况下会使用gbd 需要逆向ELF文件时(掌握gdb的使用&#xff0c;是二进制安全的基本功)开发程序时&#xff0c;程序执行结果不符合预期 动态调试ELF文件可以使用另外一种方法&#xff1a;IDA的远程linux动态调试。个…

java引入下载的依赖包配置

maven中央仓库没有依赖包&#xff0c;但是个别网站有可以下载的依赖包下载依赖包并引入的配置在maven工程目录下建立lib目录&#xff0c;并将下载的jar包放入在pom.xml文件中配置本地依赖 <dependency><groupId>org.ejml</groupId><artifactId>…

LeetCode——动态规划(Java)

动态规划 简介[简单] 509. 斐波那契数[简单] 70. 爬楼梯[简单] 746. 使用最小花费爬楼梯[中等] 62. 不同路径[中等] 63. 不同路径 II[中等] 343. 整数拆分[中等] 96. 不同的二叉搜索树背包问题01背包[中等] 416. 分割等和子集[中等] 1049. 最后一块石头的重量 II[中等] 494. 目…

【游戏分析】FPS游戏狩猎百发百中

某某游戏狩猎玩法及其类似于FPS游戏 即3D射击 所以同样拥有 自动瞄准功能和爆头功能 想达到百发百中我们就要精准的计算出3D朝向值 读取人物坐标 遍历怪物,读取怪物坐标比较简单,不过多陈诉 朝向自然而然一定是我们和敌人的坐标计算出来的 那么怎么计算的呢&#xff1f; 我…

JAVA学习笔记21(访问修饰符)

1.访问修饰符 ​ *基本介绍 ​ java提供四种访问控制修饰符号&#xff0c;用于控制方法和属性(成员变量)的访问权限(范围) 1.公开级别&#xff1a;用public修饰&#xff0c;对外公开 2.受保护级别&#xff1a;用protected修饰&#xff0c;对子类和同一个包中的类公开 3.默…

基础知识系列

文章目录 一、linux passwd文件 一、linux passwd文件 linux中&#xff0c;用户名存放在/etc/passwd中&#xff0c;密码存放在/etc/shadow中。/etc/passwd只允许root用户可读可写&#xff0c;root组的用户可读&#xff0c;其他用户不可读不可写不可执行。/etc/shadow只允许roo…

手写简易操作系统(二十)--实现堆内存管理

前情提要 前面我们实现了 0x80 中断&#xff0c;并实现了两个中断调用&#xff0c;getpid 和 write&#xff0c;其中 write 还由于没有实现文件系统&#xff0c;是个残血版&#xff0c;这一节我们实现堆内存管理。 一、arena 在计算机科学中&#xff0c;“arena” 内存管理通…

DC-9靶场

一.环境搭建 1.下载地址 靶机下载地址&#xff1a;https://download.vulnhub.com/dc/DC-9.zip 2.虚拟机配置 设置虚拟机为nat&#xff0c;遇到错误点重试和是 开启虚拟机如下图所示 二.开始渗透 1. 信息收集 查找靶机的ip地址 arp-scan -l 发现靶机的ip地址为192.168.11…