🎇作者:小树苗渴望变成参天大树
🎉作者宣言:认真写好每一篇博客
🎊作者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,原因是两个数只需要进行比较,内容不会发生变化,防止使用者自己写比较函数的时候修改了你们的数据内容。
🎀四、总结
这篇博客设计的知识点很多,有一个环节不理解的话,就不太容易把这个思路顺清楚,所以还希望读者可以自己下来把这块硬骨头啃下来,好好消化一下,今天博主就先分享到这里了,我们下期再见。