学过C语言的都知道,排序是最基本的操作,而排序的方法又有很多种,直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序,计数排序等等。
相信很多学过C语言的小伙伴都学过冒泡排序这个经典的排序方法,但是我们一般写的那个冒泡排序是只针对整形数组使用的,如果使用者需要排序一个浮点型的数组或者是一个结构体数组的话,这样的冒泡排序是实现不了的,今天我就给大家分享一个通用的qsort函数。
可以看到,上图中的qsort函数的返回类型是void,有4个参数,第一个参数是待排序的数组的起始地址,第二个参数是数组中元素的数目,第三个参数是数组中每个元素的大小(单位是字节),第四个参数是一个函数指针,由图可知,参数是两个void*类型的指针,为什么是void*而不是(int*)或者(float*)呢?是因为编写这个qsort函数的程序员不知道将来用这个函数的人会排序什么类型的数组,而void*类型的指针就刚好能够接收任意类型的地址,所以用void*指针作为参数是最合适不过的了,这个函数指针的返回类型是int,当第一个元素大于第二个元素的时候,返回一个大于0的整数,相等返回0,第一个小于第二个的时候返回一个小于0的整数。
具体细节看代码的注释:
void swap(char* buf1, char* buf2, int width) { int i = 0; //数组中的每一个数据占width个字节,我们只需要循环width次就能把两个元素的每一个字节都交换, //最终两个元素的内容也就交换成功了 for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1,const void* e2)) { //这里是典型的冒泡排序的方法 int i = 0; for (i = 0; i < sz-1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { //这里的cmp函数就是就是比较相邻的两个元素的大小,如果返回值大于0,则证明前一个元素 //比后一个元素大,则需要交换这两个元素。由于这里的base指针的类型是void*,所以我们 //首先需要将它强制类型转换成char*类型的指针,那为什么是转换成char*而不是int*, //double*呢?其实很简单,你试想一下,我们比较完了两个相邻的元素之后是不是需要 //拿后一个和这两个元素中大的元素进行比较大小,但是大家别忘了,指针类型的大小可是决 //定了你指针加1跳过几个字节的啊,整形指针加1跳过一个整形,字符指针加1跳过一个字节 //但是我们并不知道将来这个函数会被用来排序什么类型的数据的啊,但是无论是数目类型的 //数据,它的大小都至少为1个字节吧,所以转换成(char*)类型是最合理的。而参数中的宽 //度又正好能让我们找到下一个元素,只需要再起始地址加上宽度*j就能找到下一个元素了 //所以if语句里面的判断条件应该这样写 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width,width); } } } } int cmp_int(const void* e1, const void* e2) { //由于e1和e2都是void*类型的指针,所以要先把它们强制类型转换成(int*)类型才能进行解引用 //操作,由于我们排的是升序,所以前一个元素减去后一个元素刚好反映了两个元素的大小关系, //而这个函数如果前一个元素比后一个大就返回整数,相等返回0,否则小于返回负数,所以我们可以直 //接返回两个数的相减的结果。 return *((int*)e1) - *((int*)e2); } void test1(void) { int arr[] = { 9,8,7,6,5,4,3,2,1,0 };//排序一个整形数组 int sz = sizeof(arr) / sizeof(arr[0]);//计算数组大小 //调用bubble_sort函数排序,第一个参数是待排序的数组起始位置,接着是数组的元素个数,接着是 //数组每个元素的大小(单位是字节),接着是一个函数指针,这个函数的具体内容是待排序数组的排 //序方法,比如整形数组可以直接用大于小于或者等于比较大小,而像结构体数组这种单个元素是复杂 //的对象的时候则需要规定一个比较方法,例如是通过年龄比较还是通过名字比较,这个函数就是实现 //这个数组里元素的比较方法的 bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]);//打印排好序的数组各个元素 } } struct Stu { char name[20]; int age; }; int cmp_stu_by_name(const void* e1, const void* e2) { //通过姓名对结构体进行排序,需要用到strcmp函数,依然是返回1,0,-1 return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } void test2(void) { struct Stu s[] = { {"zhangsan",18},{"wangwu",17},{"lisi",20} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int cmp_stu_by_age(const void* e1, const void* e2) { return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age); } void test3(void) { struct Stu s[] = { {"zhangsan",18},{"wangwu",17},{"lisi",20} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age); } int main() { test1(); test2(); test3(); return 0; }
以上就是一个通用的快速排序qsort函数的实现,你们学会了吗?大家有兴趣可以用其他类型的数组进行排序测试一下。