目录
一、简单回顾qsort函数
二、通过冒泡排序的算法模拟实现qsort函数
1>参数改造
2>bubble_sort函数体改造
三、完整代码演示
上篇博客对qsort函数的概念和用法进行了讲解,今天来深入了解qsort函数,模拟实现qsort函数,在此之前对qsort函数进行简单回顾:
概念:qsort是一个库函数,其所需的头文件为 #include
,它可以用来对任意类型的数据进行排序
用法:
qsort函数有4个参数:
void* base:待排数据的首元素的地址
size_t num:待排数据的个数
size_t size:待排数据中一个数据所占字节大小
int (*compar) (const void*, const void*):是一个函数指针,函数指针compar指向一个函数,这个函数需要自己写,这个函数功能就是比较待排序数组的两个元素
关于qsort函数的详细讲解可以看一下我之前写过的博客:指针进阶讲解(2)
冒泡排序是我们熟悉的一个排序算法,所以用它来模拟实现qsort函数,先来看一个冒泡排序来排序整型数据的代码:
#include
void bubble_sort(int arr[], int sz)
{
int i = 0;
int j = 0;
for (i = sz - 1; i > 0; i--) //趟数
{
for (j = 0; j < i; j++) //一趟里两数交换次数
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
return 0;
}
接下来通过对该代码的改造来达成我们的目的
在bubble_sort函数调用处,函数的第一个实参是arr也就是数组首元素的地址,而我们要排序的是任意类型的数据,那么在函数定义处就不能用int arr[]来接收它,所以应该改为void*类型的指针,因为void*类型的指针可以接收任意类型数据的地址,为了和qsort函数一致,我们改为void* base;
在bubble_sort函数调用处,函数的第二个实参是sz,也就是数组长度,这里没有要改的地方,为了和qsort函数保持一致,我们将类型改为size_t;
在冒泡排序算法中是从前往后两两数据依次比较,每比完两个数据之后往后走,比较接下来的两个数据,但是,我们要比较的是任意类型的数据,那么比完两个数据之后往后走多上呢?计算机时不得而知的,所以要加上第三个参数:size_t size也就是待排数据中一个数据所占字节的大小;
在bubble_sort函数内,是这样比较两个数据的:
if (arr[j] > arr[j + 1])
{
...
}
这种方法显然无法满足任意类型数据的比较,比如结构体类型的数据,此时就需要一个新的比较方法,我们将这种方法写进另一个函数cmp中,参照qsort函数的最后一个参数,我们需要第四个参数函数指针,再利用函数指针去回调新的比较方法的函数,此时这个bubble_sort函数的参数如下:
void bubble_sort(void*base, size_t num, size_t size, int (*cmper)(const void*, const void*))
int cmp(const void*a, const void*b)
{
return *(int*)a - *(int*)b;
}
void bubble_sort(void*base, int sz, size_t size, int (*cmper)(const void*, const void*))
{ //该函数指针指向cmp函数
int i = 0;
int j = 0;
for (i = sz - 1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr), cmp);
return 0;
}
在bubble_sort函数中两个for循环都不用改变,因为它们都取决于待排数据的个数,唯一要进行改造就是两个数据的比较方法和两个数据的交换方法:
if (arr[j] > arr[j + 1]) //改造
{
/*int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;*/ 改造
}
首先是比较方法,也就if的条件语句应该去调用我们刚刚写好的新的比较方法的函数cmp,函数指针int (*cmper)(const void*, const void*)所指向的函数的参数是两个指针,所以在调用cmp函数时应该传过去的是arr[j]和arr[j+1]的地址;
那么该如何求arr[j]和arr[j+1]地址呢,base是数组首元素的地址,但是不能用base + j来表示arr[j]的地址,因为base是void*类型的,void*类型的指针不能解引用和+-操作,如果我们排序的是整型数据,可以将其强转为int*类型然后在进行+-操作,但是这样就只能排序整形数据,其他类型数据行不通;
这里介绍一种方法:(char*)base + j * size;将其强转为char*类型,因为char*类型的指针+1-1时只移动一个字节,char*类型的base + size,意思是让它移动待排数据类型所占的字节个数,让size*j就代表第j个元素的地址
if (cmp((char*)base + size * j, (char*)base + size*(j + 1))
{
...
}
接下来对两个数据的交换方法进行改造,在不知道是什么数据类型情况下,我们可以将两个数据进行一个字节一个字节的交换
void swap(char* buf1, char* buf2, size_t size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
if (cmp((char*)base + size * j, (char*)base + size*(j + 1))
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
将要交换的两个数据的地址,和每个数据所占内存大小传给swap函数,swap函数用两个字符指针来接收两个数据的地址,在函数内部将buf1和buf2解引用再交换,由于它们是两个字符指针,所以每次交换只交换一个字节,交换完后buf1和buf2分别+1,进行下一个字节的交换
至此就完成了用冒泡排序的算法模拟实现psort函数
排序整形数据:
#include
void print(int arr[], size_t sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void swap(char* buf1, char* buf2, size_t size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t num, size_t size, int (*cmper)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (cmper((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int cmp_int(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
size_t sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
return 0;
}
结果:
排序结构体数据:
#include
struct Stu
{
char name[20];
int age;
};
void swap(char* buf1, char* buf2, size_t size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t num, size_t size, int (*cmper)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (cmper((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int cmp_struct_name(const void* a, const void* b)
{
return strcmp(((struct Stu*)a)->name, ((struct Stu*)b)->name);
}
int main()
{
struct Stu arr[3] = { {"sans", 18}, {"frisk", 8},{"chara", 9} };
size_t sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_struct_name);
return 0;
}
结果: