我们都知道排序一个数组的方法有很多种,下面我介绍一下用qsort函数来排序一个数组。
一.qsort函数基本内容
我们先看一下qsort函数的原型
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
- 头文件--#include<stdilb.h>
- base -- 指向要排序的数组的第一个元素的指针。
- nitems -- 由 base 指向的数组中元素的个数。
- size -- 数组中每个元素的大小,以字节为单位。
- compar -- 着重理解,这个函数是需要用户自己定义,它用来比较两个元素的函数。它是函数指针类型 ,这个函数指针指向的函数,能够比较base指向数组中的两个元素,const是为了需要让两个比较的数值不被修改,
自己定义的compar函数通用(模板)可以这么写
//升序
int compareMyType (const void * a, const void * b)
{
if ( *(MyType*)a < *(MyType*)b ) return -1;
if ( *(MyType*)a == *(MyType*)b ) return 0;
if ( *(MyType*)a > *(MyType*)b ) return 1;
}
//降序
int compareMyType (const void * a, const void * b)
{
if ( *(MyType*)a > *(MyType*)b ) return -1;
if ( *(MyType*)a == *(MyType*)b ) return 0;
if ( *(MyType*)a < *(MyType*)b ) return 1;
}
//其实上面也可以改为,这样看着更简洁
int compareMyType (const void * a, const void * b)
{
return ( *(MyType*)a - *(MyType*)b );//升序
//return ( *(MyType*)a - *(MyType*)b );//降序
}
注 :①size_t表示类型是无符号整数
②为什么是用void*类型的指针,因为该函数不知道你想排序的是什么类型的数组,所以这种类型的指针可以接受任意类型的地址,而且该类型的指针不能进行解引用,也不能进行指针的运算
③顺带在这里提一下(后面模拟实现qsort函数的实现会用到),char*类型的指针进行解引用操作时访问的是一个字节,int*类型的指针进行解引用操作时访问的是四个字节。即指针类型可以决定指针解引用时访问几个字节。
二.下面看一下qsort函数的基本应用(默认是升序)
排序整形数组
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int i = 0;
int a[] = { 4,1,24,55,13 };
int sz = sizeof(a) / sizeof(a[0]);
qsort(a, sz, sizeof(int), cmp_int);
for (i = 0; i < sz; i++)
{
printf("%d ",a[i]);
}
//排序后结果 1 4 13 24 55
}
排序结构体数组
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int main()
{
int i = 0;
struct Stu a[] = { {"GGbond",18},{"Sheep",21},{"Tiger",15 }};
int sz = sizeof(a) / sizeof(a[0]);
qsort(a, sz, sizeof(a[0]), cmp_stu_age);
for (i = 0; i < sz; i++)
{
printf("%d ", a[i].age);
}
//排序后结果 15 18 21
}
三.模仿qsort的功能实现一个通用的冒泡排序(升序)
假设我们没有学习快速排序的算法,所以我们用冒泡函数的思想模拟实现类似qsort函数
这里我们拿整形数组来做例子,首先主函数内容就不赘述了,都看得懂,主要是bubble_sort这个函数怎么设计,首先可以明确因为模拟的是qsort函数,所以bubble_sort函数的参数要和qsort函数的参数形式上要一致,但是函数内部怎么设计呢,可以知道主体肯定是冒泡排序,如下
void bubble_sort(void* base, int sz, int size, int(*cmp)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)//躺输
{
for (j = 0; j < sz - 1 - i; j++)//一趟比较的对数
{
}
}
}
int main()
{
int i = 0;
int a[] = { 3,1,6,12,42,4 };
int sz = sizeof(a) / sizeof(a[0]);
bubble_sort(a, sz, sizeof(int), cmp_int);
for (i = 0; i < sz; i++)
{
printf("%d ",a[i]);
}
}
问题就来了,应该添加什么判断条件来对数组中要比较的两个数值进行排序呢?我们知道,一个整形数字在内存中占4个字节,如下图所示
而后我们再看到 int(*cmp)(const void*, const void*),cmp这个指针保存的是哪个函数,就是用来比较两个数值大小的函数啊!那不就是上面提到的吗!
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
好了到这里我们已经可以判断这两个数值的大小关系了,但是我们还缺个交换两个数值的函数啊!那怎么设计这个函数呢?我们再看到int cmp_int(const void* p1, const void* p2)这个函数的参数类型是const void*,接收的是地址,而且void* base指向的是数组首地址 ,而且整数在内存中是以字节为单位储存的,你想准确的操作某个数值的地址,是不是要用到指针变量啊,那用什么类型的指针变量呢?肯定是char*类型的啊,因为char*类型的指针进行解引用操作时访问的是一个字节,一是任何数字的倍数吗,所以最准确。
所以设计一个Swap函数进行交换两个数值,而且这个函数不仅要其中两个参数是接受两个数值的地址,还要一个参数是接受数值的大小(字节为单位),然后我们就可以通过一个for循环进行两个数值之间每个字节之间的交换,最终4个字节交换完之后,两个数值也就成功进行交换了
代码如下
void Swap(char* b1, char* b2,int size)
{
int i = 0;
char t = 0;
for (i = 0; i < size; i++)
{
t = *b1;
*b1 = *b2;
*b2 = t;
b1++;
b2++;
}
}
void bubble_sort(void* base, int sz, int size, int(*cmp)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if(cmp((char*)base+j*size,(char*)base + (j+1) * size)>0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}
int main()
{
int i = 0;
int a[] = { 3,1,6,12,42,4 };
int sz = sizeof(a) / sizeof(a[0]);
bubble_sort(a, sz, sizeof(int), cmp_int);
for (i = 0; i < sz; i++)
{
printf("%d ",a[i]);
}
}
再看下图如何进行数值交换的
最后的最后,谢谢你的阅读,如有错误,恳请指正,再次谢谢!