【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看

news2025/1/12 12:11:21

🎇作者:小树苗渴望变成参天大树
🎉作者宣言:认真写好每一篇博客
🎊作者gitee:link
在这里插入图片描述如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

qsort

  • 🧨 前言
  • ✨一、什么是回调函数?
  • 二、qsort函数
    • 💖2.1冒泡排序的思想
    • 💨2.2qsort函数原型
    • 💢2.3qsort函数的整型实例
    • 💥2.4qsort函数的结构体类型实例
      • 2.4.1对结构体年龄进行排序
      • 2.4.2对结构体姓名进行排序
    • 💤2.5qsort函数的浮点型实例
  • 🎄三、模拟实现qsort函数
    • 💦3.1比较函数的写法
    • ❤️‍🩹3.2交换函数的写法
    • 💞3.3完整模拟的代码
  • 🎀四、总结


🧨 前言

各位友友们,一日不见,如隔三秋,我们又见面了,我又可以给你们分享新的知识了,今天我们讲的小的知识点是回调函数,大的知识点是怎么模拟是实现qsort函数,并且里面的细节。话不多说,我们开始进入正文。

✨一、什么是回调函数?

我们先来看一下它的概念:

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

很显然你们还是不太明白,好我们用例子来解释一下。

我们在上一篇博客种写过计算器这个代码,我们第一种是使用case语句写的
在这里插入图片描述
我们看到每个case语句里面有许多重复的语句,那我们能不能把这些相同的代码包装成一个函数呢??答案是可以的。我们写一个cacl()函数

void cacl()
{
	 printf( "输入操作数:" );
     scanf( "%d %d", &x, &y);
     ret = add(x, y);
     printf( "ret = %d\n", ret);
}
case 1:
	cacl();
	break;

我们这样来包装函数,每个case语句来调用这个函数,但是这样的话就写死了,每个case只能实现加法功能,那我们怎么修改呢??这个时候就要使用回调函数,我们把每个功能的函数传给cacl函数不就行了,用函数指针接收就可以了.

void cacl(int(*p)(int,int))
{
	 printf( "输入操作数:" );
     scanf( "%d %d", &x, &y);
     ret = p(x, y);
     printf( "ret = %d\n", ret);
}
case 1:
	cacl(add);
	break;

这样是不是就可以是实现对应的功能了,不会出现写死的情况。而且add的参数是由cacl内部去自动实现传参的,和接下来的qsort函数有关系,我们先不急。那回调函数和qsort函数又什么联系呢??qsort函数又是干什么呢?跟随我的脚步来看。

二、qsort函数

💖2.1冒泡排序的思想

qsort是一个库函数,用来实现任意数组的排序,我们先来看看我们比较熟悉的一种排序算法–冒泡排序
在这里插入图片描述
在这里插入图片描述

有没有发现一个问题,我们这个冒泡排序只能实现整型的排序,把想要排的类型给写死了,我们不能排字符型,结构体类型,浮点型等等。所以有的牛人就写了一个可以排任意类型数的一个库函数叫做qsort,让我们具体来看看这个函数是怎么使用的


💨2.2qsort函数原型

在这里插入图片描述
在这里插入图片描述
我们介绍了qsort里面每一个参数代表着什么,在补充一个小的知识点我们在函数传参的时候,有一个万能的接收类型,那就是空类型,这个类型可以接收任何类型的参数,但是有一个缺点,不能直接进行解引用或者++操作,需要先转换成其他类型才可以操作
在这里插入图片描述


我们知道了前三个参数具体是什么了,那第四个参数呢??他是一个函数指针,指向一个函数,该函数是一个比较函数,这个函数是需要我们使用者自己写的,因为不管是什么排序,都少不了元素的比较,但是每个元素的类型牛人一开始是不知道的,所以把涉及元素的比较想包装成一个函数让使用者自己写,qsort里面的算法思想牛人已经写死,因为和类型的不同没有关系。那我们这个比较函数我们自己应该怎么去写呢??我们来看看这个函数的原型

在这里插入图片描述
我们看到两个参数是指针,所以qsort内部传给这个函数应该是两个比较元素的地址,我先把代码写出来,以整型为例来分析:

int cmp_int(const void* e1, const void* e2)//整形排序
{
	return  *(int*)e1 - *(int*)e2;
}

一.我们把两个待比较元素的地址传过来,用void*类型接收,我们想要解引用必须强制类型转换我们需要的类型,这个需要的类型我们使用者肯定发是知道的所以先 (int * )e1进行强制类型转换, 在 *(int * )e1进行解引用得到数据,相减就能比较出谁大谁小。

在这里插入图片描述

二.我们来思考,如果返回大于0的数据,说明e1要大于e2,我们来回想刚才介绍的冒泡排序,如果前一个数大于后一个数就交换,那我们是不是就可以说如果我们判断的结果(即为比较函数返回来的结果)大于0,进行交换的话就是排升序,则反之。我们可以先把我们刚才写的冒泡排序当成qsort库函数,假设qsort内部就是冒泡排序的思想实现的,只是把比较两个元素的大小,单独写成了一个函数,根据回调函数的特点,在qsort函数里面调用这个比较函数。

💢2.3qsort函数的整型实例

我们具体来看看怎么使用:

int int_cmp(const void * p1, const void * p2)
{
  return (*( int *)p1 - *(int *) p2);
}
void test1()
{
	 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");
}
int main()
{
   test1();
    return 0;
}

在这里插入图片描述
我们来看结果是怎么样的通过这个例子,我们再来讲结构体数组和浮点型来进行一下排序,

💥2.4qsort函数的结构体类型实例

struct Stu
{
	char name[20];
	int age;
};
void test2()
{
	struct Stu s[3] = { {"zhangsan",20},{"lisi",10},{"wangwu",30} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_age);//对年龄
	qsort(s, sz, sizeof(s[0]), cmp_name);//对名字
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", s[i]);
	}
	}

我们看到对于结构体我们这个结构体类型有两个元素,所以我们可以对名字进行排序,也可以对年龄进行排序

2.4.1对结构体年龄进行排序

我们需要自己写比较函数

int cmp_age(const void* e1, const void* e2)//对年龄的比较
{
	return  ((struct Stu*)e1)->age -((struct Stu*)e2)->age;//数字的比较
}

->这个就是访问到结构体里面的元素,也可以写成这样
return ((struct Stu)e1).age- ((struct Stu)e2).age;
我们来看运行结果:应该是10,20,30
在这里插入图片描述
我们可以看到按照年龄的升序进行排序的。

2.4.2对结构体姓名进行排序

这个比较函数还是我们自己来写

int cmp_name(const void* e1, const void* e2)//对名字的比较
{
	return  strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);//字符串的比较
}

这里我们注意这是字符串比较,我们必须使用strcmp进行比较,那strcmp实际是按照对应位进行比较,第一次遇到不同的就比较对应的字符,来表示字符串的大小。这里牛人设计的非常巧妙,把strcmp的返回值和比较函数的返回值一一对应起来了。

我们来看运行结果:应该是:lisi,wangwu,zhangsan
在这里插入图片描述
名字也按照升序的方式进行排序了

💤2.5qsort函数的浮点型实例

int cmp_float(const void*e1, const void*e2)//浮点型排序
{
//第一种写法
	return ((int)(*(float*)e1 - *(float*)e2));//因为返回是int,要强制类型转换;
	//第二种写法
	if (*(float*)e1 == *(float*)e2)//解引用的类型要改成float
		return 0;
	else if (*(float*)e1 > *(float*)e2)
		return 1;
	else
		return -1;
}
void test3()
{
	float f[] = { 10.0,9.0,8.0,7.0,6.0,5.0,4.0};
	int sz = sizeof(f) / sizeof(f[0]);
	qsort(f, sz, sizeof(f[0]), cmp_float);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%f ",f[i]);
	}
}

因为是浮点型的比较,计算出来也为浮点型,所以要强制类型转换成整型在返回出来,我们来看看看运行结果:
在这里插入图片描述
我们可以看到也是可以实现的,那我们看到这里有没有觉得牛人很厉害,没关系,我会把你变得更牛人一样的厉害,接下来我就讲怎么自己模拟是实现一个qsort函数。

🎄三、模拟实现qsort函数

我们知道,不管是哪一种排序算法,都是将一些乱序的数排成一组有序的数,那有这么多的排序算法的原因就是每种算法的效率不一样,牛人是使用快排的算法思想,但我们今天不使用快排的算法来模拟是实现,因为还要花好长时间讲快排的思想,所以我们今天就使用冒泡排序的思想来是实现这个函数功能。
我们在文章开头就介绍了冒泡排序,,我们来看图片描述:
在这里插入图片描述

💦3.1比较函数的写法

那我们怎么实现比较函数的传参呢??

首先我们传过去的是两个元素的地址,我们通过指针来往后面走来获取每个元素地址即可。
在这里插入图片描述我们能根据这个图的解释可以把比较函数的参数写成这样:

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

我们类比不管什么类型的参数,这个传参的方式都可以得到两个相邻元素的地址。接下来我们来写交换函数。

❤️‍🩹3.2交换函数的写法

在这里插入图片描述
根据这个图我们来写出下面的代码:

void _swap(void *e1, void *ee2, int width)
{
    int i = 0;
    for (i = 0; i< width; i++)
   {
        char tmp = *((char *)e1 + i);
       *(( char *)e1 + i) = *((char *) e2 + i);
       *(( char *)e2 + i) = tmp;
   }
}
//优化后的代码
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

我们的交换函数就写好了。

💞3.3完整模拟的代码

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//实现bubble_sort函数的程序员,他是否知道未来排序的数据类型-不知道
//那程序员也不知道待比较的两个元素的类型,所以函数指针写成void*
void bubble_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2))//与上面类似,目的实现上面的函数原理
{
	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);
			}
		}
	}
}

比较函数的内部还是和之前的写法一模一样的

到此位置我们的my_sort函数就有和qsort一样的功能,可以实现任意类型的排序,读者可以自己下来测试自己模拟的这个my_sort函数,只需要电泳我这个函数就行了,接下来就演示一组:

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
void my_sort(void* base, int sz, int width, int(*cmp)(void* e1, void* e2))
{
	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);
			}
		}
	}
}
struct Stu
{
	char name[20];
	int age;
};
int cmp_age(const void* e1, const void* e2)//对年龄的比较
{
	return  ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//数字的比较
}
void test4()
{
	struct Stu s[3] = { {"zhangsan",20},{"lisi",10},{"wangwu",30} };
	int sz = sizeof(s) / sizeof(s[0]);
	my_sort(s, sz, sizeof(s[0]), cmp_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d \n", s[i].name, s[i].age);
	}
}
int main()
{
	test4();
	return 0;
}

在这里插入图片描述
我们是对年龄进行排序的,发现结果和我们i想要的一样。

友情解释:在比较函数里面我们的参数加了const,原因是两个数只需要进行比较,内容不会发生变化,防止使用者自己写比较函数的时候修改了你们的数据内容。

🎀四、总结

这篇博客设计的知识点很多,有一个环节不理解的话,就不太容易把这个思路顺清楚,所以还希望读者可以自己下来把这块硬骨头啃下来,好好消化一下,今天博主就先分享到这里了,我们下期再见。

在这里插入图片描述

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

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

相关文章

报表生成器FastReport.Net常见问题解答来了 | 联合厂商作答

FastReport .Net是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案&#xff0c;使用FastReport .NET可以创建独立于应用程序的.NET报表&#xff0c;同时FastReport .Net支持中文、英语等14种语言&#xff0c;可以让你的产品保证真正的国际性。FastReport.NET官方版下…

智云通CRM:如何应对来自竞争对手的阻力?

当销售表明来意后&#xff0c;竞争性客户最常见的回答往往是&#xff1a;“我们和现在的供应商合作得很好。”销售应当牢记&#xff0c;此时我们的目标不是将竞争对手取而代之&#xff0c;而是要努力成为其配角&#xff0c;找机会发现竞争对手能力不足的方面。鉴于此&#xff0…

2023年春节跨年烟花网页特效

粉丝朋友们大家好&#xff0c;我是你们的 csdn的博主&#xff1a;lqj_本人 哔哩哔哩&#xff1a;小淼前端 另外&#xff0c;大家也可以关注我的哔哩哔哩账号&#xff0c;我会不定时的发布一些有关于全栈云开发以及前端开发的详解视频源码 1.微信小程序腾讯云开发之学生端收集数…

搭建VMware ESXi6.7(带图解)

目录 VMware ESXi介绍 准备文件 安装过程 VMware ESXi介绍 VMware ESXi是什么系统&#xff1f; VMware ESXi是可直接安装在物理服务器上的强大的裸机管理系统&#xff0c;不需安装其他操作系统&#xff0c;是VMware服务器虚拟化的基础。通过直接访问并控制底层资源&#x…

操作系统从入门到入土(一)之计算机系统概述

文章目录操作系统的基本概念1.概念2.功能和目标3.特征操作系统的发展操作系统运行环境1.处理器运行环境2.中断和异常3.系统调用系统结构操作系统的基本概念 1.概念 操作系统&#xff08;Operating System&#xff0c; OS&#xff09;是指控制和管理整个计算机系统的硬件和软件…

【C语言】字符串练习,压缩字符串,提取奇偶位(每日小细节015)

前言&#xff1a; 欢迎打开这篇博客&#xff0c;从今天开始&#xff0c;每天和大家分享一个C语言小细节&#xff0c;不久之后还会追加C 一些常常被忽视的小细节和思想统一的编程题目是这个专栏的核心哦 虽然简单但千万别在细节处失分&#xff01;&#xff01;&#xff01;&…

《高效能人士的七个习惯》

专注做有意义的事情。《高效能人士的七个习惯》作者史蒂芬科维&#xff0c;这是一本难得的好书&#xff0c;如果你没有的读过&#xff0c;我推荐你一定要读一下。最近在豆瓣看到一句话说&#xff1a;一定要读经典书籍&#xff0c;因为我们在生活中很难遇到伟大的人物&#xff0…

V2V网络灵敏度分析(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 灵敏度分析是研究与分析一个系统&#xff08;或模型&#xff09;的状态或输出变化对系统参数或周围条件变化的敏感程度的方法。…

Axure绘制登录功能

上一篇文章为大家介绍了一下登录功能的设计思路和相关的流程图&#xff0c;本篇主要带大家从0到1绘制一下B端产品的登录功能如何实现 一、功能解析 本篇主要是针对内部员工使用的B端产品&#xff0c;主要功能如下&#xff1a; ①手机号密码登录 ②忘记密码 ③记住密码 第…

从0到1完成一个Vue后台管理项目(二、使用element-ui)

从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 1.全局引入 下载element-ui cnpm i element-ui -S main.js里引入 这里需要注意的是&#xff0c;我们引入的顺序不能错&#xff0c;一定要按照我这个箭头的顺序来 使用 然后我们按照文档正常使用即可 elem…

Go第 8 章:排序和查找

Go第 8 章&#xff1a;排序和查找 8.1 排序的基本介绍 8.2 冒泡排序的思路分析 8.3 冒泡排序实现 8.4 课后练习 要求同学们能够&#xff0c;不看老师的代码&#xff0c;可以默写冒泡排序法(笔试题) 8.5 查找 2) 请对一个有序数组进行二分查找 {1,8, 10, 89, 1000, 1234} &am…

C语言之复合类型下卷(十九)(自然法则)(2023)

上一篇: C语言之复合类型上卷&#xff08;十八&#xff09;&#xff08;阴阳两极&#xff09; 逐梦编程&#xff0c;让中华屹立世界之巅。 简单的事情重复做,重复的事情用心做,用心的事情坚持做&#xff1b; 文章目录前言一、结构体指针二、结构体嵌套指针三、const修饰的结构…

qsort函数详解以及模拟实现

qsort函数详解以及模拟实现一.qsort函数是什么二.具体的使用1.参数4&#xff08;参数3在模拟实现时解释&#xff09;2.例子1.排序整形2.排序结构体三.模拟实现qsort1.参数32.模拟排序1.排整形2.排结构体一.qsort函数是什么 qsort全称为quick_sort&#xff08;快速排序&#xff…

Tomcat的基本使用

1, Tomcat 1.1 简介 1.1.1 什么是Web服务器 Web服务器是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让Web开发更加便捷。主要功能是"提供网上信息浏览服务"。 Web服…

C语言rewind()函数:将文件指针重新指向文件开头

rewind()函数用于将文件指针重新指向文件的开头&#xff0c;同时清除和文件流相关的错误和eof标记&#xff0c;相当于调用fseek(stream, 0, SEEK_SET)&#xff0c;其原型如下&#xff1a; void rewind(FILE * stream); 【参数】stream为已打开文件的指针。 注意&#xff1a;准…

深入理解Pytorch中的分布式训练

作者&#xff1a;台运鹏 (正在寻找internship...)主页&#xff1a;https://yunpengtai.top鉴于网上此类教程有不少模糊不清&#xff0c;对原理不得其法&#xff0c;代码也难跑通&#xff0c;故而花了几天细究了一下相关原理和实现&#xff0c;欢迎批评指正&#xff01;关于此部…

【Python • 字符串】巧用python字符串切片

文章目录前言字符串切片常用用法理解一个字符串逆向下标的字符串字符串切片截取下标a到b的字符串取下标a以后的所有字符串取下标a以前的所有字符串间隔n个字符取字符串字符串逆序输出从下标a的字符开始逆序取字符串逆序输出从b到a的字符串逆序间隔一个字符串输出总结前言 在py…

站酷基于服务网格ASM的生产实践

作者&#xff1a;服务网格ASM 背景介绍 站酷&#xff08;ZCOOL&#xff09;2006 年 8 月创立于北京&#xff0c;深耕设计领域多年&#xff0c;聚集了 1500 万设计师、摄影师、插画师、艺术家、创意人&#xff0c;在设计创意群体中具有一定的影响力与号召力。站酷在创立之初&am…

企业数字化转型“核心方法论”

一、什么是数字化转型&#xff1f;数字化转型是近年来&#xff0c;很多企业老生常谈的话题。那么听了这么多数字化转型的故事&#xff0c;你对其真正了解多少呢&#xff1f;下面织信就数字化转型的背景、以及多个示例的讲解&#xff0c;带你深入理解“数字化转型”这一概念。&a…

构建自组织团队,让敏捷管理更好地落地

敏捷开发是以用户的需求为核心&#xff0c;通过不断迭代、小步快跑、循序渐进的方法进行软件产品的研发&#xff0c;在迭代研发过程中的产品都需要经过测试&#xff0c;具备可视化、可集成和可运行使用的特征。 在团队方面&#xff0c;敏捷开发倡导团队协作&#xff0c;强调个…