1.实现思路
当我们要对一个整型数组排序时我们可能会用到其中一种排序方法——冒泡排序法,其核心思想是两两比较,接下来我们来回顾一下其代码的实现方法,我们可以通过如下链接查看冒泡排序的图解
https://zhuanlan.zhihu.com/p/126354991
从中我们可以了解到冒泡排序一共要排序的趟数以及每趟需要对比的次数(如需要对n个数据排序,需要排序n-1趟、每趟对比的次数为n-1-趟数)接下来我们来实现其中的核心代码
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
通过这段代码我们可以对一组整形数组进行排序,但是,它可以对字符数组甚至结构体数组进行排序吗?答案是不能!
为了排序这些类型的数组,我们必须对上面的代码进行改造,我们知道,无论是什么类型的数组,它们需要排序的趟数和每趟需要的对比的次数是一样的,很显然,想要编写一个排序各种类型数据的代码,其排序思路是一致的,但是,
1.代码编写者并不知道需要用这段代码来排序什么类型的数据,因此,数据中的两个数是否能直接进行比较是未知的如比较字符串的大小不能直接比较,而是调用库函数strcmp来比较)所以,我们就需要用户自己编写一个比较数据的函数,然后,这段代码通过用户自己编写的函数的返回数值进行数据交换
2.由于不知道数据类型,我们用来交换数据的媒介tmp也不知道该设置成什么类型,因此在这里我们需要再编写一个swap函数来进行数据交换
2、代码实现
接下来我们进行代码的实现,这里我们以结构体数组为例
int struct_compare(void*p1,void*p2)
{
//void*类型指针无法解引用,因此需要强制类型转化
if (strcmp(((struct student*)p1)->name, ((struct student*)p2)->name) > 0)
return 1;
else if (strcmp(((struct student*)p1)->name, ((struct student*)p2)->name) < 0)
return -1;
else
return 0;
}
#include<stdio.h>
struct student
{
int num;
char name[15];
};
void test()
{
struct student arr[] = { {12,"xiaoming"},{8,"chen"},{9,"zhangpen"} };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), struct_compare);
}
int main()
{
test();
return 0;
}
上面我们建立了结构体变量和数组,以及编写了一个my_qsort函数,这个函数的参数是模仿qsort函数的参数实现的,qsort函数一共有4个参数,分别是待排序数组的首元素地址、数组的元素个数、数组中每个元素的大小以及一个函数(比较两个数大小的方法的函数)的地址(该函数需要用户自己实现)
下面我们进入该函数内部观察该函数的实现
void my_qsort(void* P, int num, int size, int(*p)(void*, void*))
{
int i, j;
i = j = 0;
for (i = 0; i < num - 1; i++)
//要排序的次数比数组元素少一
{
for (j = 0; j < num - i - 1; j++)
{
if (p((char*)P + j * size,(char*)P + (j + 1) * size) > 0)
//
{
//使用swap函数进行数据交换
swap((char*)P + j * size, (char*)P + (j + 1) * size, size);
}
}
}
}
在my_qsort函数中,我们使用了一个函数指针来接收,在这段代码中我们通过函数指针调用了我们的比较函数struct_compare,通过这个函数的返回值来判断数组中相邻连个元素的大小关系,如果前一个元素大于后一个元素,就使用swap函数进行相邻两个元素的交换,接下来看swap函数的实现
void swap(void* p1, void* p2,int size)
{
for (int i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
可以看到,swap函数一共传入3个参数,分别是待交换的两个元素的地址以及数组中元素的大小,这里使用void*类型的指针接收是因为我们不知道参数的类型,因此用空类型指针接收,我们知道,两个整形进行交换是交换了4个字节的内容,那如果我们一个字节一个字节的交换,是不是也能达到同样的效果呢?当然可以!因此在上图中我们知道的带交换的两个元素的起始地址,又知道了待排序元素的大小,不就可以通过一个字节一个字节地交换来实现两个未知类型元素地交换吗。
下面是完整的代码,
#include<stdio.h>
struct student
{
int num;
char name[15];
};
int struct_compare(void*p1,void*p2)//写入要排序目标的比较方法,由于不知道数据类型,因此用void*接收
{
//void*类型指针无法解引用,因此需要强制类型转化
if (strcmp(((struct student*)p1)->name, ((struct student*)p2)->name) > 0)
return 1;
else if (strcmp(((struct student*)p1)->name, ((struct student*)p2)->name) < 0)
return -1;
else
return 0;
}
void swap(void* p1, void* p2,int size)
{
for (int i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
void my_qsort(void* P, int num, int size, int(*p)(void*, void*))
{
int i, j;
i = j = 0;
for (i = 0; i < num - 1; i++)
//要排序的次数比数组元素少一
{
for (j = 0; j < num - i - 1; j++)
{
if (p((char*)P + j * size,(char*)P + (j + 1) * size) > 0)
{
//使用swap函数进行数据交换
swap((char*)P + j * size, (char*)P + (j + 1) * size, size);
}
}
}
}
void test()
{
struct student arr[] = { {12,"xiaoming"},{8,"chen"},{9,"zhangpen"} };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), struct_compare);
}
int main()
{
test();
return 0;
}
由于结构体数组打印起来比较麻烦,我们可以通过调试的方法来观察结构体数组是否被排序,如下图
可以看到,结构体数组的元素已经被正确地交换了。