• 17.0、C语言——指针详解(3)


    17.0、C语言——指针详解(3)

    来看一个C语句【该语句出自《C陷阱与缺陷》这本书】:

    1. int main() {
    2. (*(void(*)())0)();
    3. return 0;
    4. }

            我们先把这段代码拆开来看 void(*)() 这其实是一个函数指针类型,该函数返回值为void,参数为无参,那么( void(*)() ) 0 表示把 0 强制类型转换为 void(*)() 类型,之后再解引用符 * ,表示获取该函数,最后一个括号表示调用该函数

            总结:把 0 强制类型转换成一个 函数指针类型,该函数指针指向的函数是一个无参无返回值类型的函数,然后当 0 变成一个指针类型之后对他进行解引用操作,去调用以 0 为地址处的该函数

    再来看第二个C语句:

    1. int main() {
    2. void(*signal(int, void(*)(int)))(int);
    3. return 0;
    4. }

            还是一样把这个语句拆开来看,signal( int, void(*)(int) ) 是一个函数,该函数有两个参数,第一个参数类型为 int,第二个参数类型为函数指针,把函数名和参数去掉后剩下的void(*)(int)就是signal函数的返回类型~
            总结:signal是一个函数声明;signal函数的参数有2个,第一个是int。第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void;signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void

    函数指针 typedef 重命名;向上面那个代码很复杂我们来简化一下:

    1. int main() {
    2. //简化 重命名为 pfun_t
    3. typedef void(*pfun_t)(int);
    4. pfun_t signal(int,pfun_t);
    5. return 0;
    6. }


            当我们 解引用符函数指针去调用函数的时候可以发现,不管我们用不用解引用符 都可以正常的调用到函数,代码如下所示:

    1. int add(int a,int b) {
    2. return a + b;
    3. }
    4. int main() {
    5. int (*padd)(int, int) = add;
    6. printf("%d", padd(1, 2));
    7. printf("%d", (*padd)(1, 2));
    8. return 0;
    9. }


    函数指针数组:        

    定义方式:int ( * parr1 [ 10 ] ) ();【这里 parr1 先与[ ] 结合成为数组,数组元素的类型是int(*)() 类型的函数指针】

    定义的方式和指针差不多就不过多介绍了,直接上代码:

    1. int add(int a,int b) {return a + b;}
    2. int sub(int a,int b) {return a - b;}
    3. int mul(int a,int b) {return a * b;}
    4. int div(int a,int b) {return a / b;}
    5. int main() {
    6. int(*parr[10])(int, int) = {add,sub,mul,div};
    7. //函数调用
    8. int i = 0;
    9. for (i = 0;i<4;i++) {
    10. printf("%d\n",parr[i](3,2));
    11. }
    12. return 0;
    13. }

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

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. int add(int a,int b) {return a + b;}
    4. int sub(int a,int b) {return a - b;}
    5. int mul(int a,int b) {return a * b;}
    6. int div(int a,int b) {return a / b;}
    7. int main() {
    8. int input = 0;
    9. int x = 0;
    10. int y = 0;
    11. int (*p[])(int, int) = {0,add,sub,mul,div};
    12. int arrLength = sizeof(p) / sizeof(p[1]);
    13. do {
    14. printf("请选择>\n");
    15. printf("***** 1.add 2.sub 3.mul 4.div *****\n");
    16. scanf("%d",&input);
    17. if (input >= 1 && input < arrLength) {
    18. printf("请输入两个操作数\n");
    19. scanf("%d%d", &x, &y);
    20. int ret = p[input](x, y);
    21. printf("result = %d\n", ret);
    22. }
    23. else if (input == 0) {
    24. printf("退出");
    25. }
    26. else {
    27. printf("输入的数字有误\n");
    28. }
    29. } while (input);
    30. return 0;
    31. }

    回调函数:

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

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

     还是上面的计算器的例子(这里用回调函数的方式实现):

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. int add(int a,int b) {return a + b;}
    4. int sub(int a,int b) {return a - b;}
    5. int mul(int a,int b) {return a * b;}
    6. int div(int a,int b) {return a / b;}
    7. int Calc(int (*p)(int,int)) {
    8. int x = 0;
    9. int y = 0;
    10. printf("请输入两个操作数\n");
    11. scanf("%d%d",&x,&y);
    12. return p(x, y);
    13. }
    14. int main() {
    15. int input = 0;
    16. int ret = 0;
    17. do {
    18. printf("请选择>\n");
    19. printf("***** 1.add 2.sub 3.mul 4.div *****\n");
    20. scanf("%d",&input);
    21. switch (input)
    22. {
    23. case 1:
    24. printf("result = %d\n", Calc(add));
    25. break;
    26. case 2:
    27. printf("result = %d\n", Calc(sub));
    28. break;
    29. case 3:
    30. printf("result = %d\n", Calc(mul));
    31. break;
    32. case 4:
    33. printf("result = %d\n", Calc(div));
    34. break;
    35. case 0:
    36. printf("退出");
    37. break;
    38. default:
    39. printf("输入有误\n");
    40. break;
    41. }
    42. } while (input);
    43. return 0;
    44. }

    回调函数可以很好的解决代码冗余的问题

    指向函数指针数组的指针:

    指向函数指针数组的指针是一个 指针 ,指针指向一个 数组,数组的元素都是 函数指针
    代码如下:

    1. int main() {
    2. int arr[10] = { 0 };
    3. int(*p)[10] = &arr;//取出数组的地址
    4. int(*p2)(int,int);//函数指针
    5. int(*parr[10])(int,int);//parr是一个数组-函数指针的数组
    6. int(*(*pparr)[10])(int,int) = &parr;//pparr是一个指向[函数指针数组]的指针
    7. //pparr是一个数组指针,指针指向的数组有10个元素
    8. //指向的数组的每个元素的类型是一个函数指针int(*)(int,int)
    9. return 0;
    10. }

    int ( * ( *pparr ) [10] )(int,int) = &parr;
            * 先与 pparr 结合说明这是一个指针,然后指向 [ ] 数组,把  ( *pparr ) [10] 去掉之后就是指向的数组的每个元素的类型 -> int ( * )(int,int)

    qsort()库函数:

    1. void qsort(void* base,
    2. size_t num,
    3. size_t width,
    4. int(*cmp)(const void* e1,const void* e2));

    第一个参数是:需要比较的数组 地址;
    第二个参数是:数组中的元素个数;
    第三个参数是:每个元素占用空间的大小;
    第四个参数是:用来比较两个元素的函数地址【函数中的参数是传递两个比较元素的地址】;

    【void :表示没有规定特定类型的参数,所以可以接收任意类型的参数】
            我们之前说到过,指针类型决定了 解引用符能够访问的内存空间,但是 void* 类型的指针由于没有指定指针类型所以不能够 进行 解引用符操作~【不然会报错 非法的间接寻址】
            当然 指针 也不能进行 + -  整数的操作,因为+1 , -1 也需要指定指针的类型,不然也不知道要跳过多少字节

    在qsort()库函数中规定:

    当第一个元素小于第二个元素的时候,让比较函数返回 负数
    当第一个元素等于第二个元素的时候,让比较函数返回   0
    当第一个元素大于第二个元素的时候,让比较函数返回 正数

    实例代码如下所示【在调用qsort()库函数的时候记得引头文件:#include】:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. int com_int(const void* e1, const void* e2) {
    5. //这里由于void类型的指针不能进行解引用符操作,
    6. //所以先将他们强制类型转换为int*类型再进行解引用符操作
    7. return *(int*)e1 - *(int*)e2;
    8. }
    9. int main() {
    10. int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
    11. int sz = sizeof(arr) / sizeof(arr[0]);
    12. qsort(arr, sz, sizeof(arr[0]), com_int);
    13. int i = 0;
    14. for (i = 0; i < sz; i++) {
    15. printf("%d\n", arr[i]);
    16. }
    17. return 0;
    18. }

        输入结果为 0 1 2 3 4 5 6 7 8 9 ,排序成功~
    【注意:元素比较的函数返回值类型规定必须是整数】

    如果比较的是一个结构体的数组:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. #include
    4. #include
    5. struct Stu
    6. {
    7. char name;
    8. int age;
    9. };
    10. int cmp_stu_by_age(const void* e1,const void* e2) {
    11. return ((struct Stu*)e1)->age - ((struct Stu*)e1)->age;
    12. }
    13. int cmp_stu_by_name(const void* e1, const void* e2) {
    14. return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e1)->name);
    15. }
    16. int main() {
    17. struct Stu s[3] = { {"xiaoming",20},{"xiaolan",18}, {"xiaohong",26}};
    18. int sz = sizeof(s) / sizeof(s[0]);
    19. qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
    20. qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
    21. return 0;
    22. }

    【这里注意如果比较的是字符串的大小要用 strcmp()库函数去比较】

  • 相关阅读:
    MySQL 45 讲 | 13 为什么表数据删掉一半,表文件大小不变?
    线程同步的实现
    e智团队实验室项目-第二周-卷积神经网络的学习
    在CentOS7上增加swap空间
    APP备案流程详细解读
    kubernetes日志收集 fluent-operator 动态索引名的实现
    Rsync远程同步
    Kafka To HBase To Hive
    10年开发大佬,用300案例,附学习路线,详解多线程编程核心
    LVS DR模式负载均衡群集部署
  • 原文地址:https://blog.csdn.net/m0_52433668/article/details/126700011