• 带你学会指针进阶


    前言:

         指针可以说是c语言的灵魂所在了。之所以这样认为,是因为它可以直接操作内存,而当我们写程序时,内存的报错有时候直接导致程序的崩溃,这时就需要我们一步步去检查我们的代码。但同时也为我们提供了很大的便利,比如数组或者结构体变量传参时,就不需要我们为它再开辟一块同样大小的内存块,只需要将它的首地址传过去,就可以访问它所有的数据。那么可以说它是把双刃剑,我们如何使用它就显得尤为重要了,接下来我会详细的介绍它。

    目录

    字符指针

    🎈为什么是这样存呢?

    🎈字符指针常量

    🎈为什么存在这样的现象呢?

    指针数组

    数组指针 

    🧢为什么是这个样子呢?

    🧢数组指针写法的解读

    🧢数组指针的使用 

    🧢解读

    函数指针

    ⚽函数地址

    ⚽函数指针写法解读 

    ⚽函数指针调用函数 

     函数指针数组

    ⚽函数指针数组写法解读

    指向函数指针数组的指针

     🪖指向函数指针数组的指针写法解读

    回调函数

    🐵利用回调函数,改版冒泡排序,排序任何类型数据

    🐵核心思想

    🐵注意

    🐵代码实现

    小结


    字符指针

    1. #include <stdio.h>
    2. int main()
    3. {
    4. const char* ptr = "hello word";
    5. printf("%s", ptr);
    6. return 0;
    7. }

    我们可以清楚地看到ptr指向的是首元素的地址 。

    🎈为什么是这样存呢?

        首先ptr是一个char*类型的指针,如果认为是把“hello word”存进去显然也存不下,因为这块内存在申请时就是连续的内存块,所以我们只需要拿到它的首元素地址,就可以访问它整块内存了。

    🎈字符指针常量:

    1. int main()
    2. {
    3. char str1[] = "hello word";
    4. char str2[] = "hello word";
    5. const char* ptr1 = "hello word";
    6. const char* prt2 = "hello word";
    7. return 0;
    8. }

    我们可以清楚看到str1和str2的首地址是不一样的,而ptr1和ptr2的首地址又是一样的。

    🎈为什么存在这样的现象呢?

        str1和str2是向内存申请的不同的两个字符数组,那么不同的数组肯定在不同的内存块中存放,这和它的数组内容是没有关系的。

        ptr1和ptr2是const修饰的字符指针常量,它们指向的是常量字符串,而c/c++把常量字符串单独的存储在内存的一块区域,当用几个指针指向同一个常量字符串,它们指向的都是同一块内存空间。它们是常量字符串,是不可以修改的,意味着我们只可以读数据,所以我们只需要为它开辟一块内存空间即可。 

     指针数组

    1. int main()
    2. {
    3. int* arr[10] = { NULL };
    4. char* arr[10] = { NULL };
    5. return 0;
    6. }

    顾名思义,它强调的是数组,数组中存放的是指针,int *arr[10]存放的就是10个整形指针,char* arr[10]存放的就是10个字符指针。

    数组指针 

    顾名思义,它强调的是一个指针,存放的是整个数组的地址。

    1. int main()
    2. {
    3. int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    4. int(*pa)[10] = &arr;
    5. return 0;
    6. }

    可以清楚看见pa + 1跳过了40个字节,arr + 1跳过了4个字节。

    🧢为什么是这个样子呢?

        首先&arr和arr看上去一样的,但实际指向是不同的,arr指向的是数组的首元素的地址,&arr指向的是整个数组的地址。它只不过是用首元素地址来表示,所以它们的步长是不一致的。pa的步长是40个字节,arr的步长是4个字节。

    🧢数组指针写法的解读

        int(*pa)[10],pa是这个数组指针变量的指针名,它的类型是int(*)[10],指向的是10个整形的数组。

    🧢数组指针的使用 

    1. #include <stdio.h>
    2. void print(int(*pa)[3], int row, int col)
    3. {
    4. int i = 0;
    5. for (i = 0; i < row; i++)
    6. {
    7. int j = 0;
    8. for (j = 0; j < col; j++)
    9. {
    10. printf("%d ", pa[i][j]);
    11. }
    12. printf("\n");
    13. }
    14. }
    15. int main()
    16. {
    17. int arr[2][3] = {0,1,2,3,4,5};
    18. print(arr, 2, 3);
    19. return 0;
    20. }

    🧢解读:

         二维数组的传参,二维数组的数组名是首元素地址,即整个第一行数组的地址,所以传参时就需要数组指针取接收。

    函数指针

    1. #include <stdio.h>
    2. void fun()
    3. {
    4. printf("hello word\n");
    5. }
    6. int main()
    7. {
    8. fun();
    9. printf("%p\n", fun);
    10. printf("%p\n", &fun);
    11. void(*pf)() = fun;
    12. (*pf)();
    13. return 0;
    14. }

    ⚽函数地址

        可以清楚看见,fun和&fun是一样的,即函数的地址可以通过这两种方法得到。函数地址要想存起来,就得使用函数指针void(*pf)()存。

    ⚽函数指针写法解读 

    void(*pf)(),pf指向函数的指针名,类型是void(*)(),指向的函数返会空,函数没有参数。

    ⚽函数指针调用函数: 

    (*pf)(),对函数指针pf解引用找到fun函数,然后没有参数传递。

     函数指针数组

    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. int main()
    19. {
    20. int(*pfun[4])(int x, int y) = { Add,Sub,Mul,Div };
    21. //利用函数指针数组调用Add函数
    22. int sum = (*pfun[0])(2, 3);
    23. printf("%d", sum);
    24. return 0;
    25. }

    ⚽函数指针数组写法解读

        int(*pfun[4])(int x, int y),pfun先和[4]结合说明它是一个数组,int(*)(intx, int y)是pfun数组数据的类型,是一个函数指针,函数的返回类型是int,参数是(int x, int y)。

    指向函数指针数组的指针

    1. #include <stdio.h>
    2. int sum(int x, int y)
    3. {
    4. return x + y;
    5. }
    6. int main()
    7. {
    8. int ret = sum(2, 3);
    9. int(*pfun_arr[3])(int x, int y) = { sum };
    10. int(*(*ppfun_arr)[3])(int x, int y) = &pfun_arr;
    11. printf("%d", ret);
    12. return 0;
    13. }

    顾名思义,它强调的是一个指针,指向的是函数指针数组。

     🪖指向函数指针数组的指针写法解读:

        这个要对比去理解,比如数组指针:int(*pa)[5],pa先和*结合说明它是一个指针,它的类型是int(*)[5],指向的是5个整形的数组。可以看成就是在数组名前加一个 *,然后让它两先结合,就可以说这个指针是指向这个数组了。

        int(*pfun_arr[3])(int x, int y),这个是函数指针数组,数组名是pfun_arr,由于数组名前已经有一个 *,那么我们把(*ppfun_arr)括起来让它先和 * 结合,int(*(*ppfun_arr)[3])(int x, int y)就可以说它是指向函数指针数组的指针。

    回调函数

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

    🐵利用回调函数,改版冒泡排序,排序任何类型数据

    🐵核心思想:

        核心就是不同的数据类型比较方法不同,那么我们可以把比较那块抽离出来,写成一个函数。由于排序数据的类型我们自己清楚,所以只需要实现这个比较函数即可,然后把这个函数传入冒泡排序中比较那块即可。

    🐵注意:

        这里使用了void*指针,它可以强转成任何类型的指针,为的就是不同类型的比较。需要把每个数据的首地址传入比较函数,然后强转成需要比较的数据类型,进行每个数据的比较 。  

        不同数据的交换,也是不一样的,所以我们把数据以字节为单位进行交换。在传入交换函数时,需要把每个数据的首字节地址传入过去,还需知道每个数据的宽度(字节数),进行每个数据的交换。

    🐵代码实现:

    1. #include <stdio.h>
    2. typedef struct stu
    3. {
    4. char name[20];
    5. int age;
    6. char six[4];
    7. }stu;
    8. int cmp(const void* e1, const void* e2)
    9. {
    10. return (*(int*)e1 - *(int*)e2);
    11. }
    12. int cmp2(const void* e1, const void* e2)
    13. {
    14. return ((stu*)e1)->age - ((stu*)e2)->age;
    15. }
    16. void swap(char* a, char* b, int width)
    17. {
    18. //元素以字节为单位交换
    19. while (width--)
    20. {
    21. char tmp = *a;
    22. *a = *b;
    23. *b = tmp;
    24. a++;
    25. b++;
    26. }
    27. }
    28. void bubble_sort(void* arr, int sz, int width, int (*cmp)(const void* e1, const void* e2))
    29. {
    30. int i = 0;
    31. for (i = 0; i < sz - 1; i++)
    32. {
    33. int j = 0;
    34. for (j = 0; j < sz - 1 - i; j++)
    35. {
    36. if (cmp((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)
    37. {
    38. //保证以元素来交换,传过去每个元素的首地址,width控制元素宽度
    39. swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
    40. }
    41. }
    42. }
    43. }
    44. int main()
    45. {
    46. int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
    47. int sz = sizeof(arr) / sizeof(arr[0]);
    48. stu arr2[] = { {"aa",20,"男"},{"bb",18,"女"},{"cc",8,"男"} , {"dd",2,"无"} };
    49. int sz2 = sizeof(arr2) / sizeof(arr2[0]);
    50. bubble_sort(arr, sz, sizeof(int), cmp);
    51. bubble_sort(arr2, sz2, sizeof(arr2[0]), cmp2);
    52. int i = 0;
    53. for (i = 0; i < sz; i++)
    54. {
    55. printf("%d ", arr[i]);
    56. }
    57. return 0;
    58. }

    小结:

         指针这块理解和使用是相当重要的,我们在学习过程中难免出现内存的错误等其他类型的bug,所以我们需要大量的去写代码,发现bug,并且改正bug,相信坚持下去会有不一样的收获。  

  • 相关阅读:
    推荐几款简单易用的协作化项目管理工具
    .NET分布式Orleans - 7 - Streaming
    WebSocket学习笔记
    红绿正方形染色问题
    Curve 文件存储的缓存策略
    关于CSS 盒子模型的基础教程
    手把手教你语音识别
    【DS】Java实现顺序表
    338.比特位计数
    Chrome插件精选 — 扩展管理插件
  • 原文地址:https://blog.csdn.net/weixin_62353436/article/details/125235154