• 深度剖析C语言指针


    每天进步一点点,坚持带来大改变!!!

    目录

    前言:

    1.字符指针

    2.指针数组

    3.数组指针

    4.数组传参,指针传参

    5.函数指针

    6.函数指针数组

    7.指向函数指针数组的指针

    8.回调函数

    9.指针和数组笔试题练习

    10.指针笔试题练习

    11.总结: 

    前言:

    之前我们已经学过指针的一些简单知识:

    1.指针就是地址,是标识内存空间的编号。

    2.指针变量是用来存放变量的地址,可以通过指针找到变量在内存中存储。

    3.指针的大小占4/8个字节(32/64).

    4.指针的类型决定了指针加减整数的时候跳过几个字节,解引用的时候能够访问几个字节。

    5.指针的运算:指针减指针得到中间元素的个数。

    接下来我们继续探讨指针的更多知识,深入C语言指针相关的内容。

    1.字符指针

    char*

    第一种使用:

    内存布局:

    第二种使用:

    内存布局:

    关于字符指针的一道面试题: 

    1. #include<stdio.h>
    2. int main()
    3. {
    4. char* p1 = "abcdef";
    5. char* p2 = "abcdef";
    6. char arr1[] = "abcdef";
    7. char arr2[] = "abcdef";
    8. if (p1 == p2)
    9. printf("p1 == p2\n");
    10. else
    11. printf("p1 != p2\n");
    12. if (arr1 == arr2)
    13. printf("arr1 == arr2\n");
    14. else
    15. printf("arr1 != arr2\n");
    16. return 0;
    17. }

    原因解释: 因为"abcdef"是常量字符串,放在字符常量区,不能被修改,因此在内存中只有一份,所以p1和p2都是存放首字符a的地址,而arr1和arr2在内存中开辟两块不同的空间存放"abcdef",因此地址不相同。

    2.指针数组

    概念:类比推理
    整形数组,用来存放整形的数字,每个元素的类型是int
    字符数组,用来存放字符的数组,每个元素的类型是char

    指针数组,用来存放指针的数组,每个元素的类型是 数据类型*
     

    应用:打印一个二维数组

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int arr1[] = { 1,2,3,4,5 };
    5. int arr2[] = { 2,3,4,5,6 };
    6. int arr3[] = { 3,4,5,6,7 };
    7. int* parr[3] = { arr1,arr2,arr3 };
    8. //parr是一个整形指针数组,每个元素的类型是int*
    9. int i = 0;
    10. for (i = 0; i < 3; i++)
    11. {
    12. int j = 0;
    13. for (j = 0; j < 5; j++)
    14. {
    15. printf("%d ", *(*(parr + i) + j));
    16. }
    17. printf("\n");
    18. }
    19. return 0;
    20. }

     内存布局:

     3.数组指针

    概念:类比推理:

    整形指针,用来存放整形元素的地址  int*p;
    字符指针,用来存放字符元素的地址  char*p;

    数组指针,用来存放数组的地址  数据类型(*p)[X];
     

    1. int main()
    2. {
    3. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    4. int(*p)[10] = &arr;
    5. //p先和*结合说明p是一个指针变量,然后指向一个大小为10元素的数组
    6. return 0;
    7. }

    注:&数组名代表的是数组的地址

    数组指针的使用:不恰当的使用

    1. #include<stdio.h>
    2. int main()
    3. {
    4. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    5. int(*p)[10] = &arr;
    6. int i = 0;
    7. for (i = 0; i < 10; i++)
    8. {
    9. printf("%d ", *(*p + i));//等价于arr[i]
    10. //*p得到数组首元素的地址+i得到下个元素的地址,*得到这个值
    11. }
    12. return 0;
    13. }

    打印一维数组使用数组指针,比较麻烦,不建议使用。

    打印二维数组

    1. #include<stdio.h>
    2. void print(int(*p)[5], int row, int col)
    3. {
    4. int i = 0;
    5. for (i = 0; i < 3; i++)
    6. {
    7. int j = 0;
    8. for (j = 0; j < 5; j++)
    9. {
    10. printf("%d ", *(*(p + i) + j));
    11. //*(p+i)得到二维数组每一行首元素的地址+j解引用得到每一个元素。
    12. //等价与p[i][j]
    13. }
    14. printf("\n");
    15. }
    16. }
    17. int main()
    18. {
    19. int arr[3][5] = { { 1,2,3,4,5 }, { 2, 3, 4, 5, 6 } ,{ 3, 4, 5, 6, 7} };
    20. print(arr, 3, 5);
    21. //数组名代表数组首元素的地址,二维数组首元素又是一个一维数组
    22. //所以用数组指针接受,存放数组的地址。
    23. return 0;
    24. }

    总结:

    1. int arr[5];
    2. int *parr1[10];
    3. int (*parr2)[10];
    4. int (*parr3[10])[5];

    int arr[5] : 是一个整形数组,数组有五个元素,每个元素的类型是int;
    int* parr1[10]:是一个指针数组,数组有10个元素,每个元素的类型是int*;
    int(*parr2)[10]:是一个数组指针,该指针指向的数组有10个元素,每个元素的类型是int
     该指针变量的类型是int(*)[10];

    int(*parr3[10])[5]:parr3先和[]结合说明这是一个数组,该数组有10个元素,每个元素的类型是int(*)[5](数组指针);
     

    4.数组传参,指针传参

    1.数组传参

    一维数组传参:

    1. void test1(int arr[])//ok
    2. {}
    3. void test1(int arr[10])//ok
    4. {}
    5. void test1(int*arr)//ok
    6. {}
    7. void test2(int*arr[20])//ok
    8. {}
    9. void test2(int**arr)//ok arr2是一个指针用二级指针接受
    10. {}
    11. int main()
    12. {
    13. int arr1[10] = { 0 };
    14. int* arr2[20] = { 0 };
    15. test1(arr1);
    16. test2(arr2);
    17. return 0;
    18. }

    二维数组传参:

    1. void test(int arr[3][5])//ok
    2. {}
    3. void test(int arr[][])//err 二维数组传参行可以省略,列不能省略
    4. {}
    5. void test(int arr[][5])//ok
    6. {}
    7. void test(int(*arr)[5])//ok 数组名表示首元素的地址,而二维数组首元素表示第一行数组,
    8. //因此可以用数组指针来接受
    9. {}
    10. void test(int**arr)//err 二级指针变量是存放一级指针变量的地址
    11. {}
    12. void test(int*arr[5])//err指针数组用来接受指针,不能接受数组的地址
    13. {}
    14. int main()
    15. {
    16. int arr[3][5] = { 0 };
    17. test(arr);
    18. return 0;
    19. }

    2.指针传参:

    一级指针传参

    1. void test(int* p)//ok
    2. {}
    3. int main()
    4. {
    5. int a = 10;
    6. int* p = &a;
    7. test(p);
    8. return 0;
    9. }

    二级指针传参:

    1. void test(int** pp)
    2. {}
    3. int main()
    4. {
    5. int a = 10;
    6. int* p = &a;
    7. int** pp = &p;
    8. test(pp);//ok
    9. test(&p);//ok
    10. return 0;
    11. }

    5.函数指针

    用来存放函数的地址

    1. #include<stdio.h>
    2. int Add(int a, int b)
    3. {
    4. return a + b;
    5. }
    6. int main()
    7. {
    8. int a = 10;
    9. int b = 20;
    10. int ret = Add(a, b);
    11. printf("%p\n", Add);
    12. printf("%p\n", &Add);
    13. return 0;
    14. }

     如何将一个函数的地址存起来呢?

    1. #include<stdio.h>
    2. int Add(int a, int b)
    3. {
    4. return a + b;
    5. }
    6. int main()
    7. {
    8. int (*pf)(int, int) = &Add;//Add;
    9. //pf就是一个函数指针(int,int)函数参数的类型
    10. int ret = (*pf)(2, 3);
    11. //等价与(pf)(2,3);
    12. printf("%d\n", ret);
    13. return 0;
    14. }

    下面通过两段代码深入理解函数指针:

    第二段代码比较复杂,但是有相同的代码,函数指针,因此可以通过typedef重命名简化代码

    1. typedef void(*pf_t)(int);
    2. int main()
    3. {
    4. void (*signal(int, void(*)(int)))(int);
    5. pf_t signal(int pf_t);
    6. return 0;
    7. }

     函数指针的用途:

    模拟实现一个计算器:

    1. #include<stdio.h>
    2. void menu()
    3. {
    4. printf("*************************\n");
    5. printf("***1.Add 2.Sub ***\n");
    6. printf("***3.Mul 4.Div ***\n");
    7. printf("****** 0.exit ********\n");
    8. printf("*************************\n");
    9. }
    10. int Add(int x, int y)
    11. {
    12. return x + y;
    13. }
    14. int Sub(int x, int y)
    15. {
    16. return x - y;
    17. }
    18. int Mul(int x, int y)
    19. {
    20. return x * y;
    21. }
    22. int Div(int x, int y)
    23. {
    24. return x / y;
    25. }
    26. int main()
    27. {
    28. int a = 0;
    29. int b = 0;
    30. int ret = 0;
    31. int input = 0;
    32. do
    33. {
    34. menu();
    35. printf("请选择:");
    36. scanf("%d",&input);
    37. switch (input)
    38. {
    39. case 0:
    40. printf("退出计算器\n");
    41. break;
    42. case 1:
    43. printf("请输入两个操作数:");
    44. scanf("%d%d", &a, &b);
    45. ret = Add(a, b);
    46. printf("%d\n", ret);
    47. break;
    48. case 2:
    49. printf("请输入两个操作数:");
    50. scanf("%d%d", &a, &b);
    51. ret = Sub(a, b);
    52. printf("%d\n", ret);
    53. break;
    54. case 3:
    55. printf("请输入两个操作数:");
    56. scanf("%d%d", &a, &b);
    57. ret = Mul(a, b);
    58. printf("%d\n", ret);
    59. break;
    60. case 4:
    61. printf("请输入两个操作数:");
    62. scanf("%d%d", &a, &b);
    63. ret = Div(a, b);
    64. printf("%d\n", ret);
    65. break;
    66. default:
    67. printf("输入有误,请重新输入:\n");
    68. break;
    69. }
    70. } while (input);
    71. return 0;
    72. }

    在我们实现计算器的时候,代码中出现了许多重复冗余的代码,如何规避这些问题呢? 

    下面通过函数指针的方式来解决代码中出现重复的问题:

    改进后的代码:

    1. #include<stdio.h>
    2. void menu()
    3. {
    4. printf("*************************\n");
    5. printf("***1.Add 2.Sub ***\n");
    6. printf("***3.Mul 4.Div ***\n");
    7. printf("****** 0.exit ********\n");
    8. printf("*************************\n");
    9. }
    10. int Add(int x, int y)
    11. {
    12. return x + y;
    13. }
    14. int Sub(int x, int y)
    15. {
    16. return x - y;
    17. }
    18. int Mul(int x, int y)
    19. {
    20. return x * y;
    21. }
    22. int Div(int x, int y)
    23. {
    24. return x / y;
    25. }
    26. void Calc(int(*pf)(int, int))
    27. {
    28. int a = 0;
    29. int b = 0;
    30. int ret = 0;
    31. printf("请输入两个操作数:");
    32. scanf("%d%d", &a, &b);
    33. ret = pf(a, b);
    34. printf("%d\n", ret);
    35. }
    36. int main()
    37. {
    38. int input = 0;
    39. do
    40. {
    41. menu();
    42. printf("请选择:");
    43. scanf("%d", &input);
    44. switch (input)
    45. {
    46. case 0:
    47. printf("退出计算器\n");
    48. break;
    49. case 1:
    50. Calc(Add);
    51. break;
    52. case 2:
    53. Calc(Sub);
    54. break;
    55. case 3:
    56. Calc(Mul);
    57. break;
    58. case 4:
    59. Calc(Div);
    60. break;
    61. default:
    62. printf("输入有误,请重新输入:\n");
    63. break;
    64. }
    65. } while (input);
    66. return 0;
    67. }

    将函数的地址传过去,参数用函数指针来接受,然后通过函数指针调用这个函数。

    6.函数指针数组

    是一个数组,用来存放函数地址的数组

    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 Mul(int x, int y)
    10. {
    11. return x * y;
    12. }
    13. int Div(int x, int y)
    14. {
    15. return x / y;
    16. }
    17. int main()
    18. {
    19. //如何将上面四个函数的地址存放到一个函数指针数组呢?
    20. int(*pf[4])(int, int) = { Add,Sub,Mul,Div };
    21. //pf先和[]结合说明这是一个数组,数组有4个元素,每个元素的类型是
    22. //int(*)(int,int)函数指针
    23. return 0;
    24. }

    既然可以通过函数指针数组存放函数的地址,那么如何用函数指针数组实现计算器?

    1. #include<stdio.h>
    2. void menu()
    3. {
    4. printf("*************************\n");
    5. printf("***1.Add 2.Sub ***\n");
    6. printf("***3.Mul 4.Div ***\n");
    7. printf("****** 0.exit ********\n");
    8. printf("*************************\n");
    9. }
    10. int Add(int x, int y)
    11. {
    12. return x + y;
    13. }
    14. int Sub(int x, int y)
    15. {
    16. return x - y;
    17. }
    18. int Mul(int x, int y)
    19. {
    20. return x * y;
    21. }
    22. int Div(int x, int y)
    23. {
    24. return x / y;
    25. }
    26. int main()
    27. {
    28. int input = 0;
    29. int a = 0;
    30. int b = 0;
    31. int ret = 0;
    32. int(*parr[5])(int,int) = { 0,Add,Sub,Mul,Div };
    33. do
    34. {
    35. menu();
    36. printf("请选择:");
    37. scanf("%d", &input);
    38. if (input == 0)
    39. {
    40. printf("退出计算器\n");
    41. }
    42. else if (input >= 1 && input < 5)
    43. {
    44. printf("请输入两个操作数:");
    45. scanf("%d%d", &a, &b);
    46. ret = parr[input](a, b);
    47. printf("%d\n", ret);
    48. }
    49. else
    50. {
    51. printf("输入错误,请重新输入\n");
    52. }
    53. } while (input);
    54. return 0;
    55. }

    当使用函数指针数组的时候又简化了代码,并且在后续修改代码的时候提供了极大的方便

    7.指向函数指针数组的指针

    是一个指针,用来存放函数指针数组的地址:

    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 Mul(int x, int y)
    10. {
    11. return x * y;
    12. }
    13. int Div(int x, int y)
    14. {
    15. return x / y;
    16. }
    17. int main()
    18. {
    19. //函数指针数组
    20. int(*pfarr[4]) = {Add,Sub,Mul,Div };
    21. //ppfArr是一个指向函数指针数组的指针
    22. //ppfArr先和*结合,说明ppfArr是一个指针,[4]说明指针
    23. //指向的数组有4个元素,每个元素的类型是int(*)(int,int)
    24. int(*(*ppfArr)[4])(int, int) = &pfarr;
    25. return 0;
    26. }

    8.回调函数

    上面我们在用函数指针实现计算器的时候,写了一个Calc的函数,其实Calc就是一个回调函数,那如何理解回调函数呢?

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
    如何对一组整形数组进行排序呢?
    1. #include<stdio.h>
    2. void bubble_sort(int arr[], int sz)
    3. {
    4. int i = 0;
    5. for (i = 0; i < sz - 1; i++)
    6. {
    7. int j = 0;
    8. int flag = 1;//如果待排序的数组已经有序,则跳出循环
    9. for (j = 0; j < sz - 1 - i; j++)
    10. {
    11. if (arr[j] < arr[j + 1])
    12. {
    13. int tmp = arr[j];
    14. arr[j] = arr[j + 1];
    15. arr[j + 1] = tmp;
    16. flag = 0;
    17. }
    18. }
    19. if (flag == 1)
    20. {
    21. break;
    22. }
    23. }
    24. }
    25. int main()
    26. {
    27. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    28. int sz = sizeof(arr) / sizeof(arr[0]);
    29. bubble_sort(arr, sz);
    30. int i = 0;
    31. for (i = 0; i < sz; i++)
    32. {
    33. printf("%d ", arr[i]);
    34. }
    35. return 0;
    36. }

    排序成降序:

    当我们要对一个复杂的对象进行排序的时候,显然冒泡排序不能适用,那如何要对一个复杂对象进行排序呢?

    下面来介绍一个库函数qsort()

    1. #include<stdlib.h>//包含头文件
    2. int main()
    3. {
    4. void qsort(void* base, //待排序的起始位置
    5. size_t num, //待排序的个数
    6. size_t width, //待排序元素的大小,单位是字节
    7. int( * cmp)(const void* e1, const void* e2));
    8. //是一个函数指针,该函数指针指向的函数有两个参数,e1,e2,函数返回类型是int.
    9. return 0;
    10. }
    11. //int cmp(const void* e1, const void* e2)
    12. //Return Value Description
    13. //< 0 e1 less than e2
    14. //0 e1 equivalent to e2
    15. //> 0 e1 greater than e2

    使用库函数qsort排序整形数组:

    1. #include<stdlib.h>
    2. #include<stdlib.h>
    3. int cmp_int(const void* e1, const void* e2)
    4. {
    5. return *(int*)e1 - *(int*)e2;
    6. }
    7. int main()
    8. {
    9. int arr[10] = { 9,8,7,6,5,4,3,2,1,0};
    10. int sz = sizeof(arr) / sizeof(arr[0]);
    11. qsort(arr, sz, sizeof(arr[0]), cmp_int);
    12. int i = 0;
    13. for (i = 0; i < sz; i++)
    14. {
    15. printf("%d ", arr[i]);
    16. }
    17. return 0;
    18. }

    默认是升序排列:

    使用库函数排序复杂对象:

    按照一个人的年龄进行排序:

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. struct stu
    4. {
    5. int age;
    6. char name[20];
    7. };
    8. int by_cmp_age(const void* e1, const void* e2)
    9. {
    10. return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
    11. }
    12. int main()
    13. {
    14. struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} };
    15. int sz = sizeof(s) / sizeof(s[0]);
    16. qsort(s, sz, sizeof(s[0]), by_cmp_age);
    17. int i = 0;
    18. for (i = 0; i < sz; i++)
    19. {
    20. printf("%s %d ", s[i].name, s[i].age);
    21. printf("\n");
    22. }
    23. return 0;
    24. }

    按照一个人的姓名进行排序

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<string.h>
    4. struct stu
    5. {
    6. int age;
    7. char name[20];
    8. };
    9. int by_cmp_name(const void* e1, const void* e2)
    10. {
    11. return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
    12. }
    13. int main()
    14. {
    15. struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} };
    16. int sz = sizeof(s) / sizeof(s[0]);
    17. qsort(s, sz, sizeof(s[0]), by_cmp_name);
    18. int i = 0;
    19. for (i = 0; i < sz; i++)
    20. {
    21. printf("%s %d ", s[i].name, s[i].age);
    22. printf("\n");
    23. }
    24. return 0;
    25. }

    模拟实现库函数qsort:

    实现库函数qsort的一个参数是函数指针,而函数指针指向的函数参数是void*类型的指针。

    为什么会使用void*类型的指针呢?

    下面介绍一下void*的指针的作用

    模拟实现qsort

    1. #include<stdio.h>
    2. #include<string.h>
    3. struct stu
    4. {
    5. int age;
    6. char name[20];
    7. };
    8. int by_cmp_name(const void* e1, const void* e2)
    9. {
    10. return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
    11. }
    12. void Swap(char* buf1, char* buf2, int width)
    13. {
    14. int i = 0;
    15. for (i = 0; i < width; i++)
    16. {
    17. char tmp = *buf1;
    18. *buf1 = *buf2;
    19. *buf2 = tmp;
    20. buf1++;
    21. buf2++;
    22. }
    23. }
    24. void bubble_qsort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
    25. {
    26. int i = 0;
    27. for (i = 0; i < sz - 1; i++)
    28. {
    29. int j = 0;
    30. int flag = 1;
    31. for (j = 0; j < sz - 1 - i; j++)
    32. {
    33. //将传入的数据类型强转化为char*指针类型,然后进行一个字节一个字节的比较
    34. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
    35. {
    36. Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
    37. flag = 0;
    38. }
    39. }
    40. if (flag == 1)
    41. {
    42. break;
    43. }
    44. }
    45. }
    46. int main()
    47. {
    48. struct stu s[3] = { {18,"zhangsan"},{20,"lisi"},{19,"wangwu"} };
    49. int sz = sizeof(s) / sizeof(s[0]);
    50. bubble_qsort(s, sz, sizeof(s[0]), by_cmp_name);
    51. int i = 0;
    52. for (i = 0; i < sz; i++)
    53. {
    54. printf("%s %d ", s[i].name, s[i].age);
    55. printf("\n");
    56. }
    57. return 0;
    58. }

     

    9.指针和数组笔试题练习

    一维整形数组:

     一维字符数组:数组中不包含'\0'

    szieof

     strlen:

    数组中包含'\0'

    sizeof:

    strlen:

    字符指针:

    sizeof:

    strlen:

    二维数组:

    总结:sizeof(数组名),数组名表示整个数组的大小,计算整个数组的大小

    &数组名,数组名表示整个数组的地址,除此之外,所有的数组名都表示的是数组首元素的地址。

    10.指针笔试题练习

    试题一:

    试题二:

     试题三:

     试题四:

    试题五:

     

    试题六:

     

     试题七:

    试题八:

     

     

     

    11.总结: 

    以上是关于对C语言指针知识点的一个详细解剖,包含所有关于指针的知识点和指针相关的笔试题,希望能够在学习的路上互相帮助,共同进步,欢迎点赞留言评论!!!

  • 相关阅读:
    电影<哥,你好>
    五、计算机网络
    前端vue开发项目遇到“Use // eslint-disable-next-line to ignore the next line”
    IDEA:开发配置(2024版 建议收藏)
    爬虫过程和反爬
    Redis实战 - 15 Redis事务机制和乐观锁实现
    螺旋矩阵、旋转矩阵、矩阵Z字打印
    IDEA控制台中文乱码
    必应bing广告推广和谷歌google广告的区别
    笔记本电脑没有声音?几招恢复声音流畅!
  • 原文地址:https://blog.csdn.net/qq_65307907/article/details/125068747