### qsort是C语言中的一种排序方法,该排序方式具有排列快速、兼容不同类型元素的优势,在使用时同时也要注意几个关键点,以下便是对qsort的原理解释。
## 函数原型
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
# 可以观察到此函数有四个参数,为了方便解释这四个参数,下面将通过使用该函数来具体说明
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int cmp(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
int main()
{
int arr[] = { 9,8,3,4,6,5,7,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
可以看到,通过qsort排序可以将一个无序的整型数组中的元素排列成有序的;
qsort函数头文件<stdio.h> ;第一个参数是待排序数组的首元素地址,将他作为第一个参数,可以理解为先将这个数组的地址传过去方便qsort排序的时候找到 “初始点”;第二个参数可以理解为要排序的元素个数;第三个参数是一个元素的大小;第四个参数是一个函数名,也就是函数的地址,在这里我们需要自己声明定义一个int型的函数,这个函数的作用是比较传过去的两个元素的大小的,并且通过在cmp函数内部的判断的不同返回不同值(正、负或0),返回值就可以确定两个元素的大小,并且之后就在qsort操作这两个元素进行排序;
# 对返回值的解释:
要注意的是这里的MyType是你要排序的元素的类型;
通过观察可以知道将形参的a,b交换位置那么你排序的就会改成降序;不交换a、b,那就是升序。倘若你改变返回值里面a,b的位置那么也会相应的得到不同的排序顺序;
# 函数原型的解释:
通过上面具体例子的解读,此时可以对函数原型的理解应该更加容易了:void* base就是一个基准的意思,用void*保证了形参的类型可以随时变化,并且用指针的形式是为了方便访问地址;size_t num和size_t size对应了整个要排序工程的大小和每个排序对象的大小;第四个实参是一个函数指针,在这里是为了接收你自己定义的函数的地址,这个函数指针里面的函数的参数是const void* 在这里和自己定义的函数的参数一样,都是为了兼容各种元素的排序并且保证的元素不被改变。
## 通过qsort函数来排序结构体(举例)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct stu
{
char name[20];
int age;
};
int cmp(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name , ((struct stu*)p2)->name);
}
int main()
{
struct stu meb[] = { {"zhangsan",19},{"lisi",18},{"wangwu",17} };
int sz = sizeof(meb) / sizeof(meb[0]);
qsort(meb, sz, sizeof(meb[0]), cmp);
for (int i = 0; i < sz; i++)
{
printf("%s ", meb[i].name);
}
return 0;
}
当然除此之外qsort还可以排列很多类型的元素,这里就不多赘诉了。
### qsort函数的模拟
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct stu
{
char name[20];
int age;
};
int cmp_str(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
int swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
int i = 0;
for (; i < sz - 1; i++)
{
int j = 0;
for (; 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);
}
}
}
int cmp(const void* p1, const void* p2);
int main()
{
struct stu memb[] = { {"zhangsan",18} ,{"lisi",19}, {"wangwu",20} };
int sz = sizeof(memb) / sizeof(memb[0]);
my_qsort(memb, sz, sizeof(memb[0]), cmp_str);
for (int j = 0; j < sz; j++)
{
printf("%s %d \n", memb[j].name, memb[j].age);
}
}
# 解释说明:
首先看到main函数,然后在看到my_qsort函数,它是这个模拟代码的核心,我们先模仿qsort函数的形参写上我们这个模拟函数的形参(各个形参的意义在上面函数原型解释),首先比较元素的大小这里是基于冒泡排序的原理一次一次分别比较相邻元素大小;看到嵌套的for循环里面的内容:通过另一个函数cmp_str来比较两个元素的大小进而判断要不要交换,重点是我们使用这个函数的时候实参的理解,我们将所有元素首先转换成char*的类型,在这里是为了兼容性(举个反例,假如你是int*类型的,但是你要排序一个结构体,但是这个结构体每个元素的大小都不是int的整数倍,那自然是不能传过去的),但是char*类型就不一样,当你加减一个整数来确保大小和你要排序的元素大小相同,因为char大小是一个字节,char*加几就往后走几。再看到(char*)base+j*width,base是首个元素的地址,j是你排到第几个元素了,与(j+1)成为一对比较对象,width则是你提前计算好的,这个就是上文提到的“加减一个整数”的整数;这样我们无论比较什么类型的元素,都可以通过不改变设计函数来完成了;
重点二:如果返回值>0,那么说明要交换,交换进入swap函数,在这个函数里面我们通过一个字节一个字节来交换,这样保证了无论交换什么样类型的元素,都可以按照这一个函数来实现,当然我们交换了一个大小为1字节的空间后要继续交换这个元素的下一个字节大小空间的内容,于是就有buf1++,buf2++;
运行结果:
这里是按照名字来排序。
通过模拟实现qsort函数可以加深对qsort排序原理的理解,同时也深化了我对指针的认识。
如有问题,可以一起讨论。