C语言qsort函数的实现
1.首先阅读文档,查询qsort()函数的使用方法
void qsort (void* base, size_t num, size_t size, int (*compare)(const void*,const void*));
使用qsort()函数的好处就是可以排序任意类型的数据,不像冒泡排序只能排序整形数组
可以看到这个函数有四个参数,第一个参数是数组的首元素地址,第二个参数是数组中元素的个数,第三个是数组中每个元素所占字节数,第四个是需要自己创建的函数,用来比较来排序的元素
那么为什么需要这四个参数,我们接着看后面的函数实现,我会在一步步的过程中为你解惑
2.开始实现自己创建的qsort()函数
void my_sort(void* base, int num, int width, int (*cmp)(void* e1, void* e2))
{
//需要交换多少趟
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
//每趟需要多少次交换
for (j = 0; j < num - 1 - i; j++)
{
//相邻两个元素各自比较
//如何比较两个元素是这个函`在这里插入代码片`数的重点
//由于我们不知道以后比较的数据类型,我们将这个
if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
{
//交换两个数
Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
}
}
}
}
函数中使用void* 是因为不知道以后使用这个函数的人会使用什么类型的数组以及元素
首先我们计算要排多少趟,10个元素要排9趟,num - 1表示,num - 1 - j表示那一趟要交换两个元素的次数,与冒泡排序一致
所以需要num这个参数:代表元素的个数,只能在函数外传进来
接着来到这个函数的重点,两个相邻元素的比较
比较两个元素分为:
1.找到这两个元素
2.使用使用者给出的函数来比较(毕竟不知道以后的使用者要比较什么)
这个给出的函数的返回值是int类型,>0即代表左边大于右边,返回值为0代表两者相等,返回值<0代表右边的元素大,两个函数的参数是void*类型的指针。函数为什么这样设计我们举几个例子来感受下
函数应该如何设计,以及使用方法
int cmp_int(const void* e1, const void* e2)
{
return *((int*)e1) - *((int*)e2);
}
void test1()
{
int arr1[] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
qsort(arr1, sz, sizeof(arr1[0]), cmp_int);
int i = 0;
for (i = 0; i < 9; i++)
{
printf("%d\n ",arr1[i]);
}
}
可以看到我们要比较两个整形元素的大小关系,所以我们设计函数cmp_int()函数,一定要按照qsort()函数的参数类型设计,不然无法使用此函数,将两个元素的地址传过来,一void 类型的指针接受,通过强制类型转换转换为我们需要的int类型的指针,再通过指针解引用操作比较两个数,最后返回整形值,大于0,左边大于右边,小于0,左边小于右边,等于0左边等于右边
这个例子过于简单,我们举一个结构体的例子,排序结构体数组
typedef struct Stu
{
char name[20];
int age;
}S;
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((S*)e1)->name, ((S*)e2)->name);
//strcmp:#include<stdio.h>
//字符串大小的比较是以ASCII 码表上的顺序来决定,此顺序亦为字符的值。
// strcmp()首先将s1 第一个字符值减去s2 第一个字符值,若差值为0 则再继续比较下个字符,若差值不为0 则将差值返回。
//例如字符串"Ac"和"ba"比较则会返回字符"A"(65)和'b'(98)的差值(-33)
}
void test3()
{
S s1[3] = { {"zhangsan",20},{"lisi",53},{"wangwu",18} };
int sz = sizeof(s1) / sizeof(s1[0]);
qsort(s1, sz, sizeof(s1[0]), cmp_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s\n ", s1[i].name);
}
}
我们来按照结构体中的名字字符串来排序,我们设计函数cmp_by_name(),首先通过强制类型转化e1,e2为结构体指针,这里要将(S*)e1先用括号括起来,因为箭头的优先级更高;比较两个字符串大小用strcmp函数,这样就完成,使用qsort()函数非常方便
在了解完如何创作函数,使用sqort函数后我们回归正题
//相邻两个元素各自比较
if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
{
//交换两个数
Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
}
cmp的参数是这两个元素的地址,之后在自己所创作的函数中再去强制类型转化为所需类型,第一个元素地址就是base,如何找到之后的呢,观看代码
使用了一种很巧妙的方法:首先将其转化为char的指针,其加即就是原地址越过一个字节的地址,如何找到后面的地址呢,这里就需要到了第三个参数int width,代表所占的字节空间数,可以用sizeof直接计算,第一个元素的地址+1 * width不就跳到了第二个元素了吗,后面同理可得
(char)base + width * j ,widthj跳过第j个元素的地址,这就代表了第j+1个元素的地址,(char)base + width * (j + 1)代表j+2个元素的地址 ,这样就可以访问到所有元素了
比较完后,if如果大于0.,交换两个元素,所以我们还需要设计一个交换的函数
我们再设计一个Swap()函数来实现交换
void Swap(char* p1,char* p2,int wid)
{
//char* 只能交换一个字节,每次交换一个字节,width告诉你要交换几次
int i = 0;
for (i = 0; i < wid; i++)
{
char tmp = 0;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
我们将两个元素的地址交给Swap(),char类型来接收,但是char只能访问一个字节,接下来我们就需要引入Swap的第三个参数了,width,交换width个字节,不就是把这两个元素整个进行交换了吗
最后用循环交换width次1个字节实现就成功,到这里函数的整个设计也就完成了
本人第一篇文章,哪里有问题还请多多指教