• 【C进阶】指针(二)


    目录

    六、函数指针数组

    七、指向函数指针数组的指针

    八、回调函数

    1.回调函数

    2.qsort快排:

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

    (1)参数1:void*base(数组)

    (2)参数2:size_t num(数组中元素的个数)

    (3)参数3:size_t size(每个元素的大小)

    (4)int (*cmp)(const void*e1,const void *e2) (函数指针)

    (5)cmp((char*)base+j*size,(char*)base+(j+1)*size)(比较对象的地址)

    (6)swap函数交换


    六、函数指针数组

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

    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
    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. int main()
    27. {
    28. int x, y = 0;
    29. int input = 0;
    30. int ret = 0;
    31. do
    32. {
    33. menu();
    34. printf("请选择:\n");
    35. scanf("%d", &input);
    36. switch (input)
    37. {
    38. case 1:
    39. printf("请输入2个操作数:\n");
    40. scanf("%d %d", &x, &y);
    41. ret = add(x, y);
    42. printf("ret=%d\n", ret);
    43. break;
    44. case 2:
    45. printf("请输入2个操作数:\n");
    46. scanf("%d %d", &x, &y);
    47. ret = sub(x, y);
    48. printf("ret=%d\n", ret);
    49. break;
    50. case 3:
    51. printf("请输入2个操作数:\n");
    52. scanf("%d %d", &x, &y);
    53. ret = mul(x, y);
    54. printf("ret=%d\n", ret);
    55. break;
    56. case 4:
    57. printf("请输入2个操作数:\n");
    58. scanf("%d %d", &x, &y);
    59. ret = div(x, y);
    60. printf("ret=%d\n", ret);
    61. break;
    62. case 0:
    63. printf("退出程序\n");
    64. break;
    65. default:
    66. printf("选择错误,请重新选择\n");
    67. break;
    68. }
    69. } while (input);
    70. return 0;
    71. }

    使用函数指针数组实现:

    这样就统一起来了,更加简洁,方便,如果要加功能,只需要改菜单,数组定义,input的范围

    1. #include
    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. int main()
    27. {
    28. int x, y = 0;
    29. int input = 1;
    30. int ret = 0;
    31. int (*p[5])(int, int) = { NULL,&add,&sub,&mul,&div };//转移表(加个NULL是为了和数组下标统一)
    32. while (input)
    33. {
    34. menu();
    35. printf("请选择:\n");
    36. scanf("%d", &input);
    37. if (input == 0)
    38. {
    39. printf("退出程序\n");
    40. }
    41. else if (input <= 4 && input >= 1)
    42. {
    43. printf("请输入2个操作数:\n");
    44. scanf("%d %d", &x, &y);
    45. ret = (*p[input])(x, y);//*可不写,和函数指针一样
    46. printf("ret=%d\n", ret);
    47. }
    48. else
    49. {
    50. printf("输入错误,请重新输入\n");
    51. }
    52. }
    53. return 0;
    54. }

    注意:这样改的条件是:函数的返回类型和参数类型一样


    七、指向函数指针数组的指针

    (这部分内容不重要,但是可以拓展视野)

    类比:指向指针数组的指针  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
    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. void calc(int (*pf)(int,int))
    27. {
    28. int x, y = 0;
    29. printf("请输入2个操作数:\n");
    30. scanf("%d %d", &x, &y);
    31. int ret = pf(x, y);
    32. printf("ret=%d\n", ret);
    33. }
    34. int main()
    35. {
    36. int input = 0;
    37. do
    38. {
    39. menu();
    40. printf("请选择:\n");
    41. scanf("%d", &input);
    42. switch (input)
    43. {
    44. case 1:
    45. calc(add);
    46. break;
    47. case 2:
    48. calc(sub);
    49. break;
    50. case 3:
    51. calc(mul);
    52. break;
    53. case 4:
    54. calc(div);
    55. break;
    56. case 0:
    57. printf("退出程序\n");
    58. break;
    59. default:
    60. printf("选择错误,请重新选择\n");
    61. break;
    62. }
    63. } while (input);
    64. return 0;
    65. }

    图解:

    借助calc函数回调add,sub,mul,div等函数,是通过函数地址回调函数,逻辑就是进入case语句calc函数开始执行,当执行到ret=pf(x,y),又回调add等函数,回调结束又回到calc函数中

    注意:add,sub,mul,div这些函数才是回调函数,而不是calc函数


    eg2:

    2.qsort快排:

    对数据的排序方法有很多:冒泡排序,选择排序,插入排序,快速排序

    为了对比qsort函数进行排序我们这里再来复习一下冒泡排序:

    1. #include
    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. return 0;
    33. }

    但是这个冒泡排序只能排序int类型的数据,下面就来介绍qsort快排:

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

    头文件:#include

    void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

    qsort(被排序数组的初始位置,要排序的数组的元素个数,一个元素所占字节,比较函数)

    重点是最后一个参数,比较函数:

    如果要升序(elem1

    如果要降序(elem1>elem2),则return>0,就要return elem2-elem1

    1. #include
    2. #include
    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. //int cmp_stu_by_age(const void* e1, const void* e2)
    8. //{
    9. // return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    10. //}
    11. //int cmp_stu_by_name(const void* e1, const void* e2)
    12. //{
    13. // return *(struct *)e1 - *(int*)e2;
    14. //}
    15. int cmp_stu_by_name(const void* e1, const void* e2)
    16. {
    17. return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
    18. }
    19. int main()
    20. {
    21. struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 12} };
    22. int sz = sizeof(arr) / sizeof(arr[0]);
    23. //qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    24. qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    25. return 0;
    26. }

    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

    (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指向的元素

    (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
    2. #include
    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. int main()
    68. {
    69. //测试bubble_sort排序整型数据
    70. //test1();
    71. //测试bubble_sort排序结构体数据
    72. test2();
    73. return 0;
    74. }

    本次内容就到此啦,欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 ! 

  • 相关阅读:
    【每日一练】勾股定理困难版
    使用github托管博客后添加clouldflare,用CDN加速时配置DNS遇到的问题
    人工智能前沿——未来AI技术的五大应用领域
    day16-测试自动化之selenium的PO模式
    智能化燃气场站建设4要点!
    Unity报错:Microsoft Visual C# Compiler version
    【安装Pytorch】
    [补题记录] Atcoder Beginner Contest 297(F)
    php反序列化基础
    【网络工程】6、防火墙介绍及配置实操
  • 原文地址:https://blog.csdn.net/qq_73017178/article/details/132745589