六、函数指针数组
我们已经知道了函数指针,它的类型是int(*)(int,int)。那么我们能否进行推广呢?将其推广成一个数组,答案是可以的。我们只需要加上一个[],就成了数组
如下代码所示
#include<stdio.h> int my_strlen(const char* p) { return 0; } int main() { //pf是函数指针 int (*pf)(const char*) = &my_strlen; //pfA是函数指针数组 int (*pfA[5])(const char*) = { my_strlen }; return 0; }
那么这个函数指针数组有什么用呢?我们这里举一个例子,比如说当我们想写一个计算器的时候,按照我们之前的方法是这样写的
#include<stdio.h> void meau() { 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; } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { meau(); printf("请输入你的操作>:\n"); scanf("%d", &input); switch (input) { case 1: printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); ret=Add(x, y); printf("%d\n", ret); break; case 2: printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("%d\n", ret); break; case 3: printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("%d\n", ret); break; case 4: printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("%d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); return 0; }
这样写是没问题,但是当未来计算器的功能逐渐增多的时候,那么这个代码就显得有点冗杂了。那么我们该如何解决这个问题呢?
我们可以这样做,使用一个函数指针数组来记录每一个函数的地址,我们通过下标i来进行调用函数,如下代码所示
#include<stdio.h> void meau() { 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; } int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div }; int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { meau(); printf("请输入你的操作>:\n"); scanf("%d", &input); if (input == 0) { printf("退出计算器\n"); } else if((input>=1) && (input<=4) ) { printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); ret = pf[input](x, y); printf("%d\n", ret); } else { printf("选择错误,请重新选择\n"); } } while (input); return 0; }
而这个函数指针数组其实还有一个名字叫做转移表,因为我们通过下标可以跳转到其他的函数
七、指向函数指针数组的指针
我们已经知道了函数指针数组,那么指向函数指针数组的指针其实也不难理解,就是将这个数组的地址取出来,放到一个指针里面去,这个指针的类型,就如同一个数组的地址放入一个数组指针中一样。
#include<stdio.h> int main() { //数组指针 int arr[10]; int(*pa)[10] = &arr; //指向函数指针数组的指针 int (*pf[5])(int, int); int (*(*ppf)[5])(int, int) = &pf; return 0; }
八、回调函数
1.回调函数的概念
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该时间或条件进行响应。
2.回调函数实现计算器
听概念似乎有点难以理解,我们用上面的计算器的那个例子
我们会发现在一开始的计算器的版本中,有太多的相似的部分,我们能否将其封装成一个函数呢?答案始可以的,我们这些的区别就只是函数的不一样的,所以我们只需要传一个函数的地址,通过函数指针来调用这个函数就可以了
#include<stdio.h> void meau() { 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; printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); int ret = pf(x, y); printf("%d\n", ret); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { meau(); printf("请输入你的操作>:\n"); 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.回调函数实现冒泡排序
我们知道冒泡排序的基本思想,就是两个相邻元素两两比较,假设有10个元素,第一趟比较9次,可以将一个最大的数放到最后面,然后第二趟比较8次,又可以搞定一个数,一共比较9次就能实现排序,这是它的基本实现
#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 - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { int arr[10] = { 10,9,8,7,6,5,4,3,2,1 }; 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快速排序这个库函数。
这个这里我们可以知道qsort有四个参数
第一个是一个base,它的类型是指针,也就是要被排序的数组要传入到这里,这个base的类型是void,目的是为了接收任意类型的数组,因为void类型就像一各垃圾桶,任意一个类型的指针的都可以传入进去。
第二个是num,它的类型是size_t,这个参数是传入数组的元素个数
第三个是size,这个参数的意思是数组的每个元素的类型的字节大小,也就是宽度
第四个是compar,这是一个函数指针,这个函数指针所指向的函数有两个参数,一个是e1,一个是e2,当e1的大小大于e2时,返回一个大于0的数,等于则返回0,小于则返回小于0的一个数,这个其实也就是一个回调函数,这个compar是需要我们自定义出来的
我们来使用一下这个库函数
如下代码所示,我们使用qsort去排序了一个整型数组
int cmp(const void* e1,const void* e2) { return (*(int*)e1) - (*(int*)e2); } int main() { int arr[10] = { 10,9,8,7,6,5,4,3,2,1 }; int sz = sizeof(arr) / sizeof(arr[0]); //bubble_sort(arr, sz); qsort(arr, sz, sizeof(arr[0]), cmp); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
那么我们接下来来实现排序一个结构体
#include<stdio.h> #include<stdlib.h> #include<string.h> struct Stu { char name[20]; int age; }; //按照年龄比较 int cmp_by_stu_age(const void* e1,const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } //按照名字比较 int cmp_by_stu_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } int main() { struct Stu s[3] = { {"zhangsan",20},{"lisi",50},{"wangwu",18} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_by_stu_age); qsort(s, sz, sizeof(s[0]), cmp_by_stu_name); return 0; }
那么我们现在会了qsort的使用,我们可不可以使用qsort来类比着写一个万能的冒泡排序呢?答案是可以的,我们现在就来实现
#include<stdio.h> #include<string.h> void Swap(char* buf1, char* buf2, size_t size) { int i = 0; for (i = 0; i < size; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, size_t sz, size_t size, int (*cmp)(const void*, const void*)) { size_t i = 0; for (i = 0; i < sz - 1; i++) { size_t j = 0; for (j = 0; j < sz - 1 - i; j++) { //如果前一个大于后一个的话 if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) { //交换 Swap((char*)base + j * size, (char*)base + (j + 1) * size, size); } } } } struct Stu { char name[20]; int age; }; //按照年龄比较 int cmp_by_stu_age(const void* e1,const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } //按照名字比较 int cmp_by_stu_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } int main() { struct Stu s[3] = { {"zhangsan",20},{"lisi",50},{"wangwu",18} }; int sz = sizeof(s) / sizeof(s[0]); bubble_sort(s, sz, sizeof(s[0]), cmp_by_stu_age); bubble_sort(s, sz, sizeof(s[0]), cmp_by_stu_name); return 0; }
这段代码需要的注意的是:为了让我们的普适性更强,所以我们需要的base参数必须是void类型,这个指针是void类型的话,为了确定我们的指针步长,我们需要传一个元素的字节大小,然后通过我们的char*类型的强制类型转化, 即可走到每一个元素的起始地址,有了起始地址,我们可以通过回调函数,来调用比较函数。根据比较函数输出的结果,来确定是否进行交换,由于我们不知道传了一个什么类型的数据,所以我们需要强制类型转化为char*类型,结合宽度,来进行一个字节一个字节的交换。这样一来,我们就实现了普适性更强的冒泡排序
总结
本小节讲解了函数指针数组,指向函数指针数组的指针,回调函数的使用,以及如何使用qsort和如何实现普适性更强的冒泡排序
如果对你有帮助的话,不要忘记一键三连哦!!!
想获得更多优质内容,那么一定要关注我哦!!!