我们来回顾一下qsort函数
qsort(s,sz,sizeof(arr[0]),cmp_stu_by_name)
qsort函数一共有四个参数
第一个参数:待排列数组的首元素地址
第二个参数:待排列数组的元素个数
第三个参数:待排列数组一个元素所占的字节数。
第四个参数:是函数指针,比较两个元素所用函数的地址,函数使用者自己实现,函数的两个参数是带比较的两个参数的地址
struct Stu
{
char name[20];
int age[10];
};
void test4()
{
int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
bubble_sort();
}
void test5()
{
struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
}
test4函数是我们使用冒泡排序函数对整形数组进行排列
test5函数是我们使用冒泡排序函数对结构体数组进行排列,我们进行类比操作
我们构建我们的冒泡排序函数
void bubble_sort(void*base, int sz, int width,比较函数)
{
}
我们对这个函数的参数进行分析:
第一个参数:void*base 第一个参数是一个无类型的指针 ,因为我们对待排序数组一无所知,所以我们首先要把待排序数组的首元素的地址传入函数内部,方便函数使用参数,为什么用void* 因为待排序数组的类型有很多种,用void*表示可以接收各种类型的地址,比如我们要对整型数组和结构体数组都排序
分别使用两个函数分别对整型数组和结构体数组排序,我们传递第一个参数时,只需传递待排序数组首元素的地址即可,无需对其进行其他处理,因为void*指针能够接收不同种类型的指针
第二个参数:int sz,这里的sz是sizeof(arr)/sizeof(arr[0]),表示待排列数组元素的个数,注意!我们需要在test函数内部对sz参数进行定义过后才能将元素个数通过传参传递过去
第三个参数:int width,width表示宽度,这里表示待排列指针一个元素所占的字节数,我们需要在test函数内部对width参数进行定义过后才能将元素的宽度通过传参传递过去
第四个参数:第四个参数是我们的比较函数的地址,在冒泡函数中不可避免要使用两次for循环
void bubble_sort(void*base, int sz, int width,int(*cmp)(void*e1 ,void*e2))
{
int i = 0;
for (i = 0; i < sz; i++)//趟数
{
int j = 0;
for (j = 0; j < sz - 1; j++)//每一趟的对数
{
//排序的具体逻辑
}
}
}
如图所示,这里的排序的基本逻辑就是我们需要构建的比较函数。
我们的比较函数的两个参数必须是void*型,两个void*的指针表示我们能函数能接收任意类型的指针
在排序的基本逻辑中,我们首先要明确,冒泡函数是把两个元素进行比较,再通过for循环实现每个元素的比较,所以我们要写出相邻两个元素的表达式,我们可以思考一下
cmp(base,base+1);
相邻两个元素用这样表达可以吗?
答:不可以,我们的base的类型是void*,void*是不能进行加减运算的,原因是void类型接收的指针类型是不同的,不同的类型+1跳过的字节数是不同的,int型的+1跳过四个字节,char型的+1跳过1个字节
cmp(base, (int*)base+1)
这种可以吗?
答:不可以,这种方法的意思是将base的类型强制转化为int*,在+1表示下一个元素,但这种方法有缺陷,当我们base接收的类型是结构体类型的指针时,元素之间的间隔不仅仅是四个字节,使用这种方法也会错误
cmp(base, (char*)base+width)
这种可以吗?
答:也是不可以的,但这种方法已经接近正确答案,我们对这种方法进行说明:第二个元素
(char*)base+width,我们首先将base强制类型转化为char*,这样base+1 -1只会跳过一个字节,我们再加上width,width在这里表示元素之间的间距,比如int型的width=4,char型的width=1,实现了任意类型都可以访问第二个元素,但是这种写法只能访问第一个元素和第二个元素,其他的元素无法比较
最正确的写法是这样
cmp((char*)base+j*width, (char*)base+(j+1)*width))
说明:(char*)base+j*width表示前一个元素,(char*)base+(j+1)*width)表示后一个元素,为什么呢?(char*)base表示我们首先将其强制类型转化为char型的指针,j表示的是第j对,当j=0时,参数就变成((char*)base,(char*)base+1*width) 满足第一对,当j=1时,参数就变成(char*)base+1*width, (char*)base+2*width) ,满足第二对,完整的冒泡函数是这样的
void bubble_sort(void*base, int sz, int width, int(*cmp)(void*e1 ,void*e2))
{
int i = 0;
for (i = 0; i < sz; i++)//趟数
{
int j = 0;
for (j = 0; j < sz - 1; j++)//每一趟的对数
{
if (cmp((char*)base + j*width, (char*)base + (j + 1)*width)>0)
{
//交换
}
}
}
}
接下来,我们来创建函数cmp
void cmp_int(void*e1, void*e2)
{
return *(int*)e1 - *(int*)e2;
}
我们需要再写一个交换函数
swap((char*)base + j*width, (char*)base + (j + 1)*width,width);
swap函数的参数类型和比较函数的相同,我们需要对swap函数进行定义
void swap(char*buf1, char*buf2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
为什么这样写呢?
答:假设数组元素的宽度是8个字节,第一个元素为(1111 1111) 第二个元素为(0000 0000),如果我们直接将*buf1和*buf2进行交换,因为我们buf的类型是char型,char型只占一个字节,我们进行交换后的结果为(0111 1111) 第二个元素为(1000 0000)并没有实现真正意义的交换,所以我们要使用for循环,这时候的width=8,我们执行8次交换,得出的结果才是真正意义上的交换,注意这里的buf是指针,要访问的话需要解引用操作, 还要注意这里需要参数width,
我们将完整的int型的数组排列代码写出来
void cmp_int(void*e1, void*e2)
{
return *(int*)e1 - *(int*)e2;
}
void swap(char*buf1, char*buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void*base, int sz, int width, int(*cmp)(void*e1, void*e2))
{
int i = 0;
for (i = 0; i < sz; i++)//趟数
{
int j = 0;
for (j = 0; j < sz - 1; j++)//每一趟的对数
{
if (cmp((char*)base + j*width, (char*)base + (j + 1)*width)>0)
{
swap((char*)base + j*width, (char*)base + (j + 1)*width,width);
}
}
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
对这个函数进行简要说明
第一部分 :main函数(内部引用了冒泡函数(内部引用了int型的比较函数))
第二部分:创建冒泡函数(内部接收了int型的比较函数)(内部引用了swap函数)
第三部分:创建swap函数,实现交换
第四部分:创建int型的比较函数
接下来,我们写一个结构体类型的数组
struct Stu
{
char name[20];
int age[10];
};
void swap(char*buf1, char*buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void*base, int sz, int width, int(*cmp)(void*e1, void*e2))
{
int i = 0;
for (i = 0; i < sz; i++)//趟数
{
int j = 0;
for (j = 0; j < sz - 1; j++)//每一趟的对数
{
if (cmp((char*)base + j*width, (char*)base + (j + 1)*width)>0)
{
swap((char*)base + j*width, (char*)base + (j + 1)*width,width);
}
}
}
}
int cmp_by_age(const void*e1, const void*e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 10 } };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_by_age);
return 0;
}
可以发现,使用冒泡函数调用结构体类型和整数数组类型差距很小
第一个:我们要对结构体类型进行声明 struct Stu
{
char name[20];
int age[10];
};
第二:比较函数的定义不同int cmp_by_age(const void*e1, const void*e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
这里要注意:(struct Stu*)e1外围必须加上括号,因为->的优先级大于强制类型转化,所以要保证强制类型转化,所以必须加上括号。