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

C 语言函数库qsort通过void *支持多种数据类型的数组,使用起来也很方便。
我们这里也一步一步地写一个类似qsort的函数。我们的目的是学习void *的用法,并不是学习快速排序,所以我们就实现简单一些的排序算法,用冒泡排序好了。

冒泡排序,第一版

很快就写好了第一版

#include <stdio.h>

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 <stdio.h>

// 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 <stdlib.h>,系统会保警告

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335