【C语言】回调函数(qsort)与模拟实现

news2024/11/23 17:22:06

何思何虑,居心当如止水;勿取勿忘,为学当如流水。— 出自《格言联璧·学问类》
解释:无思无虑,心境应当平静如水;不求冒进也不忘记,学业当如流水一般永无止境。

在这里插入图片描述


    这篇博客我们将会理解回调函数这个概念,以及借用qsort帮助理解,并且最终用qsort的思路来实现冒泡排序。
    在这里插入图片描述

    目录

    • 回调函数🍀
    • qsort函数🤢
    • 用冒泡实现qsort💥
    • 总结😈

    回调函数🍀

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

    如果这样看你还是不太理解,我们可以举出一个简单的例子。

    void test2()
    {
    	printf("haha\n");
    
    }
    void test1(void (*p) ())
    {
    	p();
    }
    int main()
    {
    	test1(test2);
    
    	return 0;
    }
    

    这这段代码中:
    test1的实参是test2,而test1的形参也对应函数指针类型,

    {
    	p();
    }
    
    

    而p()就是借助p的存储的test2的地址调用test2,就相当于test2()

    归纳一下:
    在这个过程中谁是回调函数?
    答:test2,因为test2并不是直接调用,而是通过将test2的地址传递给test1的实参,然后通过形参的地址调用。
    现在我们已经理解了什么是回调函数。

    现在我们可以用一个简单的计算器来运用一下:

    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 menu()
    {
    
    	printf("**************************\n");
    	printf("****  1.add   2.sub   ****\n");
    	printf("****  3.mul   4.div   ****\n");
    	printf("****  0.exit          ****\n");
    	printf("**************************\n");
    
    }
    
    void  cal(int (*p) (int, int))
    {
    	int x = 0;
    	int y = 0;
    	scanf("%d %d", &x, &y);
    	printf("%d\n", p(x, y));
    }
    int main()
    {
    	int input = 0;
    	do
    	{
    		menu();
    		printf("请输入");
    		scanf("%d", &input);
    		switch (input)
    		{
    		case 1:
    			cal(Add);
    			break;
    		case 2:
    			cal(Sub);
    			break; 
    		case 3:
    			cal(Mul);
    			break;
    		case 4:
    			cal(Div);
    			break;
    		case 0:
    			printf("退出计算机\n");
    			break;
    		default:
    			printf("输入错误请重新输入\n");
    		}
    
    
    
    	} while (input);
    
    }
    

    这就是用回调函数的一个运用方法,虽然没有转移表那么简洁,但是也是一种方法。

    qsort函数🤢

    我们先来看看这个函数的介绍。

    参数和返回值:
    在这里插入图片描述

    这个函数的参数应该是算比较多的了。下面是参数的具体解释:

    在这里插入图片描述

    这个函数的功能:
    qsort函数是C语言标准库中的一个排序函数,用于对数组进行快速排序(其实就是根据快排来实现的)。它的基本功能是将给定的数组(当然也包括结构体数组)按照指定的比较函数进行排序。

    头文件:
    在这里插入图片描述
    那么是根据什么来判断要排序呢?

    在这里插入图片描述
    实际上跟strcmp有点相似,比较两个元素的大小,根据回调函数(compar就是一个函数名,当qsort的参数里包含另一个函数的名字,就与我们说的回调函数相符)的返回值来判断是否需要排序。

    我们现在就来用用看:

    int cmp(const void* e1, const void* e2)
    {
    	//if (*(int*)e1 - *(int*)e2 > 0)
    	//	return 1;
    	//else if (*(int*)e1 - *(int*)e2 == 0)
    	//	return 0;
    	//else
    	//	return -1;
    	return (*(int*)e1 - *(int*)e2);
    		
    }
    int main()
    {
    	int arr[7] = { 8, 7, 4, 3, 6, 1, 2 };
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	qsort(arr, sz, sizeof(arr[0]), cmp);
    	for (int i = 0; i < 7; i++)
    	{
    		printf("%d ",arr[i]);
    	}
    
    
    	return 0;
    }
    

    我们简单的排序一个整型数组,在这里插入图片描述

    qsort这个函数能排序任何类型的数组的原因就是在于,
    cmp这个回调函数是由我们自己来实现的。
    在这里插入图片描述
    介绍中也强调了这一点。那为什么是void * 类型的参数呢?
    因为void *类型能接收其他类型,所以才使得我们能排序所有类型的数组,
    但是cmp函数的实现也需要根据不同的类型进行不同的改动。

    接下来我们看看结构体数组元素的排序。

    typedef struct student
    {
    	char name[20];
    	int age;
    
    }student;
    void cmp(const void* e1, const void* e2)
    {
    	if ((*(student*)e1).age - (*(student*)e2).age > 0)
    	{
    		return 1;
    	}
    	else if ((*(student*)e1).age - (*(student*)e2).age == 0)
    	{
    		return 0;
    	}
    	else
    		return -1;
    		//return ((*(student*)e1).age - (*(student*)e2).age);简写
    }
    
    //void cmp(const void* e1, const void* e2)
    //{
    //	if (strcmp((*(student*)e1).name , (*(student*)e2).name)> 0 )
    //	{
    //		return 1;
    //	}
    //	else if (strcmp((*(student*)e1).name, (*(student*)e2).name) == 0)
    //	{
    //		return 0;
    //	}
    //	else
    //		return -1;
    //}
    int main()
    {
    	student s1[3] = { {"zhangxiaofan",20} ,{"shihao",18} ,{"fangyuan",25} };
    	qsort(s1, sizeof(s1) / sizeof(s1[0]), sizeof(s1[0]), cmp);
    	for (int i = 0; i < 3; i++)
    	{
    		printf(" %s is %d\n",s1[i].name, s1[i].age);
    	}
    	return 0;
    }
    

    分析(排序age)

    • 在排序结构体数组成员时,首先必须要有结构体数组,然后我们再进行初始化。
    • 当我们要排序的是int age;时,我们所创建的回调函数就与整型数组的排序有区别。
    • 区别首先在于强制类型转换,整型数组的元素类型是整型所以可以直接(int *),但是结构体数组的每个元素是结构体(所以用到student *),结构体的成员(int age)才是我们要排序的目标。
    • 在整型数组时我们直接讲e1,e2进行强制类型转换解引用然后相减,是因为e1,e2就直接表示的是整型元素,而结构体数组并不能和整型数组一样,因为结构体数组强制类型转换再解引用得到的是这个结构体,结构体怎么能和结构体相减呢?所以我们需要用到.去找到int age
      -在这里插入图片描述

    分析(排序名字)
    排序名字与排序年龄其实差不了太多,最重要的就是需要用到函数strcmp来比较字符的ASCII码,strcmp((*(student*)e1).name , (*(student*)e2).name)这样来判断就行了。

    在这里插入图片描述

    我们已经学习完qsort函数了,我们知道qsort函数其实是用快速排序实现的,虽然我们没有学过快速排序,但是我们知道冒泡排序。下面我们就来自己用冒泡排序来实现一下吧。

    用冒泡实现qsort💥

    首先先简单的回顾一下冒泡排序。

    void bubble_sort(int* arr, int sz)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < sz - 1; i++)
    	{
    		for (j = 0; j < sz - 1 - i; j++)
    		{
    			int tmp = 0;
    			if (arr[j] > arr[j + 1])
    			{
    				tmp = arr[j];
    				arr[j] = arr[j + 1];
    				arr[j + 1] = tmp;
    			}
    		}
    	}
    }
    int main()
    {
    	int arr[] = { 10, 9, 8, 7, 6 ,5, 4, 3, 2, 1 };
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	bubble_sort(arr, sz);
    	for (int i = 0; i < sz; i++)
    	{
    
    		printf("%d ", arr[i]);
    	}
    
    	return 0;
    }
    

    在这里插入图片描述
    然后我们思考一下到底该如何能够排序任意的类型的数组呢?我们必须有个思路。

    • 就像qsort函数的创作者一样,他并不知道我们要排序什么样类型的数组,但是呢?他通过qsort函数的参数能够解决这个问题。
    • 也就是说我们给出数组的地址,元素多少,一个元素的大小,还有回调函数就能够实现排序的功能,那我们也需要想办法通过元素的多少和大小来实现我们自己的能排序各种类型数组的冒泡排序。当然我们可以自己试试看。

    下面是参考

    int cmp(const void* e1, const void* e2)
    {
    
    	return *(int*)e1 - *(int*)e2;
    }
    void swap(char* e1,char * e2,  int width)
    {
    	char tmp = 0;
    	while (width--)
    	{
    		tmp = *e1;
    		*e1 = *e2;
    		*e2 = tmp;
    		e1++;
    		e2++;
    	}
    
    }
    void my_bubble_sort(void *base, int size, int width,
    	int (*p)(const void*, const void*))
    {
    
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < size - 1; i++)
    	{
    		for (j = 0; j < size - 1 - i; j++)
    		{
    			char tmp = 0;
    			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()
    {
    	int arr[] = { 10, 9, 8, 7, 6 ,5, 4, 3, 2, 1 };
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	my_bubble_sort(arr, sz,sizeof(arr[0]),cmp);
    	for (int i = 0; i < sz; i++)
    	{
    
    		printf("%d ", arr[i]);
    	}
    
    	return 0;
    }
    

    在这里插入图片描述

    如果想要排序逆序的数组,把 if 中的> 0 改成 < 0 就行了

    分析
    其实在自己实现的过程中最重要的就是my_bubble_sort这个函数,

    1. 首先我们要把参数写正确,

    void my_bubble_sort(void *base, int size, int width,
    	int (*p)(const void*, const void*))
    

    由于我们并不知道排序的类型所以我们将该数组的首地址放在void *,最后也要把函数指针写正确。

    1. 我们在冒泡中需要修改的就是if ()中的表达式,我们并不能直接判断大小,而是用cmp函数来判断。
      (char *) base+j *width,(char *)base+(j+1)*width
      我们先将base强转为char *类型,为什么呢?为什么不是int *,short *呢?
      我们知道内存中最小的单位是自己,而char *指针解引用只能访问一个字节,我并不需要知道具体是什么类型,我只需要知道数组一个元素的大小就行,通过(char *) base+j *width实际上找到了数组的一个元素width就是来表达大小的。
    2. swap函数
    swap((char*)base + j * width, (char*)base + (j + 1) * width,
    				width);
    

    我们需要传的参数其实就三个,元素1的地址,和元素2的地址,最后是元素的大小,为什么要这样做?

    void swap(char* e1,char * e2,  int width)
    {
    	char tmp = 0;
    	while (width--)
    	{
    		tmp = *e1;
    		*e1 = *e2;
    		*e2 = tmp;
    		e1++;
    		e2++;
    	}
    
    }`
    

    可以看到我们用char *来接收这两个地址,目的就是让字节让字节和字节交换,比如说一个结构体数组的一个元素的大小是20字节,那么我用字节为单位与另一个元素交换20次不就行了吗?这里的20不就相当于width吗?

    总结😈

    我们在这篇博客中从回调函数的概念到学会运用,再列举出qsort函数来帮助大家理解,并且也学会了这个强大的排序函数,最后我们还用冒泡排序来模拟出了一个可以排序任意类型数组的函数,可谓是收获颇多啊。
    完。
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

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

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

    相关文章

    长达 1.7 万字的 explain 关键字指南!

    当你的数据里只有几千几万&#xff0c;那么 SQL 优化并不会发挥太大价值&#xff0c;但当你的数据里去到了几百上千万&#xff0c;SQL 优化的价值就体现出来了&#xff01;因此稍微有些经验的同学都知道&#xff0c;怎么让 MySQL 查询语句又快又好是一件很重要的事情。要让 SQL…

    桥梁监测是做什么的?桥梁结构监测方案

    现代化大型桥梁是交通主干道的重要节点&#xff0c;对交通运输发展具有重大影响。然而&#xff0c;桥梁在长期使用过程中容易受到各种因素的影响&#xff0c;如自然灾害、车辆载荷、材料老化等&#xff0c;从而导致结构损伤和安全隐患。因此&#xff0c;对桥梁结构性能进行自动…

    Vue3+Vite+Pinia+Naive后台管理系统搭建之八:构建 login.vue 登录页

    前言 如果对 vue3 的语法不熟悉的&#xff0c;可以移步Vue3.0 基础入门&#xff0c;快速入门。 项目所需要的图片&#xff0c;icon图标&#xff08;推荐&#xff1a;阿里巴巴矢量图标库&#xff09;自行获取&#xff0c;命名一致就行。 1. 构建 src/components/CopyRight.vu…

    猿人学第一届刷题18

    1.第十八题 jsvmp - 猿人学 问题: 1.第一页请求正常能返回数据 2.第二页开始之后出现{"error": "Unexpected token/Validation failed"} 分析&#xff1a; 1.第二页开始&#xff0c;有带加密参数&#xff0c;直接重发请求无果&#xff0c;应该带了时间戳…

    优化产品知识库的 SEO 技巧

    在当今数字化的商业环境中&#xff0c;为产品知识库进行搜索引擎优化&#xff08;SEO&#xff09;是至关重要的。随着用户越来越倾向于通过搜索引擎获取信息&#xff0c;优化产品知识库可以帮助你的企业在竞争激烈的市场中脱颖而出。 通过改进SEO&#xff0c;你可以帮助用户找到…

    locust性能测试和分布式压测

    一、工具介绍 Locust是一个开源的Python性能测试工具&#xff0c;用于模拟大量并发用户访问网站、API等&#xff0c;以测试系统的性能和稳定性。它的主要特点包括&#xff1a; 1.简单易用&#xff1a;Locust基于Python编写&#xff0c;使用方便&#xff0c;学习曲线较低。 2…

    数据库基本操作--------MySQL 索引

    目录 一、MySQL 索引 1&#xff0e;索引的概念 2&#xff0e;索引的作用 3&#xff0e;创建索引的原则依据 4&#xff0e;索引的分类和创建 &#xff08;1&#xff09;普通索引 ●直接创建索引 &#xff08;2&#xff09;唯一索引 &#xff08;3&#xff09;主键索引 ●创…

    禁止浏览器自动填充密码功能,设置自动填充背景色。

    禁止浏览器自动填充密码功能&#xff0c;设置自动填充背景色 1、禁止浏览器自动填充密码功能2、设置自动填充背景色&#xff08;阴影效果&#xff09; 1、禁止浏览器自动填充密码功能 text设置autocomplete“off” password设置 autocomplete“new-password” 两个一起设置&am…

    雪花算法生成分布式ID源码分析及低频场景下全是偶数的解决办法

    目录 雪花算法原理介绍 雪花算法源码分析 低频场景下都是偶数的原因 解决雪花算法的偶数问题 1、切换毫秒时使用随机数 2、抖动上限值加抖动序列号 雪花算法原理介绍 雪花算法(snowflake)最早是twitter内部使用的分布式下的唯一id生成算法&#xff0c;在2014年开源&…

    【iOS】消息传递与消息转发

    Objective-C是一门非常动态的语言&#xff0c;以至于确定调用哪个方法被推迟到了运行时&#xff0c;而非编译时。与之相反&#xff0c;C语言使用静态绑定&#xff0c;也就是说在编译期就能决定程序运行时所应该调用的函数&#xff0c;所以在C语言中&#xff0c; 如果某个函数没…

    MySQL优化 | 如何正确使用索引

    文章目录 一、简介1、索引的作用和优势2、索引的基本原理和数据结构 二、常见索引类型和适用场景1、B-Tree索引及其适用场景2、哈希索引及其适用场景 三、选择合适的索引策略1、 选择合适的列作为索引2、使用复合索引和最左前缀原则3、 覆盖索引的使用技巧 四、索引的创建和维护…

    IDEA项目报错随笔记录

    文章目录 1. 无效的源发行版: 172. java: 无法访问org.springframework.boot.SpringApplication3. java: 程序包org.junit.jupiter.api不存在4. SpringbootTest注解爆红5. maven命令安装本地jar包报错&#xff1a;[拒绝访问]5. maven命令安装本地jar包报错&#xff1a;Unknown …

    华为OD机试真题 Java 实现【矩阵元素的边界值】【2023 B卷 100分】,附详细解题思路

    目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明4、再输入5、再输出6、再说明 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多&#xff…

    IOS与Android APP开发的差异性

    iPhone和 Android是全球最流行的两种移动平台&#xff0c;有许多不同的开发者开发了应用程序&#xff0c;并将它们发布到市场上。虽然大多数开发者都使用了这两个平台&#xff0c;但您仍然需要了解它们的差异。 虽然 iOS和 Android两个平台都是基于 Linux&#xff0c;但它们却…

    流程提效80%!从3个维度搭建高效的数字化采购体系...

    采购是企业经营的一个核心环节&#xff0c;也是企业获取利润和市场资源的重要部门&#xff0c;对于企业订单交付尤为关键。特别是在装备制造行业&#xff0c;项目多、零件品类多、定制化高、订单交付周期短&#xff0c;边设计边采购边生产&#xff0c;企业采购负荷重&#xff0…

    读书笔记怎么写?心理学名著《乌合之众》读书笔记!

    世界上这么多人&#xff0c;看似每个人是一个独立的个体&#xff0c;但其实众生如蚁&#xff0c;彼此相互影响&#xff0c;形成了复杂而神秘的群体心态。 当我们融入群体时&#xff0c;个人的特质会被群体弱化&#xff0c;变得盲从、愚昧甚至暴力&#xff0c;这就是《乌合之众》…

    IP地址:超网监控

    随着组织的 IT 基础架构的扩展&#xff0c;新设备会不断添加以满足不断增长的网络需求。这就需要跨多个子网管理数百个 IP 地址&#xff0c;以确保每个新添加的设备都配置了唯一的 IP 以连接到网络。为了简化此过程&#xff0c;网络管理员依赖于网络超网的概念&#xff0c;也称…

    事务隔离级别是如何实现的

    事务隔离级别是如何实现的 数据库系统提供了以下 4 种事务隔离级别 读未提交&#xff1a;解决了回滚覆盖类型的更新丢失&#xff0c;但可能发生脏读现象(一个事务读取到了另一个事务修改但未提交的数据)&#xff0c;也就是可能读取到其他会话中未提交事务修改的数据。 已提交…

    解决报错:FUNC glfwErrorCallback GLX: Failed to create context: GLXBadFBConfig

    解决报错&#xff1a;FUNC glfwErrorCallback GLX: Failed to create context: GLXBadFBConfig 执行glxinfo | grep OpenGL命令查看系统中的OpenGL信息时&#xff0c;显示以下信息&#xff1a; 根据得到的信息可以看到 OpenGL core profile version string 为 4.5 说明显卡驱…

    基于web的考研信息交流平台/考研信息分享平台的设计与实现

    摘 要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;考研信息交流平台也不例外&#xff0c;但目前国内的有些平台仍然都使用人工管理&#xff0c;浏览网站人数越来越多&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时…