C 语言函数库qsort
通过void *
支持多种数据类型的数组,使用起来也很方便。
我们这里也一步一步地写一个类似qsort
的函数。我们的目的是学习void *
的用法,并不是学习快速排序,所以我们就实现简单一些的排序算法,用冒泡排序好了。
冒泡排序,第一版
很快就写好了第一版
#include
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void sort(int arr[], int total_elems) {
for (int i = 0; i < total_elems; i++) {
for (int j = i + 1; j < total_elems; j++) {
if (arr[i] > arr[j]) {
swap(&arr[i], &arr[j]);
}
}
}
}
int main() {
int data[] = {3, 55, -4, 12, -73, 127, 6, 19, 1, 8};
sort(data, sizeof(data) / sizeof(int));
for (int i = 0; i < (sizeof(data) / sizeof(int)); i++) {
printf("%d ", data[i]);
}
printf("\n");
return 0;
}
我们测试一下,输出
-73 -4 1 3 6 8 12 19 55 127
看起来没有问题,但是,如果我们的数据如果是double
呢?它就罢工了,必须要修改sort函数才行。
那么库函数qsort又是怎么支持多种数据类型的呢?我们看一下qsort函数签名:
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
看来需要用指针来搞定,在把sort函数改成和qsort的参数之前,我们先把sort改成指针形式。
冒泡排序,第二版
第二版,把sort改成指针形式,其它不变。
void sort(int *pbase, int total_elems) {
int *right_ptr = pbase + total_elems;
for (int *i = pbase; i < right_ptr; i++) {
for (int *j = i + 1; j < right_ptr; j++) {
if (*i > *j) {
swap(i, j);
}
}
}
}
第二版的程序把数组改成指针,看起来和第一版没有什么区别,不过比第一版更容易转化成第三版。
冒泡排序,第三版
#include
// borrowed from The GNU C Library (glibc)
#define SWAP(a, b, size) \
do { \
int __size = (size); \
char *__a = (a), *__b = (b); \
do { \
char __tmp = *__a; \
*__a++ = *__b; \
*__b++ = __tmp; \
} while (--__size > 0); \
} while (0)
void sort(void *pbase, int total_elems, int size,
int( * cmp)(void *, void *)) {
char *right_ptr = (char *)pbase + total_elems * size;
for (char *i = (char *)pbase; i < right_ptr; i += size) {
for (char *j = i + size; j < right_ptr; j += size) {
if ((*cmp)((void *)i, (void *)j) > 0) {
SWAP(i, j, size);
}
}
}
}
int cmp(void * a, void * b) {
return *((int *)a) - *((int *)b);
}
int main() {
int data[] = {3, 55, -4, 12, -73, 127, 6, 19, 1, 8};
sort(data, sizeof(data) / sizeof(int), sizeof(int), cmp);
for (int i = 0; i < (sizeof(data) / sizeof(int)); i++) {
printf("%d ", data[i]);
}
printf("\n");
return 0;
}
如果我的数据类型要改为double呢?sort函数不用修改,只需修改cmp函数即可。
int cmp(void * a, void * b) {
return (*((double *)a) - * ((double *)b) > 0) ? 1 : -1;
}
这个比较函数cmp独立出来还有一个好处,就是你可以自定义比较的规则,如果你需要从大到小排列,只需要调换a和b的位置。
int cmp(void * a, void * b) {
return (*((double *)b) - * ((double *)a) > 0) ? 1 : -1;
}
我们看到sort函数其实并不认识数据,只是数据的搬运工,通过cmp
函数,告诉sort
如何比较数据,通过元素长度size
,告诉sort
每次移动多少个字节,而内部的char *
表明按字节操作,并不是真正的类型。
现在我们sort
函数看起来和系统函数qsort
一样,我们把sort给位qsort并导入头文件#incude
,系统会保警告
sor.c:37:56: warning: incompatible pointer types passing 'int (void *, void *)' to parameter of type 'int
(* _Nonnull)(const void *, const void *)' [-Wincompatible-pointer-types]
qsort(data, sizeof(data) / sizeof(int), sizeof(int), cmp);
^~~
哦,我们应该加上const,把cmp和sort函数都加上:
cmp
改为:
int cmp(const void * a, const void * b) {
return (*((double *)b) - * ((double *)a) > 0) ? 1 : -1;
}
sort 函数签名改为
void sort(void *pbase, int total_elems, int size,
int( * cmp)(const void *, const void *))
警告就消除了。
再回看系统函数
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
除了两个参数是用了size_t
类型,size_t
等同无符号长整型unsigned long
,不过一般用int长度也够了,如果你的数据很多,还是用系统的qsort
吧,当然你的数据很少,也应该用qsort
:),除非你需要稳定排序或者你用的单片机空间吃紧。