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

news/2024/11/8 23:11:18/

🎇作者:小树苗渴望变成参天大树
🎉作者宣言:认真写好每一篇博客
🎊作者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)//解引用的类型要改成floatreturn 0;else if (*(float*)e1 > *(float*)e2)return 1;elsereturn -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.ppmy.cn/news/9905.html

相关文章

(七)汇编语言——更灵活的定位内存地址的方法

目录 and和or ASCII码 [bxidata] SI和DI寄存器 [bxsi]和[bxdi] [bxsiidata]和[bxdiidata] 总结 例子&#xff08;双重循环的解决方案&#xff09; 我们知道&#xff0c;对于汇编来说&#xff0c;内存是极为重要的&#xff0c;所以&#xff0c;能精准且巧妙地定位内存地…

node.js快速入门指南

Node.js迅速蹿红&#xff0c;衍生了一个强大的开源社区、支持企业&#xff0c;甚至还拥有属于自己的技术大会。我把这种成功归结于它的简介&#xff0c;高校&#xff0c;同时提高了编程生产力。 Node.js 的前置知识很多&#xff0c;例如以下知识 JavaScriptES6Ajax 还不会的…

Compose跨平台第一弹:体验Compose for Desktop

前言 Compose是Android官方提供的声明式UI开发框架&#xff0c;而Compose Multiplatform是由JetBrains 维护的&#xff0c;对于Android开发来说&#xff0c;个人认为学习Jetpack Compose是必须的&#xff0c;因为它会成为Android主流的开发模式&#xff0c;而compose-jb作为一…

寒假本科创新——机器学习(二)

绪论1.3归纳偏好 一般原则&#xff1a;奥卡姆剃刀 什么样的算法比较好&#xff1f;1.4NFL定理 NFL定理的前提&#xff1a; NFL定理的寓意&#xff1a;1.3归纳偏好 归纳偏好&#xff08;lnductive Bias&#xff09;&#xff1a; 机器学习算法在学习过程中对某种类型假设的偏好…

JAVA并发编程工具篇--1.1理解Future获取线程执行结果

背景&#xff1a;在并发编程中&#xff0c;我们可以使用Future来获取子线程执行的结果&#xff0c;然后在主线程一起进行业务处理&#xff1b; 那么Future是如何来工作的&#xff1b; 1 使用&#xff1a; demo1&#xff1a;使用Future每次都阻塞获取任务的执行结果&#xff1a…

android 换肤框架搭建及使用 (3 完结篇)

本系列计划3篇: Android 换肤之资源(Resources)加载(一)setContentView() / LayoutInflater源码分析(二)换肤框架搭建(三) — 本篇 tips: 本篇只说实现思路,以及使用,具体细节请下载代码查看! 本篇实现效果: fragment换肤recyclerView换肤自定义view属性换肤打开打开打开动…

Java Bean Validation

JSR303 是一套JavaBean参数校验的标准&#xff0c;它定义了很多常用的校验注解&#xff0c;我们可以直接将这些注解加在我们JavaBean的属性上面&#xff0c;就可以在需要校验的时候进行校验了。校验框架注解如下&#xff1a; 注解解释Null被注释的元素必须为nullNotNull被注释…

成为有钱人的终极秘诀:做到这7步,你也可以成为富人!

经常有人问&#xff1a;互联网有什么快速赚钱的方法?大多数人内心浮躁&#xff0c;总想以最快的方式搞到钱。因为浮躁&#xff0c;所以沉不下心来去搞钱。做一个项目赚不到钱&#xff0c;然后又开始找项目&#xff0c;换项目&#xff0c;做项目&#xff0c;一直恶性循环中。最…