前言
学到了函数指针,那这篇博客我们可以根据函数指针,了解一个函数qsort的应用与模拟实现
欢迎关注个人主页:小张同学zkf
若有疑问 评论区见
目录
1.回调函数
2.qsort函数使用
3.qsort模拟实现
1.回调函数
讲这个东西之前我们来认识一下回调函数,回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数
时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
简单来说,就是把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这就是回调函数的功能。
2.qsort函数使用
qsort是一个库函数,函数功能就是快速排序,可以将数组里的东西按照一定的顺序升序或降序排列
qsort的形式:
qsort(void* base,size_t num,size_t size,int(*compar)(const void*,const void*));
我们来一个一个分析,第一个参数是指针指向的是待排序数组的第一个元素,由于不知道你会传递什么指针类型,就用void*(是无具体类型的指针,它的作用就是接收任何类型的地址)暂时来作为指针类型。第二个参数是base指向的待排序数组的元素个数,第三个参数是每个元素的大小(字节长度),最后一个传入的是一个函数compar的指针,指向的是两个元素的比较函数。
举一个例子:
#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
这个是排序整形数据,我们可以看到,arr首元素地址,元素个数,每个元素的大小,以及比较方式都传入了qsort函数,这个比较函数int_cmp,在qsort函数内部调用,返回的是整型类型的两个元素的整型差,根据结果推断,如果第一个元素比第二个元素大,返回的是大于零的数,按的是升序排列,如果返回时两个元素交换位置,返回的若是大于零的数,则是降序排列。
那如果不是整型数组能排列吗,我要是结构体类型那
struct Stu //学⽣
{
char name[20];//名字
int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test2();
test3();
return 0;
}
可以看到结构体类型也可以按照某个成员类型来排列数组。
所以若使用qsort函数快速排序时这个比较函数必须自己定义好比较方式,然后把它的地址作为参数传入qsort内部进行调用。就能实现qsort快速排序的功能。
3.qsort模拟实现
那这个函数内部到底是怎么实现的那,qsort函数是快速排序的功能,那我们可以先想想我之前发的指针博客里的冒泡排序。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void maopao(int arr[], int x)
{
for (int i = 0; i < x-1; i++)//趟数
{
int s = 1;//假设有序
for (int j = 0; j <x-i-1 ; j++)//一趟比较几次
{
if (arr[j]>arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j+1] = tmp;
s = 0;//不完全有序
}
}
if (s == 1)//以完全有序
{
break;
}
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
maopao(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
两个for循环,外部那个for循环代表一共要比较几趟,如果10个数比较9趟,11个数比较10趟可见最后一个数不需要再走,内部那个for循环代表一趟要比较几次,第一个数比较,除了自身以外比较元素个数减1次,第二个数开始比较时由于已经和第一个数比较过了,就不需要再和它比较,所以比较次数再减1,所以随着趟数的增加,比较次数依次减小,直到完全有序。
那我们想想,qsort内部是不是也是类似于冒泡排序那,我们来模拟一下
void qsort_s(void* a, size_t x, size_t whits, int (*campare)(const void* as1, const void* as2))
{
//趟数
for (int i = 0; i < x-1; i++)
{
for (int j = 0; j < x - i - 1; j++)//每一趟
{
if (campare((char*)a + j*whits, (char*)a+(j+1)*whits) > 0)//满足交换的条件
{
swap((char*)a + j * whits, (char*)a + (j + 1) * whits,whits);//内部一个单元一个单元的交换
}
}
}
}
void print(int arr[], int x)
{
for (int i = 0; i < x; i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[] = { 23,5,67,89,36,2,300};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort_s(arr, sz, sizeof(arr[0]), campare);
print(arr, sz);
return 0;
}
我们假设qsort_s是模仿qsort的函数,qsort不仅仅可以排列整型,一切类型都可以排序,所以元素和下一个元素之间的字节宽度我们要考虑到,就是第三个参数whits,两次for循环内部是一个判断条件,就是比较函数返回值是否大于零,大于零就交换,也就是传入这个函数的地址是为了在此处调用比较函数进行判断是否达成交换的条件,在判断的时候不管元素地址是什么类型都要将它强制转换成char*类型,是因为我们交换这俩元素,这俩元素不管什么类型都是俩内存块,内存块交换要内部一个字节一个字节交换,从而达到两个元素的整体交换。
从这里我们也看出qsort函数内部模仿原理跟冒泡排序有点类似。
我们在定义比较函数时,也要注意,传过来的元素地址不管什么类型,都要将它强制转换成整型指针,这是因为因为比较函数返回值就是整型。以下是比较函数的定义
int campare(const void* as1, const void* as2)
{
return *(int*)as1 - *(int*)as2;
}
别忘了定义交换函数
void swap(char* cv1, char* cv2, size_t whits)
{
for (int i = 0; i < whits; i++)//一一对应交换
{
char tmp = *cv1;
*cv1 = *cv2;
*cv2 = tmp;
cv1++;
cv2++;
}
}
以下是qsort函数模拟实现的所有代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int campare(const void* as1, const void* as2)
{
return *(int*)as1 - *(int*)as2;
}
void swap(char* cv1, char* cv2, size_t whits)
{
for (int i = 0; i < whits; i++)//一一对应交换
{
char tmp = *cv1;
*cv1 = *cv2;
*cv2 = tmp;
cv1++;
cv2++;
}
}
void qsort_s(void* a, size_t x, size_t whits, int (*campare)(const void* as1, const void* as2))
{
//趟数
for (int i = 0; i < x-1; i++)
{
for (int j = 0; j < x - i - 1; j++)//每一趟
{
if (campare((char*)a + j*whits, (char*)a+(j+1)*whits) > 0)//满足交换的条件
{
swap((char*)a + j * whits, (char*)a + (j + 1) * whits,whits);//内部一个单元一个单元的交换
}
}
}
}
void print(int arr[], int x)
{
for (int i = 0; i < x; i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[] = { 23,5,67,89,36,2,300};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort_s(arr, sz, sizeof(arr[0]), campare);
print(arr, sz);
return 0;
}