• 指针进阶(2)


    6.函数指针数组

    数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
    比如:

    1. int *arr[10];
    2. //数组的每个元素是int*

    那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

    1. int (*parr1[10])();
    2. int *parr2[10]();
    3. int (*)() parr3[10];

    答案是:parr1
    parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
    是 int (*)() 类型的函数指针。

    如果我们想写一个加法函数,然后我们把这个函数的地址存放到pf1里面去,再写一个减法函数,将这个函数的地址存放到pf2里面去,但是我们如果写更多同类型的函数,都这样存放地址的话,就要重复这样的操作,所以我们可以创建一个函数指针数组,存放同类型函数指针。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. #include
    5. int Add(int x, int y)
    6. {
    7. return x + y;
    8. }
    9. int Sub(int x, int y)
    10. {
    11. return x - y;
    12. }
    13. int main()
    14. {
    15. //int (*pf1)(int,int) = &Add;
    16. //int (*pf2)(int,int) = ⋐
    17. int (*pfarr[2])(int,int)={&Add,&Sub};
    18. return 0;
    19. }

    函数指针数组的用途:转移表.
    例子:(计算器)

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. #include
    5. void menu()
    6. {
    7. printf("********************************");
    8. printf("****1.Add 2.Sub***************");
    9. printf("****3.Mul 4.Div***************");
    10. printf("**** 0.exit *******************");
    11. }
    12. int Add(int x, int y)
    13. {
    14. return x + y;
    15. }
    16. int Sub(int x, int y)
    17. {
    18. return x - y;
    19. }
    20. int Mul(int x, int y)
    21. {
    22. return x * y;
    23. }
    24. int Div(int x, int y)
    25. {
    26. return x / y;
    27. }
    28. void calc(int (*pf)(int,int))
    29. {
    30. int x = 0;
    31. int y = 0;
    32. int ret = 0;
    33. printf("请输入2个操作数:");
    34. scanf("%d %d", &x, &y);
    35. ret = pf(x, y);
    36. printf("ret = %d\n", ret);
    37. }
    38. int main()
    39. {
    40. int input = 0;
    41. menu();
    42. printf("请选择:>");
    43. scanf("%d", &input);
    44. do
    45. {
    46. case 1:
    47. printf("请输入2个操作数:");
    48. scanf("%d %d", &x, &y);
    49. ret = Add(x, y);
    50. printf("ret = %d\n", ret);
    51. break;
    52. case 2:
    53. printf("请输入2个操作数:");
    54. scanf("%d %d", &x, &y);
    55. ret = Sub(x, y);
    56. printf("ret = %d\n", ret);
    57. break;
    58. case 3:
    59. printf("请输入2个操作数:");
    60. scanf("%d %d", &x, &y);
    61. ret = Mul(x, y);
    62. printf("ret = %d\n", ret);
    63. break;
    64. case 4:
    65. printf("请输入2个操作数:");
    66. scanf("%d %d", &x, &y);
    67. ret = Div(x, y);
    68. printf("ret = %d\n", ret);
    69. break;
    70. case 0:
    71. printf("退出计算器\n");
    72. break;
    73. default:
    74. printf("选择错误, 重新选择\n");
    75. break;
    76. }
    77. } while (input);
    78. return 0;
    79. }

    使用函数指针数组的实现:

    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 Mul(int x, int y)
    10. {
    11. return x * y;
    12. }
    13. int Div(int x, int y)
    14. {
    15. return x / y;
    16. }
    17. int main()
    18. {
    19. int input = 0;
    20. int x = 0;
    21. int y = 0;
    22. int ret = 0;
    23. do
    24. {
    25. menu();
    26. printf("请选择:>");
    27. scanf("%d", &input);
    28. //函数指针数组 - 转移表
    29. int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
    30. // 0 1 2 3 4
    31. if (0 == input)
    32. {
    33. printf("退出计算器\n");
    34. }
    35. else if (input >= 1 && input <= 4)
    36. {
    37. printf("请输入2个操作数:");
    38. scanf("%d %d", &x, &y);
    39. ret = pfArr[input](x, y);
    40. printf("ret = %d\n", ret);
    41. }
    42. else
    43. {
    44. printf("选择错误,重新选择!\n");
    45. }
    46. } while (input);
    47. return 0;
    48. }

     未来如果想要在这个功能的基础上加上其他的功能,先把菜单改变一下,再把该功能的代码写出来,再把这个函数的地址放在函数指针数组里面去就可以了,这样还不会增加switch的长度,这种写法非常的巧妙,但同时也是有缺陷的,有约束。就是这些函数的返回类型和参数必须都是int,必须保持一模一样。

    7. 指向函数指针数组的指针

    指向函数指针数组的指针是一个 指针
    指针指向一个 数组 ,数组的元素都是 函数指针 ;
    如何定义?

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. #include
    5. void test(const char* str)
    6. {
    7. printf("%s\n", str);
    8. }
    9. int main()
    10. {
    11. //函数指针pfun
    12. void (*pfun)(const char*) = test;
    13. //函数指针的数组pfunArr
    14. void (*pfunArr[5])(const char* str);
    15. pfunArr[0] = test;
    16. //指向函数指针数组pfunArr的指针ppfunArr
    17. void (*(*ppfunArr)[5])(const char*) = &pfunArr;
    18. return 0;
    19. }

    8. 回调函数

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

     回调函数是一个非常重要的知识点,回调函数依赖函数指针,有了函数指针才能实现回调函数。

     我们可以使用回调函数简化上面的计算器。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. #include
    5. int Add(int x, int y)
    6. {
    7. return x + y;
    8. }
    9. int Sub(int x, int y)
    10. {
    11. return x - y;
    12. }
    13. int Mul(int x, int y)
    14. {
    15. return x * y;
    16. }
    17. int Div(int x, int y)
    18. {
    19. return x / y;
    20. }
    21. void calc(int (*pf)(int,int))
    22. {
    23. int x = 0;
    24. int y = 0;
    25. int ret = 0;
    26. printf("请输入2个操作数:");
    27. scanf("%d %d", &x, &y);
    28. ret = pf(x, y);
    29. printf("ret = %d\n", ret);
    30. }
    31. int main()
    32. {
    33. int input = 0;
    34. do
    35. {
    36. menu();
    37. printf("请选择:>");
    38. scanf("%d", &input);
    39. switch (input)
    40. {
    41. case 1:
    42. calc(Add);
    43. break;
    44. case 2:
    45. calc(Sub);
    46. break;
    47. case 3:
    48. calc(Mul);
    49. break;
    50. case 4:
    51. calc(Div);
    52. break;
    53. case 0:
    54. printf("退出计算器\n");
    55. break;
    56. default:
    57. printf("选择错误, 重新选择\n");
    58. break;
    59. }
    60. } while (input);
    61. return 0;
    62. }

     通过回调函数可以使得函数变得通用,拥有多种功能。

    首先演示一下qsort函数的使用:
    qsort是一个库函数,底层使用的是快速排序的方式,对数据进行排序的。这个函数可以直接使用,可以用来排序任意类型的数据。

    首先我们来回忆一下冒泡排序,冒泡排序的核心思想就是相邻的两个元素进行比较。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. #include
    5. void print_arr(int arr[], int sz)
    6. {
    7. int i = 0;
    8. for (i = 0; i < sz; i++)
    9. {
    10. printf("%d ", arr[i]);
    11. }
    12. printf("\n");
    13. }
    14. void bubble_sort(int arr[], int sz)
    15. {
    16. //趟数
    17. int i = 0;
    18. for (i = 0; i < sz - 1; i++)
    19. {
    20. //每一趟冒泡排序的过程
    21. int j = 0;
    22. for (j = 0; j < sz - 1 - i; j++)
    23. {
    24. if (arr[j] > arr[j + 1])
    25. {
    26. int tmp = arr[j];
    27. arr[j] = arr[j + 1];
    28. arr[j + 1] = tmp;
    29. }
    30. }
    31. }
    32. }
    33. int main()
    34. {
    35. //数据
    36. int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    37. int sz = sizeof(arr) / sizeof(arr[0]);
    38. print_arr(arr, sz);
    39. bubble_sort(arr, sz);//冒泡排序
    40. print_arr(arr, sz);
    41. return 0;
    42. }

     这个函数有个不好的地方就是只能排序整型数组,所以并不通用,但是qsort函数可以排序任意类型的数据。

    1. void qsort(void* base, //待排序数组的第一个元素的地址
    2. size_t num, //待排序数组的元素个数
    3. size_t size,//待排序数组中一个元素的大小
    4. int (* cmp)(const void* e1, const void* e2)//函数指针-cmp指向了一个函数,这个函数是用来比较两个元素的
    5. //e1和e2中存放的是需要比较的两个元素的地址
    6. );

    如果我们需要排序整型数组的话,我们就要自己写一个比较函数。

    void* 类型的指针 - 不能进行解引用操作符,也不能进行+-整数的操作。
    void* 类型的指针是用来存放任意类型数据的地址。
    void* 无具体类型的指针。

    写成void*指针的好处是在进行调用这个函数的时候可以根据自己的需求进行转换。

    所以我们需要将e1和e2进行强制类型转换成int*,将他们做差,将结果返回。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. int cmp_int(const void* e1, const void* e2)
    5. {
    6. return *(int*)e1 - *(int*)e2;
    7. }
    8. void print_arr(int arr[], int sz)
    9. {
    10. int i = 0;
    11. for (i = 0; i < sz; i++)
    12. {
    13. printf("%d ", arr[i]);
    14. }
    15. printf("\n");
    16. }
    17. //测试qsort排序整型数据
    18. void test1()
    19. {
    20. int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    21. int sz = sizeof(arr) / sizeof(arr[0]);
    22. print_arr(arr, sz);
    23. qsort(arr, sz, sizeof(arr[0]), cmp_int);
    24. print_arr(arr, sz);
    25. }
    26. int main()
    27. {
    28. //数据
    29. test1();
    30. //test2();
    31. //test3();
    32. return 0;
    33. }

    如果我们想排序结构体数组,那我们就写一个test2和test3,test2比较年龄,test3比较名字。

    所以这里我们要重新写一个比较函数,比较名字的话我们就先强制类型转换成struct Stu*,然后用->来访问,再进行作差。

    test3也需要写一个比较函数,所以我们也强制类型转换成struct Stu*,再用strcmp来比较,strcmp的返回值刚好是0,>0或者<0。

    1. struct Stu
    2. {
    3. char name[20];
    4. int age;
    5. };
    6. //结构体数据怎么比较呢?
    7. //1. 按照年龄比较
    8. //2. 按照名字比较
    9. int cmp_stu_by_age(const void* e1, const void* e2)
    10. {
    11. return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    12. }
    13. void test2()
    14. {
    15. struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
    16. int sz = sizeof(arr) / sizeof(arr[0]);
    17. qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    18. }
    19. int cmp_stu_by_name(const void* e1, const void* e2)
    20. {
    21. return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    22. }
    23. void test3()
    24. {
    25. struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
    26. int sz = sizeof(arr) / sizeof(arr[0]);
    27. qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    28. }

    今天的分享到这里就结束啦!谢谢老铁们的阅读,让我们下期再见。

  • 相关阅读:
    Unity 3D 简易对象池
    看完这篇,你也可以画出有趣的动态曲线
    谷粒学苑 —— 6、课程管理:课程发布页面1 —— 添加课程信息
    springboot+vue
    java学习--day8 (面向对象)
    三十而立学FPGA之UART
    MySQL常用操作
    盒子模型——内边距以及外边距以及外边距让盒子水平居中
    Django: 事务 transaction.atomic
    pycharm增加新的编译器
  • 原文地址:https://blog.csdn.net/2301_79035870/article/details/132906436