一.qsort函数的简介:
在qsort函数中第四个参数 int (*compar)(const void*, const void*),为什么是一个函数指针呢?
因为调用这个函数时,函数开发者并不知道你要调用这个函数来比较什么类型(整形,字符串,结构体元素等等)的大小,所以利用函数指针,当函数使用者想来比较什么类型时就可以分装另一个函数,当调用时将另一个函数的地址(即函数名)传给qsort函数即可。
二.使用qsort函数:
1.调用qsort函数排序整型的数据:
在这里直接解引用p1和p2来比较大小是行不通的因为函数的两个参数是void*,而void*这个类型的指针是无具体类型的指针这种类型的指针不能直接解引用也不能直接进行加减整数的运算。
而如何解决呢?
在这里因为我们知道我们自己是要比较整形类型的大小所以我们可以直接进行强制转换:
把握住上面的细节后就可以直接进行调用了:
可以将cmp_int函数里面的比较简化一下,让这两个值直接做差,>0返回>0的数,=0就返回0,<0就返回<0的数。
上述代码是升序,如何改成降序呢?
直接将p1与p2的位置直接调换即可。让逻辑变反。就可以看作冒泡排序中如果arr[j]>arr[j+1]进行交换(即升序)变成如果arr[j]<arr[j+1]进行交换(即降序)。
2.调用qsort函数排序结构体的数据:
1.按照结构体中字符串的大小排序:
这里和整形比较一样因为是void*类型无法解引用所以要强制转换成结构体指针
当强制转换成结构体指针时就可以解引用访问结构体中的数据了。
这里通过调用字符串函数strcmp函数来比较两个字符串大小,当strcmp(str1,str2)中str1>str2时返回>0的数,相等时返回0,str1<str2时返回<0的数。跟qsortd的第四个参数里的函数返回值一样
strcmp是按照对应着字符串中的字符的ASSCII码值比较的而不是比较两个字符串的长度。
排序前的调试:
排序后的调试:
由上述代码可知排完序之后zhangsan最大其次wangwu最小的是lisi。
2.按照结构体中整形的大小排序:
排序前的调试:
排序后的调试:
三.利用qsort函数模拟实现一个通用的冒泡排序:
1.排列整形的冒泡排序:
2.改造bubble_sort函数里的参数:
改造前因为知道排序的是整形数组里的数,所以传参时可直接传递数组首地址和数组长 度
改造后:
因为不知道使用者用来排序怎样的数据所以要知道更多的参数:
参数1 void* base不管你用来排什么都用void*来接受你的起始地址(元素)
参数2 size_t sz表示你base指向待排数组的元素个数
参数3 size_t width因为不知道你要排什么元素但知道元素个数和从哪开始,所以写出这个参数用来知道你一个元素的宽度(大小)
参数4 (*cmp)(const void*p1,const void*p2)因为不同类型用来排序的手段不一样,所以定义一个函数指针参数来排序不同的类型的数据。这个比较函数里的两个参数const void*p1,const void*p2而因为不知道使用者传给函数什么参数所以用从const void*来接受不同类型的数据
3.改造bubble_sort函数的内部:
1.第一步改造if语句里的条件:
改造前:
改造后:
因为不知道要排什么样的类型的数据所以运用cmp比较函数来返回值,而cmp函数里的参数一个是(char*)base+j*width,一个是(char*)base+(j+1)*width,怎么理解呢?
当排序的是整形数组时是arr[j]与arr[j+1]但现在不知道你是什么类型的数据 但是我知道你元素的宽度(大小),又因为void*指针不能进行指针运算所以把其强制转换为char*类型的指针(一个字节)更好用来指向不同类型的数据与元素。
2.改造if语句里的内容:
改造前:
改造后:
这里分装一个Swap交换函数当cmp函数返回值>0满足条件时就调用Swap函数将其数据进行交换
Swap函数的参数不能仅仅有数据的首地址,也还要知道这个数据元素的宽度,来交换多少。
Swap函数的思路,因为已经将void*转化为了char*所以函数的参数直接用char*即可而传过来的width就是交换元素的宽度因为char*是一个字节所以循环条件就是i<其循环宽度即可。
每换一次指针++即可指向下一个交换内容。
4.用改造的bubble_sort函数排序整形数组:
5.用改造的bubble_sort函数排序结构体字符串:
6.用改造的bubble_sort函数排序结构体整形: