目录
一.回调函数:
1.回调函数的定义:
2.回调函数的使用:
3.qsort函数的使用:
4.利用回调函数模拟实现qsort函数:
二.总结:
博客主页:张栩睿的博客主页
欢迎关注:点赞+收藏+留言
系列专栏:c语言学习
家人们写博客真的很花时间的,你们的点赞和关注对我真的很重要,希望各位路过的朋友们能多多点赞并关注我,我会随时互关的,欢迎你们的私信提问,也期待你们的转发!
希望大家关注我,你们将会看到更多精彩的内容!!!
一.回调函数:
1.回调函数的定义:
我们都知道,函数的调用除了我们最基础的调用方式之外,通过函数指针我们也可以实现对函数的调用。函数指针调用函数的函数,就称为回调函数。
如果你把函数的地址作为参数(即使用指针)传递给另一个函数 ,且当这个指针调用了它指向的函数时,我们就把这样的使用方式称作回调函数。
2.回调函数的使用:
实例1:
//函数1:
//函数1使用了这样的调用方式,就被称为回调函数
void test()
{
printf("test\n");
}
//函数2:
//使用函数指针接收,并对函数test进行调用:
void TEST(void(*p)())
{
//使用函数指指针调用test函数:
p();
printf("TEST\n");
}
int main()
{
//将函数地址传递给另一个函数:
TEST(test);
return 0;
}
在这个过程中,函数 test 就被作为函数参数传递了出去,并且在函数 TEST 中被函数指针接收,且该指针调用了它指向的 test 函数,于是我们就可以说 test 函数是回调函数。
实例2:
我们之前通过转移表简化了计算器代码的书写计算器的书写,我们是否可以用回调函数简化呢?
void menu()
{
printf("*******************************\n");
printf("****** 1. add 2. sub *****\n");
printf("****** 3. mul 4. div *****\n");
printf("****** 0. exit *****\n");
printf("*******************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
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;
}
3.qsort函数的使用:
我们之前学过使用冒泡排序,用冒泡排序写一个函数来对整形数组进行升序降序,但是局限就是只能对整形,对其他类型无法排序。而qsort函数对于任意类型都可以排序,整形,字符型,甚至结构体型都可以,那么我们该如何·使用·qsotr函数呢?
库函数 qsort 是基于快速排序法实现的一个排序函数。
我们在cplusplus上看看用法:
函数括号里到底传入了什么参数呢?
这里的compare函数如何书写呢?
不难发现,在升序排序中,compare是将两个要排序的元素的地址作为参数,将他们比较大小的差作为返回值。
如果结果>0,返回正数
如果结果=0.返回0
如果结果<0,返回负数
那么就会有同学问了,那么降序怎么办?
如果降序的话就让两个值交换位置相减即可。
那么就会有同学问了,那个void*是什么类型,不同类型如何转化?
下面我们就来看看结构体的排序吧!
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;
}
//按学生的姓名排序
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[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(s) / sizeof(s[0]);
//qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
这里需要注意的是strcmp的使用,他对于两个字符串的每个字符一一对比,如果大于返回正数,等于就往后继续比,小于就返回负数。这和我们要返回的数值正好对应。
下面,我们就通过冒泡排序来实现qsort函数吧!
4.利用回调函数模拟实现qsort函数:
首先我们回忆一下冒泡排序:
#include <stdio.h>
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 - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 1,2,3,4,0,8,9,7,6,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这样的冒泡排序已经写死了整形,不能比较其他类型。
我们设计一个兼容可以排序整型,又可以排字符型和结构体的呢
模拟qsort的思想,利用冒泡的形式
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++;
}
}
int cmp_int(void* e1, void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0, j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; 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] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++)
printf("%d ",arr[i]);
}
这里有两处非常重要的点:
首先是这个冒泡排序:
for (i = 0; i < sz - 1; i++) { for (j = 0; j < sz - 1 - i; j++) { if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width,width); } } }
因为我们不知道我们会对什么类型的变量排序,所以我们用char*强转指针,使这个指针的步长为一,后面就可以显现出width(类型大小)的作用了:
因为我们不知道类型,不能用[]下标访问传过来的数组,就只能用指针的形式,通过指针+-整数来访问这个数组的每个元素。我们根据+width的倍数,正好就是这个类型的步长。
其次是这个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++; } }
由于我们不知道要交换元素的类型,所以我们通过变量中拥有最小单位:一个字节的char类型来依次交换每个字节来实现元素的交换,这样是不是很简便呢?
我们在来看一看结构体类型的交换:
void swap(char* buf1, char* buf2, int width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_name(void* e1, void* e2)
{
return (strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name));
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(void* e1, void* e2))//这里等下看一下
{
int i, j;
for (i = 0; i < sz; i++)
{
for (j = 0; j < sz - 1 - i; 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()
{
//结构体排序
struct stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(s) / sizeof(s[0]);
//bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
结果:
不难发现,bubble_sort函数,swap函数都一模一样,都是写好了的,所以我们只要自己写出那个回调函数cmp函数(比较大小) 即可,这就是qsort的方便之处。
二.总结:
通过qsort函数的学习,我们对回调函数有了更深的理解。这里我们在复习一下qsort里的参数。
今天我们算是给我们的指针升级之路画上了一个完美的句号,至此我们关于指针的进阶的学习就告一段落了。 辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!