目录
一.回调函数:
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里的参数。
今天我们算是给我们的指针升级之路画上了一个完美的句号,至此我们关于指针的进阶的学习就告一段落了。 辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!