【C进阶】指针(二)

简介: 【C进阶】指针(二)

六、函数指针数组

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

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. }

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


目录
相关文章
指针进阶(1)(下)
指针进阶(1)(下)
|
6月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
52 4
|
存储
指针进阶详解!!!,干货满满(下)
指针进阶详解!!!,干货满满
|
存储 C语言 C++
指针进阶(详解)
指针进阶(详解)
Day_13 > 指针进阶(2)
Day_13 > 指针进阶(2)
|
存储 C++
Day_12 > 指针进阶
Day_12 > 指针进阶
|
存储 编译器 C++
指针进阶详解!!!,干货满满(上)
指针进阶详解!!!,干货满满