• C语言之指针进阶篇(3)


    目录

    思维导图

    回调函数

    案例1—计算器

    案例2—qsort函数

    关于qsort函数  

    NO1. 

    NO2.

    NO3. 

    NO4. 

    演示qsort函数的使用

    案例3—冒泡排序 

    整型数据冒泡排序

    回调函数搞定各类型冒泡排序

    cmp_int比较大小

     cmp传参数

    NO1.

    NO2.

    解决方案

    交换swap

    总代码


    今天我们学习指针难点之回调函数🆗🆗🆗。

    首先我们用思维导图回顾一下前面的内容。

    思维导图

    回调函数

    回调函数就是一个通过函数指针调用的函数。

    如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

    回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

    案例1—计算器

    就前文我们学习的计算器,我们再用回调函数来解决一下!🆗🆗🆗

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. //计算器
    3. #include
    4. void meau()
    5. {
    6. printf("**************************\n");
    7. printf("** 1.add 2.sub ****\n");
    8. printf("** 3.mul 4.div ****\n");
    9. printf("** 0.exit *****\n");
    10. printf("**************************\n");
    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. int main()
    29. {
    30. int input = 0;
    31. int x = 0;
    32. int y = 0;
    33. int ret = 0;
    34. do
    35. {
    36. meau();
    37. printf("请选择>");
    38. scanf("%d", &input);
    39. switch (input)
    40. {
    41. case 1:
    42. printf("请输入2个操作数:");
    43. scanf("%d %d", &x, &y);
    44. ret = Add(x, y);
    45. printf("ret=%d\n", ret);
    46. break;
    47. case 2:
    48. printf("请输入2个操作数:");
    49. scanf("%d %d", &x, &y);
    50. ret = Sub(x, y);
    51. printf("ret=%d\n", ret);
    52. break;
    53. case 3:
    54. printf("请输入2个操作数:");
    55. scanf("%d %d", &x, &y);
    56. ret = Mul(x, y);
    57. printf("ret=%d\n", ret);
    58. break;
    59. case 4:
    60. printf("请输入2个操作数:");
    61. scanf("%d %d", &x, &y);
    62. ret = Div(x, y);
    63. printf("ret=%d\n", ret);
    64. break;
    65. case 0:
    66. printf("退出游戏");
    67. break;
    68. default:
    69. printf("选择错误,重新选择\n");
    70. break;
    71. }
    72. } while (input);
    73. return 0;
    74. }

    回调函数 

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. //计算器
    3. #include
    4. void meau()
    5. {
    6. printf("**************************\n");
    7. printf("** 1.add 2.sub ****\n");
    8. printf("** 3.mul 4.div ****\n");
    9. printf("** 0.exit *****\n");
    10. printf("**************************\n");
    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(*p)(int, int))//函数指针传参
    29. {
    30. int x = 0;
    31. int y = 0;
    32. printf("请输入两个操作数\n");
    33. scanf("%d %d", &x, &y);
    34. int ret = p(x, y);//函数调用
    35. printf("ret=%d\n", ret);
    36. }
    37. int main()
    38. {
    39. int input = 0;
    40. do
    41. {
    42. meau();
    43. printf("请选择>\n");
    44. scanf("%d", &input);
    45. switch (input)
    46. {
    47. case 1:
    48. calc(&Add);
    49. break;
    50. case 2:
    51. calc(&Sub);
    52. break;
    53. case 3:
    54. calc(Mul);
    55. break;
    56. case 4:
    57. calc(Div);
    58. break;
    59. case 0:
    60. printf("退出游戏");
    61. break;
    62. default:
    63. printf("选择错误,重新选择\n");
    64. break;
    65. }
    66. } while (input);
    67. return 0;
    68. }

    解释如下: 

    在main函数中,没有直接去调用函数。而是把函数指针传参给另外的一个函数calc,在calc内部使用函数指针调用,通过函数指针就可以找到指针指向的函数,此刻被指向的函数就是回调函数。

    像上图所示,通过calc函数调用Add函数指针,找到Add函数,就把Add函数称为回调函数

    calc称为回调函数的机制

    老板>>组长>>员工

    案例2—qsort函数

    那出了上面回调函数的案例,还有一个经典回调函数的案例:qsort

     qsort是一个库函数,底层使用的是快速排序的方式,对不同数据进行排序的。

    这个函数可以直接使用。

    这个函数可以用来排序任意类型的数据。

    对数据进行排序方法很多:

    冒泡排,序选择排序,插入排序,快速排序等等。 

    关于qsort函数  

    NO1. 

    关于qsort函数的点--->qsort - C++ Reference (cplusplus.com)

    需要包含头文件#include

    •  排序整型数组,两个整型可以直接使用>比较
    • 排序结构体数组,两个结构体的数据可能不能直接使用>比较

    也就是不同类型的数据,比较大小的方法是有差异的

    最后一个参数,排序不同数据的重要点,需要封装不同的函数去比较不同的数据的大小

    1. void qsort(void* base, //指向了待排序数组第一个元素的首地址
    2. size_t num, //待排序数组的元素个数
    3. size_t size,//每个待排序数组元素的大小
    4. int (*compar)(const void* e1, const void* e2));
    5. //函数指针,compar指向了一个函数,这个函数是用来比较两个元素的大小,
    6. //e1和e2存放的是两个元素的地址
    7. //在qsort内部调用这个函数,指向这个函数,这个函数就被称为回调函数
    8. //
    9. //qsort内部怎么排序我们不需要过多去探讨
    10. //const也暂不做讲解
    11. //因为不知道要比较的元素类型,所以我们使用void*指针的类型,来统一存放各种类型的指针
    NO2.

    那怎样通过元素地址,去比较两个整型元素数据的大小呢? 

    int的数据:将void*类型的数据强制转化成(int*),再作差

    当e1>e2,函数返回>0的值;

    当e1

    当e1=e2,函数返回=0

    1. //整型
    2. void compar_int(const void* e1, const void* e2)
    3. {
    4. return *(int*)e1 - *(int*)e2;
    5. }
    NO3. 

    那怎样通过元素地址,去比较两个字符元素数据的大小呢? 

     

     char的数据:strcmp库函数的使用,需要带头文件哦,#include

    strcmp - C++ Reference (cplusplus.com)

    1. //字符串
    2. void compar_stu_by_name(const void* e1, const void* e2)
    3. {
    4. return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
    5. //return strcmp((*(struct Stu*)e1).name , (*(struct Stu*)e2).name);
    6. }
    NO4. 

    那有人询问为什么不直接对元素地址const void* e1和 const void* e2解引用?

    作为void*指针不能直接解引用。

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

    1. #include
    2. int main()
    3. {
    4. char a = 'x';
    5. char* pa = &a;
    6. int b = 1;
    7. void* p = &b;//存放int*
    8. p = &a;//存放char*
    9. return 0;
    10. }

    演示qsort函数的使用

    1. #include
    2. #include
    3. void print(int arr[], int sz)
    4. {
    5. int i = 0;
    6. for (i = 0; i < 10; i++)
    7. {
    8. printf("%d ", arr[i]);
    9. }
    10. }
    11. void qsort(void* base,
    12. size_t num,
    13. size_t size,
    14. int (*compar)(const void*, const void*));
    15. void compar_int(const void* e1, const void* e2)
    16. {
    17. return *(int*)e1 - *(int*)e2;
    18. }
    19. void test1()
    20. {
    21. int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
    22. int sz = sizeof(arr) / sizeof(arr[0]);
    23. print(arr, sz);
    24. printf("\n");
    25. qsort(arr, sz, sizeof(arr[0]), compar_int);
    26. print(arr, sz);
    27. }
    28. int main()
    29. {
    30. test1();
    31. test2();
    32. return 0;
    33. }

    以上我只是以整型为例,结构体数据数组也是一样的逻辑,大家可以自行分析。

    下面结构体:

    1. #include
    2. #include
    3. #include//strcmp的头文件
    4. void qsort(void* base,
    5. size_t num,
    6. size_t size,
    7. int (*compar)(const void*, const void*));
    8. struct Stu
    9. {
    10. char name[20];
    11. int age;
    12. };
    13. //结构体数据怎么比较呢?
    14. //按照年龄比较
    15. //按照名字比较
    16. //按照年龄
    17. int compar_stu_by_age(const void* e1, const void* e2)
    18. {
    19. return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    20. //return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
    21. }
    22. void test2()
    23. {
    24. struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
    25. int sz = sizeof(arr) / sizeof(arr[0]);
    26. qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);
    27. }
    28. //按照名字
    29. int compar_stu_by_name(const void* e1, const void* e2)
    30. {
    31. return strcmp((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
    32. return strcmp((*(struct Stu*)e1).name ,(*(struct Stu*)e2).name);
    33. //return (*(struct Stu*)e1).name - (*(struct Stu*)e2).name;//❌❌
    34. }
    35. void test2()
    36. {
    37. struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
    38. int sz = sizeof(arr) / sizeof(arr[0]);
    39. qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);
    40. }
    41. int main()
    42. {
    43. test2();
    44. return 0;
    45. }
    • 记住字符串的比较必须用strcmp函数
    • 两种方式去访问结构体
    • 所有要使用的变量必须在它被使用之前就声明了!!例如结构体必须在比较函数之前声明。
    • 有返回值return 就不能是void!!

    只要qsort函数使用得当,可以对任何数据进行排序!🆗🆗 

    案例3—冒泡排序 

    (使用回调函数,模拟实现qsort(采用冒泡的方式)

    整型数据冒泡排序

    (这种方式只能排列整数,存在局限性)

    1. //冒泡排序
    2. #include
    3. void bubble_sort(int arr[], int sz)
    4. {
    5. int i = 0;
    6. for (i = 0; i < sz - 1; i++)
    7. {
    8. int j = 0;
    9. for (j = 0; j < sz - 1 - i; j++)
    10. {
    11. if (arr[j] > arr[j + 1])
    12. {
    13. int tmp = 0;
    14. tmp = arr[j];
    15. arr[j] = arr[j + 1];
    16. arr[j + 1] = tmp;
    17. }
    18. }
    19. }
    20. }
    21. void print_arr(int arr[], int sz)
    22. {
    23. int i = 0;
    24. for (i = 0; i < sz; i++)
    25. {
    26. printf("%d ", arr[i]);
    27. }
    28. }
    29. int main()
    30. {
    31. int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
    32. int sz = sizeof(arr) / sizeof(arr[0]);
    33. bubble_sort(arr, sz);
    34. print_arr(arr, sz);
    35. }
    回调函数搞定各类型冒泡排序

     经过分析冒泡排序,我们得到

    void bubble_sort(void* base, size_t num, size_t size,

                                int (*cmp)(const void* e1, const void* e2))

    cmp_int比较大小

    以整型为例 

    int (*cmp)(const void* e1, const void* e2)

    e1是一个指针,存放了一个要比较的元素的地址。

    e2是一个指针,存放了一个要比较的元素的地址。

    e1指向的元素>e2指向的元素,返回>0的数字。

    e1指向的元素0的数字。

    e1指向的元素==e2指向的元素,返回>0的数字。

    cmp是函数指针指向一个我们程序想要待排序的数组。

    将比较函数cmp_int的地址传给cmp即可。

    1. //比较大小
    2. void cmp_int(const void* e1, const void* e2)
    3. {
    4. return *(int*)e1 - *(int*)e2;
    5. }
    6. //这里就是将cmp_int的地址在调用函数bubble_sort时将其传过去即可。
     cmp传参数
    NO1.

    有同学提出直接对待排序的数组首元素地址解引用找到e1的地址,然后通过一个元素的大小或者+1可以找到e2的地址,可以吗?当然不可以

    • 作为void*指针不能直接解引用。

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

    NO2.

    有同学又提出那将void*的指针强制转换成我们想要的int*或double*等,再+1可以吗?           不可以,理由就是,强制转换存在在于我们公共的bubble_sort排序函数中时不能随着待排序的数组数据类型不同而改变,我们只能改变不同数据类型的不同比较方法。

    解决方案

     

    1. //if(arr[j]>arr[j+1])
    2. if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
    3. {
    4. int tmp = 0;
    5. tmp = arr[j];
    6. arr[j] = arr[j + 1];
    7. arr[j + 1] = tmp;
    8. }
    交换swap

    当我们只知道元素的起始地址,并不知道元素的类型所以我们并没有合适的中间值类型tmp创建。所以我们换一种方法。

    我们已知元素e1和e2的起始地址每个元素的大小

    那我们可以用一个一个char类型的数据交换用for循环

    直到每个元素的大小size结束,也就是元素交换完成。

     

    1. //交换数据
    2. void change(char* buf1, char* buf2,size_t size)
    3. {
    4. char i = 0;
    5. for (i = 0; i < size; i++)
    6. {
    7. char tmp = 0;
    8. tmp = *buf1;
    9. *buf1=*buf2;
    10. *buf2 = tmp;
    11. buf1++;//*buf1++
    12. buf2++;//*buf2++
    13. }
    14. }
    总代码
    1. //冒泡排序
    2. #include
    3. #include
    4. void print_arr(int arr[], int sz)
    5. {
    6. int i = 0;
    7. for (i = 0; i < sz; i++)
    8. {
    9. printf("%d ", arr[i]);
    10. }
    11. }
    12. void bubble_sort(void* base, size_t num, size_t size,
    13. int (*cmp)(const void* e1, const void* e2))
    14. {
    15. int i = 0;
    16. for (i = 0; i < num - 1; i++)
    17. {
    18. int j = 0;
    19. for (j = 0; j < num - 1 - i; j++)
    20. {
    21. //if(arr[j]>arr[j+1])
    22. if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
    23. {
    24. change((char*)base + j * size, (char*)base + (j + 1) * size, size);
    25. }
    26. }
    27. }
    28. }
    29. //交换数据
    30. void change(char* buf1, char* buf2,size_t size)
    31. {
    32. char i = 0;
    33. for (i = 0; i < size; i++)
    34. {
    35. char tmp = 0;
    36. tmp = *buf1;
    37. *buf1=*buf2;
    38. *buf2 = tmp;
    39. buf1++;//*e1++
    40. buf2++;//*e2++
    41. }
    42. }
    43. //比较大小
    44. void cmp_int(const void* e1, const void* e2)
    45. {
    46. return *(int*)e1 - *(int*)e2;//>0
    47. }
    48. void test1()
    49. {
    50. int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
    51. int sz = sizeof(arr) / sizeof(arr[0]);
    52. print_arr(arr, sz);
    53. printf("\n");
    54. bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    55. print_arr(arr, sz);
    56. }
    57. int main()
    58. {
    59. test1();
    60. }

    当然我们也可以用结构体类型去测试一下! 🆗🆗试试

    1. //冒泡排序
    2. #include
    3. #include
    4. #include
    5. //打印函数
    6. void print_arr(int arr[], int sz)
    7. {
    8. int i = 0;
    9. for (i = 0; i < sz; i++)
    10. {
    11. printf("%d ", arr[i]);
    12. }
    13. }
    14. //排序函数
    15. void bubble_sort(void* base, size_t num, size_t size,
    16. int (*cmp)(const void* e1, const void* e2))
    17. {
    18. int i = 0;
    19. for (i = 0; i < num - 1; i++)
    20. {
    21. int j = 0;
    22. for (j = 0; j < num - 1 - i; j++)
    23. {
    24. //if(arr[j]>arr[j+1])
    25. if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0)
    26. {
    27. change((char*)base + j * size, (char*)base + (j + 1) * size, size);
    28. }
    29. }
    30. }
    31. }
    32. //交换数据函数
    33. void change(char* buf1, char* buf2,size_t size)
    34. {
    35. char i = 0;
    36. for (i = 0; i < size; i++)
    37. {
    38. char tmp = 0;
    39. tmp = *buf1;
    40. *buf1=*buf2;
    41. *buf2 = tmp;
    42. buf1++;//*e1++
    43. buf2++;//*e2++
    44. }
    45. }
    46. //结构体
    47. struct Stu
    48. {
    49. char name[20];//20
    50. int age;//4
    51. };
    52. //结构体数据怎么比较呢?
    53. //按照年龄比较
    54. //按照名字比较
    55. //按照年龄
    56. void compar_stu_by_age(const void* e1, const void* e2)
    57. {
    58. return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    59. //return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
    60. }
    61. void test2()
    62. {
    63. struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
    64. int sz = sizeof(arr) / sizeof(arr[0]);
    65. qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);
    66. }
    67. //按照名字
    68. void compar_stu_by_name(const void* e1, const void* e2)
    69. {
    70. return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    71. //return strcmp((*(struct Stu*)e1).name , (*(struct Stu*)e2).name);
    72. }
    73. void test2()
    74. {
    75. struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };
    76. int sz = sizeof(arr) / sizeof(arr[0]);
    77. qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);
    78. }
    79. int main()
    80. {
    81. test2();
    82. }

    我们把回调函数这种情况叫做泛型编程。即便泛型编程在C语言中比较牵强。 

    最后,提出一个问题上面的题目我们都是正序排序。那如果我们想要倒序排序,代码又要怎样去修改呢?什么代码可以修改,什么代码不能修改呢?

    那其实我们也在上面提到过,我们的排序函数代码bubble_sort是不能修改的。

    以整型为例,所以我们只能修改比较大小的函数cmp_int

    ✔✔✔

    1. //倒叙
    2. //比较大小
    3. void cmp_int(const void* e1, const void* e2)
    4. {
    5. return *(int*)e2 - *(int*)e1;//>0
    6. }
    7. //正序
    8. //比较大小
    9. void cmp_int(const void* e1, const void* e2)
    10. {
    11. return *(int*)e1 - *(int*)e2;//>0
    12. }

    ✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!旗鼓相当

    代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com

    联系------→【邮箱:2784139418@qq.com】

  • 相关阅读:
    OpenPCDet解析
    【java学习—九】面向对象内容总结(8)
    网络安全是否有需求
    【2018】【论文笔记】最后一米THz——
    一文读懂VR数字展览会,从沉浸式体验到市场竞争力的全方位提升
    C. String Equality(思维)
    到底选择Vite还是Webpack?
    2023 第十二届中国智能产业高峰论坛 - 文档大模型的未来展望
    win10/Windows 10 纯净精简版美化
    VB6.0研发五金厂信息管理系统,研发适合小企业小工厂的信息系统
  • 原文地址:https://blog.csdn.net/m0_74841364/article/details/132779950