六、函数指针数组
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组
eg:
int *arr[10] //整形指针数组-数组-存放的是整形指针
char *arr[5] //字符指针数组-数组-存放的是字符指针
那么把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢
int (*parr【10】)( )
parr先和【】结合,说明parr是数组,那么数组的内容是什么呢?
是int(*)()类型的函数指针(从函数指针+数组名【】)
总结:
看pa和【】还是和*结合,
如果是和【】结合,那么pa就是数组
如果是和*结合,那么pa就是指针
eg:函数指针数组可以将类型为函数指针的元素放在一起
1. int Add(int x, int y) 2. { 3. return x + y; 4. } 5. int Sub(int x, int y) 6. { 7. return x - y; 8. } 9. int main() 10. { 11. int (*pf1)(int, int) = &Add; 12. int (*pf2)(int, int) = ⋐ 13. //数组中存放相同类型的多个元素 14. int (*pfArr[2])(int, int) = { &Add,&Sub }; 15. //pfArr是函数指针数组-存放函数指针的数组 16. return 0; 17. }
函数指针数组的用途:转移表
eg2:(计算器)
基础版:但是这个代码不好,如果想实现||,&&,&,|,>>,<<更多的功能还得重复写一些步骤,就很冗余
1. #include<stdio.h> 2. int add(int x, int y) 3. { 4. return x + y; 5. } 6. int sub(int x, int y) 7. { 8. return x - y; 9. } 10. int mul(int x, int y) 11. { 12. return x * y; 13. } 14. int div(int x, int y) 15. { 16. return x / y; 17. } 18. void menu() 19. { 20. printf("***********************************\n"); 21. printf("***1.add 2.sub***************\n"); 22. printf("***3.mul 4.div***************\n"); 23. printf("***0.exit *************************\n"); 24. printf("***********************************\n"); 25. 26. } 27. int main() 28. { 29. int x, y = 0; 30. int input = 0; 31. int ret = 0; 32. do 33. { 34. menu(); 35. printf("请选择:\n"); 36. scanf("%d", &input); 37. switch (input) 38. { 39. case 1: 40. printf("请输入2个操作数:\n"); 41. scanf("%d %d", &x, &y); 42. ret = add(x, y); 43. printf("ret=%d\n", ret); 44. break; 45. case 2: 46. printf("请输入2个操作数:\n"); 47. scanf("%d %d", &x, &y); 48. ret = sub(x, y); 49. printf("ret=%d\n", ret); 50. break; 51. case 3: 52. printf("请输入2个操作数:\n"); 53. scanf("%d %d", &x, &y); 54. ret = mul(x, y); 55. printf("ret=%d\n", ret); 56. break; 57. case 4: 58. printf("请输入2个操作数:\n"); 59. scanf("%d %d", &x, &y); 60. ret = div(x, y); 61. printf("ret=%d\n", ret); 62. break; 63. case 0: 64. printf("退出程序\n"); 65. break; 66. default: 67. printf("选择错误,请重新选择\n"); 68. break; 69. } 70. 71. } while (input); 72. 73. return 0; 74. }
使用函数指针数组实现:
这样就统一起来了,更加简洁,方便,如果要加功能,只需要改菜单,数组定义,input的范围
1. #include<stdio.h> 2. int add(int x, int y) 3. { 4. return x + y; 5. } 6. int sub(int x, int y) 7. { 8. return x - y; 9. } 10. int mul(int x, int y) 11. { 12. return x * y; 13. } 14. int div(int x, int y) 15. { 16. return x / y; 17. } 18. void menu() 19. { 20. printf("***********************************\n"); 21. printf("***1.add 2.sub***************\n"); 22. printf("***3.mul 4.div***************\n"); 23. printf("***0.exit *************************\n"); 24. printf("***********************************\n"); 25. 26. } 27. int main() 28. { 29. int x, y = 0; 30. int input = 1; 31. int ret = 0; 32. int (*p[5])(int, int) = { NULL,&add,&sub,&mul,&div };//转移表(加个NULL是为了和数组下标统一) 33. while (input) 34. { 35. menu(); 36. printf("请选择:\n"); 37. scanf("%d", &input); 38. if (input == 0) 39. { 40. printf("退出程序\n"); 41. } 42. else if (input <= 4 && input >= 1) 43. { 44. printf("请输入2个操作数:\n"); 45. scanf("%d %d", &x, &y); 46. ret = (*p[input])(x, y);//*可不写,和函数指针一样 47. printf("ret=%d\n", ret); 48. } 49. else 50. { 51. printf("输入错误,请重新输入\n"); 52. } 53. } 54. 55. return 0; 56. }
注意:这样改的条件是:函数的返回类型和参数类型一样
七、指向函数指针数组的指针
(这部分内容不重要,但是可以拓展视野)
类比:指向指针数组的指针 int*(*p)【3】=&arr
指向函数指针数组的指针是一个指针,指针指向一个数组,数组元素都是函数指针
eg:
int(*(*p)【5】)(int,int)=&pfArr p是指向函数指针数组的指针
int(* 【5】)(int,int)=&pfArr 函数指针数组类型
int(* )(int,int)=&pfArr 函数指针类型
八、回调函数
1.回调函数
回调函数就是一个通过函数指针调用的函数,如果你把函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用函数其指向的函数时,我们就说这是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
简单来说就是有两个函数,通过函数指针得到一个函数A(回调函数)的地址, 另一个函数
B(&A)实现间接调用A函数
注意:依赖函数指针才有回调函数
eg1:
计算器基础版简化:
这个部分每个case都在重复,那就用一个函数进行代替:
1. case 1: 2. printf("请输入2个操作数:\n"); 3. scanf("%d %d", &x, &y); 4. ret = add(x, y);//sub/mul/div 5. printf("ret=%d\n", ret); 6. break;
运用回调函数进行改进:
1. #include<stdio.h> 2. int add(int x, int y) 3. { 4. return x + y; 5. } 6. int sub(int x, int y) 7. { 8. return x - y; 9. } 10. int mul(int x, int y) 11. { 12. return x * y; 13. } 14. int div(int x, int y) 15. { 16. return x / y; 17. } 18. void menu() 19. { 20. printf("***********************************\n"); 21. printf("***1.add 2.sub***************\n"); 22. printf("***3.mul 4.div***************\n"); 23. printf("***0.exit *************************\n"); 24. printf("***********************************\n"); 25. 26. } 27. void calc(int (*pf)(int,int)) 28. { 29. int x, y = 0; 30. printf("请输入2个操作数:\n"); 31. scanf("%d %d", &x, &y); 32. int ret = pf(x, y); 33. printf("ret=%d\n", ret); 34. } 35. int main() 36. { 37. int input = 0; 38. do 39. { 40. menu(); 41. printf("请选择:\n"); 42. scanf("%d", &input); 43. switch (input) 44. { 45. case 1: 46. calc(add); 47. break; 48. case 2: 49. calc(sub); 50. break; 51. case 3: 52. calc(mul); 53. break; 54. case 4: 55. calc(div); 56. break; 57. case 0: 58. printf("退出程序\n"); 59. break; 60. default: 61. printf("选择错误,请重新选择\n"); 62. break; 63. } 64. 65. } while (input); 66. 67. return 0; 68. }
图解:
借助calc函数回调add,sub,mul,div等函数,是通过函数地址回调函数,逻辑就是进入case语句calc函数开始执行,当执行到ret=pf(x,y),又回调add等函数,回调结束又回到calc函数中
注意:add,sub,mul,div这些函数才是回调函数,而不是calc函数
eg2:
2.qsort快排:
对数据的排序方法有很多:冒泡排序,选择排序,插入排序,快速排序
为了对比qsort函数进行排序我们这里再来复习一下冒泡排序:
1. #include<stdio.h> 2. void print_arr(int* arr, int sz) 3. { 4. for (int i = 0; i < sz; i++) 5. { 6. printf("%d ", arr[i]); 7. } 8. printf("\n"); 9. } 10. void bubble_arr(int* arr, int sz) 11. { 12. for (int i = 0; i < sz-1; i++)//趟数 13. { 14. for (int j = 0; j < sz-i-1; j++)//每一趟冒泡排序 15. { 16. if (arr[j] > arr[j + 1]) 17. { 18. int tmp = arr[j]; 19. arr[j] = arr[j + 1]; 20. arr[j + 1] = tmp; 21. } 22. } 23. } 24. } 25. int main() 26. { 27. int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; 28. int sz = sizeof(arr) / sizeof(arr[0]); 29. print_arr(arr, sz); 30. bubble_arr(arr, sz); 31. print_arr(arr, sz); 32. 33. return 0; 34. }
但是这个冒泡排序只能排序int类型的数据,下面就来介绍qsort快排:
qsort函数是一个库函数,底层使用的快速排序的方式,对数据进行排序,这个函数可以直接使用,可以用来排序任意类型的数据
头文件:#include<stdlib.h>
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
qsort(被排序数组的初始位置,要排序的数组的元素个数,一个元素所占字节,比较函数)
重点是最后一个参数,比较函数:
如果要升序(elem1<elem2),则return<0,就要return elem1-elem2
如果要降序(elem1>elem2),则return>0,就要return elem2-elem1
1. #include<stdio.h> 2. #include<stdlib.h> 3. int int_cmp(const void* e1, const void* e2) 4. { 5. return (*(int*)e1 - *(int*)e2); 6. } 7. int main() 8. { 9. int arr[] = { 1,3,5,7,9,2,4,6,8,0 }; 10. qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), int_cmp); 11. for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) 12. { 13. printf("%d ", arr[i]); 14. } 15. printf("\n"); 16. return 0; 17. }
比较不同类型的数据,方法是有差异的:
(1)排序整形数据,两个整形可以直接直接使用><比较
(2)比较结构体数据,两个结构体的数据可能不能使用>比较
(eg:字符串的比较得用strcmp)
注意:比较函数(__cdecl *compare )的返回值一定为int类型
比较函数就是回调函数,比较时没有直接用比较函数(__cdecl *compare ),而是通过函数指针传给qsort函数
为什么用void*类型的指针 ?
void*指针:无具体类型的指针
不能进行解引用操作,也不能进行+-整数的操作
它是用来存放任意类型数据的地址的
结构体类型数据快排:
1. //结构体类型 2. struct Stu 3. { 4. char name[20]; 5. int age; 6. }; 7. 8. //int cmp_stu_by_age(const void* e1, const void* e2) 9. //{ 10. // return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; 11. //} 12. //int cmp_stu_by_name(const void* e1, const void* e2) 13. //{ 14. // return *(struct *)e1 - *(int*)e2; 15. //} 16. 17. int cmp_stu_by_name(const void* e1, const void* e2) 18. { 19. return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name); 20. } 21. int main() 22. { 23. 24. struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 12} }; 25. int sz = sizeof(arr) / sizeof(arr[0]); 26. 27. //qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); 28. 29. qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); 30. 31. return 0; 32. }
double类型的数据快排:这两种都可以
1. //浮点型数据 2. int cmp_double(const void* e1, const void* e2) 3. { 4. return (int)(*(double*)e1 > *(double*)e2); 5. } 6. //int cmp_double(const void* e1, const void* e2) 7. //{ 8. // return *(double*)e1 < *(double*)e2?-1:1 ; 9. //}
3.使用回调函数,模拟实现qsort(采用冒泡的方式)
如果像下面这样正常方法,的确是冒泡排序的方式,但是只能比较int类型的数据,而且没有回调函数
1. void bubble_sort(int arr[], int sz) 2. { 3. //冒泡排序的趟数 4. for (int i = 0; i < sz - 1; i++) 5. { 6. //一趟冒泡排序 7. for (int j = 0; j < sz - 1 - i; j++) 8. { 9. if (arr[j] > arr[j + 1]) 10. { 11. int tmp = arr[j]; 12. arr[j] = arr[j + 1]; 13. arr[j + 1] = tmp; 14. } 15. } 16. } 17. }
下面来改改bubble_sort函数的参数:
(1)参数1:void*base(数组)
第一个参数int *arr肯定不行,要是想比较结构体等其他类型的数据,那就考虑像qsort函数一样传void*base(void*类型的指针,无具体类型的指针,这样可以存放任意类型的地址)
(2)参数2:size_t num(数组中元素的个数)
第二个参数还是数组中元素的个数(size_t num),确定趟数等时候需要
(size_t类型表示C中任何对象所能达到的最大长度,它是无符号整形)
(头文件:#include<stddef.h>)
(3)参数3:size_t size(每个元素的大小)
由于要对每个元素进行比较,如果只有两个参数不知道怎么取出下一个元素,那就需要给出第三个参数每个元素的大小(size_t size)
(4)int (*cmp)(const void*e1,const void *e2) (函数指针)
由于结构体的比较不能用< >比较,所以引入参数函数指针int (*cmp)(const void*e1,const void *e2) (e1和e2分别是两个指针,存放一个要比较的元素的地址)
e1指向的元素>e2指向的元素,返回>0的数字
e1指向的元素==e2指向的元素,返回0
e1指向的元素<e2指向的元素,返回<0的数字
(5)cmp((char*)base+j*size,(char*)base+(j+1)*size)(比较对象的地址)
有了比较函数,那么怎么将arr【j】的和arr【j+1】的地址给e1和e2,如果是int类型的arr,那么先将void*base转化为int *类型,再加上j,即arr【j】的地址为(int *)base+j*size,但是如果是char类型的数组或者是结构体,那么又要改
为了统一,我们采用最小单元(char *),也就是将void*base强制转化为char *类型,然后再+j*size,那么最后比较就是cmp((char*)base+j*size,(char*)base+(j+1)*size))
(6)swap函数交换
如果if条件中的比较函数为真,那么就需要交换进行比较的元素,以前的int tmp肯定不行,如果要交换其他类型的数据,那么就考虑写个swap函数,把交换元素的地址传进去
那怎么交换元素呢,为了统一可以考虑一个字节一个字节(char*)的进行交换,并且用for循环控制结束,直到走完元素总字节数结束循环,因为不同类型数据所占的字节数不同,而且传参传的是地址,那么就需要将数据的size传进来,即swap(char* buf1 ,char* buf2,size_t size)
完整的代码如下:
1. #include<stddef.h> 2. #include<stdio.h> 3. void swap(char* buf1, char* buf2,size_t size)程序员写的内部逻辑 4. { 5. for (int i = 0; i < size; i++) 6. { 7. char tmp = * buf1; 8. *buf1 = *buf2; 9. *buf2 = tmp; 10. buf1++; 11. buf2++; 12. } 13. } 14. void bubble_sort(void*base, size_t num, size_t size,int (*cmp)(const void*e1,const void*e2))//程序员写的内部逻辑 15. { 16. for (int i = 0; i < num - 1; i++) 17. { 18. for (int j = 0; j < num - 1 - i; j++) 19. { 20. if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) 21. { 22. swap((char*)base + j * size, (char*)base + (j + 1) * size,size); 23. } 24. } 25. } 26. } 27. void print_arr(int *arr,int sz) 28. { 29. for (int i = 0;i < sz; i++) 30. { 31. printf("%d ", arr[i]); 32. } 33. printf("\n"); 34. } 35. int cmp_int(const void* e1, const void* e2)//自己使用时写的 36. { 37. return (int*)e2 - (int*)e1; 38. } 39. void test1() 40. { 41. int arr[] = { 0,1,2,3,4,5,6,7,8,9 }; 42. int sz = sizeof(arr) / sizeof(arr[0]); 43. print_arr(arr, sz); 44. bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); 45. print_arr(arr, sz); 46. } 47. struct Stu 48. { 49. char name[20]; 50. int age; 51. }; 52. int cmp_stu_by_name(const void* e1, const void* e2) 53. { 54. return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name); 55. } 56. int cmp_stu_by_age(const void* e1, const void* e2) 57. { 58. return (((struct Stu*)e1)->age -( (struct Stu*)e2)->age); 59. } 60. void test2() 61. { 62. struct Stu arr[] = { {"zhangsan",20},{"list,30"},{"wangwu",15} }; 63. int sz = sizeof(arr) / sizeof(arr[0]); 64. bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); 65. //bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); 66. 67. } 68. int main() 69. { 70. //测试bubble_sort排序整型数据 71. //test1(); 72. 73. //测试bubble_sort排序结构体数据 74. test2(); 75. return 0; 76. }
本次内容就到此啦,欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 !