对于前一篇内容的补充,为了后面更方便的找到此内容
8. 回调函数
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
8.1 回调函数初步介绍
前面我们说过,下面代码改进后出现了冗余
如果我们想要进一步改进这段代码,首先就会想到把冗余的代码封装成一个函数,但是case的三个模块实现的功能是不一样的,那我们该如何进行封装呢?
如上,加 减 乘 除 需要分别调用这个函数,下面我们就要构造这个函数
如图我们将代码进行了封装,同时避免了代码的冗余
- 最终代码:
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 menu() { printf("***************************\n"); printf("***** 1.add 2. sub ****\n"); printf("***** 3.mul 4. div ****\n"); printf("***** 0.exit ****\n"); printf("***************************\n"); } //函数名是函数地址Add,函数地址应该放到函数指针里 //指针(*p)指向的函数有两个参数(int,int),返回类型是int void calc(int (*p)(int, int)) { int x = 0; int y = 0; int ret = 0; printf("请输入2个操作数:>"); scanf("%d %d", &x, &y); ret = p(x, y);//p==(*p)前面我们介绍过,调用函数并且传参,把x,y传进去,产生的结果放到ret里 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); }
对回调函数定义的理解:
8.2 qsort函数
qsort函数-c语言标准库提供的排序函数
排序方式有以下几种
qsort属于快速排序q(quick)
这里我们先回顾一下之前的冒泡排序
- 冒泡排序的代码:
#include <stdio.h> void bubble_sort(int arr[], int sz) { int i = 0; //趟数 for (i = 0; i < sz - 1; i++)//sz-1表示进行冒泡排序的趟数 { //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz-1-i; j++)//sz-1-i表示的是需要进行比较的对数 { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { //冒泡排序 //对整形数据进行排序 - 排序为升序 int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); print(arr, sz); return 0; }
但是,bubble_sort函数只能排序我们定义的类型 - 整型,如果想更加灵活,那我们就要用到qsort函数,通过查阅相关网站我们得到用法如下:
- 下面代码包含了void*的用法以及qsort函数的具体用法
#include <stdio.h> //冒泡排序 void bubble_sort(int arr[], int sz) { int i = 0; //趟数 for (i = 0; i < sz - 1; i++)//sz-1表示进行冒泡排序的趟数 { //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz-1-i; j++)//sz-1-i表示的是需要进行比较的对数 { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void test1() { //冒泡排序 //对整形数据进行排序 - 排序为升序 int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); print(arr, sz); } //qsort 可以排序任意类型的数据 //void qsort(void* base, //待排序数据的起始地址 // size_t num, //待排序数据的元素个数 // size_t size, //待排序数据元素的大小(单位是字节) // int (*cmp)(const void*, const void*) //比较2个元素大小的函数指针 // ); //int cmp_int(const void* e1, const void* e2) //{ // if (*(int*)e1 > *(int*)e2) // return 1; // else if (*(int*)e1 < *(int*)e2) // return -1; // else // return 0; //} //上面代码有些多余,这里做改进,意义是一样的 int cmp_int(const void* e1, const void* e2) { return (*(int*)e1 - *(int*)e2); } //测试qsort函数的功能 void test2() { int arr[]= { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr,sz,sizeof(arr[0]), cmp_int); print(arr, sz); } int main() { //test1(); test2(); /*char ch = 'w'; int i = 20;*/ //void*用法 //错误写法,类型不兼容 //float* pf = &i; //pf = &ch; //正确写法: /*void* p = &ch; p = &i;*/ //void*指针可以接收任意类型的指针地址 //但是void*不能解引用操作,需要强制类型转换 //p++;需要强制类型转换 //错误写法 //void* p = &i; //*p = 200; //正确写法 //void* p = &i; //*(int*)p = 200;//知道i是整型,强制类型转换为int型指针 //p++;//err,void型指针不能直接进行此操作,需要强制类型转换后才可使用 return 0; }
运行结果和错误如下
需要注意qsort是库函数,需要加 #include <stdlib.h>,代码中我们对cmp_int函数做了改进,其效果与之前类似,但是却减少了代码量,比较巧妙
通过代码注释中对void*的介绍,我们知道了
void*可以接收任意类型的指针
void*不能解引用操作,需要强制类型转换
void*后p++;需要强制类型转换
但是要比较的类型不同,定义cmp函数的方法也不同
- 使用库函数,qsort排序各种类型的数据:
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include<string.h> void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } //qsort 可以排序任意类型的数据 //void qsort(void* base, //待排序数据的起始地址 // size_t num, //待排序数据的元素个数 // size_t size, //待排序数据元素的大小(单位是字节) // int (*cmp)(const void*, const void*) //比较2个元素大小的函数指针 // ); //int cmp_int(const void* e1, const void* e2) //{ // if (*(int*)e1 > *(int*)e2) // return 1; // else if (*(int*)e1 < *(int*)e2) // return -1; // else // return 0; //} //上面代码有些多余,这里做改进,意义是一样的 int cmp_int(const void* e1, const void* e2) { return (*(int*)e1 - *(int*)e2); } //测试qsort函数排序整型数据 void test2() { int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); } struct Stu { char name[20]; int age; }; int cmp_stu_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } int cmp_stu_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } //测试qsort排序结构体数据 void test3() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; //按照名字比较 int sz = sizeof(s) / sizeof(s[0]); //qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); } int cmp_char(const void* e1, const void* e2) { return *(char*)e2 - *(char*)e1; } test4() { char ch[6] = { 'a', 'b', 'c', 'd', 'e', 'f' }; int sz = sizeof(ch) / sizeof(ch[0]); qsort(ch, sz, sizeof(ch[0]), cmp_char); int i = 0; for (i = 0; i < sz; i++) { printf("%c ", ch[i]); } printf("\n"); } int main() { //测试qsort函数排序整型数据 //test2(); //测试qsort排序结构体数据 //test3(); //测试排序字符数据 test4(); return 0; }
如图对test3;进行监视,可以清晰地看到代码执行前后的变化,变为了升序
那么利用qsort函数的原理
如何把冒泡排序改造得对于任意类型的数据都可以排序?
- 使用回调函数,模拟实现qsort(采用冒泡排序的方式)。
#include <stdio.h> #include <stdlib.h> #include<string.h> //用冒泡排序函数改造出一个类型于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++; } } void bubble_sort2(void* base, int sz,int width,int(*cmp)(const void*e1,const void*e2)) { int i = 0; //趟数 for (i = 0; i < sz - 1; i++)//sz-1表示进行冒泡排序的趟数 { //一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++)//sz-1-i表示的是需要进行比较的对数 { if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0) { //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } void print(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } //qsort 可以排序任意类型的数据 //void qsort(void* base, //待排序数据的起始地址 // size_t num, //待排序数据的元素个数 // size_t size, //待排序数据元素的大小(单位是字节) // int (*cmp)(const void*, const void*) //比较2个元素大小的函数指针 // ); //int cmp_int(const void* e1, const void* e2) //{ // if (*(int*)e1 > *(int*)e2) // return 1; // else if (*(int*)e1 < *(int*)e2) // return -1; // else // return 0; //} //上面代码有些多余,这里做改进,意义是一样的 int cmp_int(const void* e1, const void* e2) { return (*(int*)e1 - *(int*)e2); } //测试qsort函数排序整型数据 void test2() { int arr[] = { 2,1,3,7,5,9,6,8,0,4 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort2(arr, sz, sizeof(arr[0]), cmp_int); print(arr, sz); } struct Stu { char name[20]; int age; }; int cmp_stu_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } int cmp_stu_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } //测试qsort排序结构体数据 void test3() { struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} }; //按照名字比较 int sz = sizeof(s) / sizeof(s[0]); //qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_age); } int cmp_char(const void* e1, const void* e2) { return *(char*)e2 - *(char*)e1; } test4() { char ch[6] = { 'a', 'b', 'c', 'd', 'e', 'f' }; int sz = sizeof(ch) / sizeof(ch[0]); bubble_sort2(ch, sz, sizeof(ch[0]), cmp_char); int i = 0; for (i = 0; i < sz; i++) { printf("%c ", ch[i]); } printf("\n"); } int main() { //测试qsort函数排序整型数据 //test2(); //测试qsort排序结构体数据 //test3(); //测试排序字符数据 test4(); return 0; }
- 要点
这些代码都不会脱离规定
qsort 可以排序任意类型的数据 void qsort(void base, //待排序数据的起始地址
size_t num, //待排序数据的元素个数
size_t size, //待排序数据元素的大小(单位是字节)
int (cmp)(const void, const void) //比较2个元素大小的函数指针);**
- 结语:
铁铁们,介绍到这里本章内容就结束了,如果小伙伴还有不理解的内容,也不要担心,毕竟学习是一个循序渐进的过程嘛
文章中某些内容我们之前有介绍,所以只是一笔带过,还请谅解。
希望以上内容对大家有所帮助👀,如有不足望指出🙏