类似qsort支持多数据类型的C语言排序

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 :),除非你需要稳定排序或者你用的单片机空间吃紧。

你可能感兴趣的:(类似qsort支持多数据类型的C语言排序)