前言:
本文主要是函数指针的重要应用,介绍qsort函数以及模拟实现这样一个不限制使用数据类型的快速排序函数。
回调函数
函数指针有一个非常大的作用就是实现回调函数。非常重要
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
以上面的计算机的代码为例子观察代码可以发现 ,只有调用函数部分不一样,那么能不能定义一个cacl()函数,通过将加减乘除函数作为参数传入到calc函数中,达到在calc函数中调用加减乘除函数?
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void menu()
{
printf("*******************************\n");
printf("****** 1.add 2.sub *****\n");
printf("****** 3.mul 4.div *****\n");
printf("****** 0.exit *****\n");
printf("*******************************\n");
}
void calc(int(*pf)(int, int))
{
int a = 0;
int b = 0;
int ret = 0;
printf("请输入2个操作数:");
scanf("%d %d", &a, &b);
ret = pf(a, b);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
图解:
回调函数的应用qsort函数
介绍一下冒泡排序
冒泡排序
有一组整数,需要排序为升序
1. 两两相邻的元素比较
2. 如果不满足顺序就交换
当前的代码只适合于整型数据
void bubble_sort(int arr[], int sz)
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)
{
//一趟比较
//两两相邻元素比较
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
这个冒泡排序其实是有缺陷的,它的参数是限制为只能是int类型,限制了它只能够排序整型数据!而qsort函数是一个可以用来排序任意类型数据的函数。
qsort函数介绍
qsort函数是C语言中的一个标准库函数,用于快速排序一个数组。它的原型如下:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *))
参数解释:
- void *base:指向待排序数组的起始位置的指针。也就是需要排序的数组的第一个元素
- size_t nitems:数组中的元素个数。
- size_t size:每个元素的大小(以字节为单位)。
- int (*compar)(const void *, const void *):比较函数指针,用于比较两个元素的大小。
qsort函数使用了快速排序算法,它会根据比较函数的返回值对数组进行排序。比较函数的定义如下:
int compar(const void *a, const void *b)
比较函数需要返回一个整数值。如果返回值小于0,表示a应该在b之前;如果返回值大于0,表示a应该在b之后;如果返回值等于0,表示a和b的顺序不变。这个函数需要两个参数两个要比较的值。
快速排序是一种基于比较的排序算法,它的平均时间复杂度为O(nlogn),是常用的排序算法之一。通过不断地分割数组并以一个基准值为中心进行排序,快速排序可以高效地将数组排序。
使用qsort函数时,你只需要提供一个比较函数,qsort函数会自动根据这个比较函数对数组进行排序。这使得排序变得非常简单和方便。你可以使用qsort函数对任何一种数据类型的数组进行排序,只需要提供相应的比较函数。
比较函数的形参中为什么用的是void*:
void* 是无具体类型的指针,不能直接进行解引用操作符(但是可以强制类型转换之后可以解引用),也不能进行+-整数的操作,它是用来存放任意类型数据的地址(可以理解为一个仓库,什么都能装,当需要用时再强制类型转换为需要的类型)。只有void*被允许存放任意类型数据的地址,如果是其他类型的指针编译器会报错。正是因为定义qsort函数时用的是void*,qsort函数才可以排序任意类型的数据。
个用于排序整型数据的比较函数cmp_int:
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
做差是因为这个比较函数需要返回值,如果返回值小于0,表示e1应该在e2之前;如果返回值大于0,表示e1应该在e2之后;如果返回值等于0,表示e1和e2的顺序不变
想要排成降序把这个比较函数的两个参数减法的位置变一下就好了
同理,qsort函数排序结构体类型数据(下面例子以结构体中的年龄来排序):
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
按年龄比较的函数中: 同样这里的指针不能直接解引用,先强制类型转换。
按名字比较的那个函数中:两个字符串比较使用strcmp函数(专门用来比较字符串的大小的)
模拟实现qsort函数
使用【冒泡排序】的算法,模拟实现一个排序函数 bsort ,可以用来排序任意类型的数据。
在冒泡排序的基础上来实现。
冒泡排序
有一组整数,需要排序为升序
1. 两两相邻的元素比较
2. 如果不满足顺序就交换
当前的代码只适合于整型数据
void bubble_sort(int arr[], int sz)
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)
{
//一趟比较
//两两相邻元素比较
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
问题:参数只能接收整型数组
参数一:void*的指针,指向需要排列的数组的第一个元素因为它是用来存放任意类型数据的地址
参数二:对照库函数qsort的定义中,第二个参数为待排序数组的元素个数,因此我们也用个 size_t num 存放待排序数组的元素个数。
当前,我们可以通过参数一和参数二知道起始位置地址(void* base)和元素个数(num),但是仅仅知道起始地址和元素个数是不够的,因为不知道一个元素有多大的,一次需要跳过多少个字节
参数三:因此还需要一个参数记录一个元素的大小 size_t size
函数内部循环的趟数和一趟的次数是不需要改造的
问题:对于不同类型的数据要有不同的比较方式,比如整型可以通过><来比较但是结构体就不行
解决方法·:将2个元素的比较方法以函数参数的形式传递
问题:不同的数据交换方式也有差异
因为是void*类型不能够直接加减整数来移动所以要强制类型转换才行,转换为char*最为合适因为步长最短,可以满足所有类型的交换。base是下标,想要找到下一个元素的地址应该加上一个元素的大小也就是size。交换第j个元素和第j+1个元素,应该起始地址加上j*size和起始地址加上(j+1)*size
cmp((char*)base + size * j
完成使用cmp来比较两个元素的大小关系了接下来就是完成交换的代码创建一个函数来实现交换,需要的参数肯定要这两个元素的地址,同时还要这两个元素的大小
交换函数要完成一个字节一个字节的交换因为可以实现任意类型的交换。
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{
int i = 0;
char tmp = 0;
for (i = 0; i < size; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
比较函数cmp和主函数是由程序员B来完成的,冒泡函数是由程序员A完成的用于完成排序(任何类型)
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{
int i = 0;
char tmp = 0;
for (i = 0; i < size; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
int i = 0;
//趟数
for (i = 0; i < num - 1; i++)
{
int j = 0;
//一趟内部比较的对数
for (j = 0; j < num - 1 - i; j++)
{
//假设需要升序cmp返回>0,交换
if (cmp((char*)base+j*size, (char*)base+(j+1)*size)>0)//两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
{
//交换
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
swap函数中临时建立一个变量用于两个变量的交换
同理想要实现按照什么来排序就i完成什么样的比较函数cmp,比如结构体变量按照名字,按照年龄
int cmp_int(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); } //测试bubble_sort 排序整型数据 void test1() { int arr[10] = { 3,1,5,2,4,7,9,6,8,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); }
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sizeof(struct Stu));
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
struct Stu { char name[20]; int age; }; int cmp_stu_by_age(const void* p1, const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } 测试bubble_sort 排序结构体数据 void test2() { struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); }
如果想要进行降序排序的话,只需要将用户自行创建并封装的 cmp函数 中return的p1和p2交换即可