• C语言—指针进阶(详解篇)


    目录

    1.字符指针

    1.1字符指针定义

    1.2 字符指针用法

    2.指针数组

    2.1 指针数组定义及使用

    3.数组指针

    3.1 数组指针定义

    3.2 &数组名和数组名 

     3.3 数组指针的基本用法

    4. 数组参数、指针参数 

    5. 函数指针 

    5.1 函数指针定义既基本使用

    5.2 有趣的代码 

    6. 函数指针数组 

    6.1 函数指针数组定义

    6.2 函数指针数组用法 

    6.3 指向函数指针数组的指针 

    7. 回调函数 

    7.1 回调函数定义

    7.2 回调函数使用 


    在初学指针的时候你是否也常常分不清楚 ’指针数组‘ 和 ’数组指针‘ 呢?

    结果是指针数组是数组,数组指针是指针,那快来学习一下这篇好文,更深刻的了解吧

    前面初阶指针中学习了一下指针的基本概念:

    1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
    2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
    3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
    4. 指针的运算
    http://t.csdn.cn/wsHNl​​​​​​

    1.字符指针

    1.1字符指针定义

    在指针类型中我们知道一种指针类型为:char*

    一般情况就是存放字符变量地址的指针

    1. int main()
    2. {
    3. char a = 'w';
    4. char* pc = &a;
    5. return 0;
    6. }

    除去存放字符变量地址这个用法外还有其他的用法:

    首先知道字符串常量:一对双引号括起来的字符序列

    1. int main()
    2. {
    3. const char* pc = "abcdef";
    4. //字符串"abcdef"是常量,不可被修改所以加上const修饰
    5. printf("%c\n", *pc); // 'a'
    6. printf("%s\n", pc); // "abcdef"
    7. return 0;
    8. }

    这么一串代码字符指针pc存放的是字符串"abcdef"的首元素地址,所以如果打印一个字符:解引用pc打印结果就是字符串的首元素‘a’,知道了首元素既可通过字符指针pc打印字符串

    1.2 字符指针用法

    字符指针经典面试题:题源《剑指offer》

    1. int main()
    2. {
    3. char str1[] = "hello world.";
    4. char str2[] = "hello world.";
    5. const char* str3 = "hello world.";
    6. const char* str4 = "hello world.";
    7. if (str1 == str2)
    8. printf("str1 and str2 are same\n");
    9. else
    10. printf("str1 and str2 are not same\n");
    11. if (str3 == str4)
    12. printf("str3 and str4 are same\n");
    13. else
    14. printf("str3 and str4 are not same\n");
    15. return 0;
    16. }

    输出结果:

    str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

    2.指针数组

    2.1 指针数组定义及使用

    指针数组是一个存放指针的数组。

    1. int main()
    2. {
    3. char* pch[5]; // 字符指针数组
    4. int* parr[5];// 整形指针数组
    5. char** ppch[5]; //二级字符指针数组
    6. return 0;
    7. }

    指针数组用法:将三个一维数组通过指针变成二维数组并输出打印

    1. int main()
    2. {
    3. int arr1[] = { 1,2,3,4,5 };
    4. int arr2[] = { 2,3,4,5,6 };
    5. int arr3[] = { 3,4,5,6,7 };
    6. int* arr[] = { arr1,arr2,arr3 };//定义一个指针数组存放arr1、2、3,的首元素地址
    7. int i = 0;
    8. for (i = 0; i < 3; i++)
    9. {
    10. int j = 0;
    11. for (j = 0; j < 5; j++)
    12. {
    13. // *(arr[i]+j)=arr[i][j] 通过i分别找出arr1、2、3并通过j找出所对于的数
    14. printf("%d ", arr[i][j]);
    15. }
    16. printf("\n");
    17. }
    18. return 0;
    19. }

    3.数组指针

    3.1 数组指针定义

    数组指针是指向数组的指针 

    数组指针表示形式:

    1. int main()
    2. {
    3. int arr[10] = { 0 };
    4. int(*p)[10] = &arr;//取出的arr数组的地址
    5. // 取出arr的地址,元素个数10,每个元素类型为int
    6. return 0;
    7. }

     解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
    这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

    区分:指针数组 和 数组指针

    3.2 &数组名和数组名 

    &数组名:取出的是整个数组的地址

    数组名:表示的是首元素的地址 

    http://t.csdn.cn/Pm7B9

    本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
    数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

     3.3 数组指针的基本用法

     例:将一个整形二维数组中的元素输出打印

    1. void print1(int arr[3][5],int x,int y)
    2. {
    3. int i = 0;
    4. int j = 0;
    5. for (i = 0; i < x; i++)
    6. {
    7. for (j = 0; j < y; j++)
    8. {
    9. printf("%d ", arr[i][j]);
    10. }
    11. printf("\n");
    12. }
    13. }
    14. void print2(int(*pa)[5], int x, int y)
    15. {
    16. //int(*pa)[5] 表示指向一个有5个int类型元素的数组,既arr首地址
    17. int i = 0;
    18. for (i = 0; i < x; i++)
    19. {
    20. int j = 0;
    21. for (j = 0; j < y; j++)
    22. {
    23. printf("%d ", (*(pa)+i)[j]);//*(pa)+i==pa[i]
    24. }
    25. printf("\n");
    26. }
    27. }
    28. int main()
    29. {
    30. int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
    31. print1(arr,3,5);//arr首元素地址是{1,2,3,4,5}
    32. print2(arr,3,5);
    33. return 0;
    34. }

    数组名arr,表示首元素的地址
    但是二维数组的首元素是二维数组的第一行
    所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    可以数组指针来接收

    4. 数组参数、指针参数 

    一维数组传参

    1. void test1(int arr [])//同样用整形数组接收
    2. {}
    3. void test2(int* str) //用一个同类型指针接收
    4. {}
    5. void test3(int arr[5])//同类型同大小接收(数组大小可忽略)
    6. {}
    7. void test1_1(int* parr[]) //相同整形指针数组接收
    8. {}
    9. void test2_2(int** str) //二级指针接收
    10. {}
    11. int main()
    12. {
    13. int arr[5] = { 0 };//一维数组
    14. int* parr[10] = { 0 };//指针数组
    15. test1(arr);
    16. test2(arr);
    17. test3(arr);
    18. test1_1(parr);
    19. test2_2(parr);
    20. return 0;
    21. }

     二维数组传参

    1. void test1(int arr[3][5])//相同类型二维数组
    2. {}
    3. void test2(int arr[][5])//二维数组传参,函数形参的设计只能省略第一个[]的数字。
    4. {}
    5. void test3(int(*pa)[5])//用数组指针接收
    6. {}
    7. int main()
    8. {
    9. int arr[3][5] = { 0 };
    10. test1(arr);
    11. test2(arr);
    12. test3(arr);
    13. return 0;
    14. }

    一级指针传参

    1. void test(int* p)//同类型指针接收
    2. {
    3. int i = 0;
    4. for (i = 0; i < 5; i++)
    5. {
    6. printf("%d ", *(p + i));
    7. }
    8. }
    9. int main()
    10. {
    11. int arr[5] = { 1,2,3,4,5 };
    12. int* pa = arr;
    13. test(pa);
    14. return 0;
    15. }

    二级指针传参

    1. void test(int** ppa)//相同类型二级指针接收
    2. {}
    3. int main()
    4. {
    5. int a =5;
    6. int* pa = &a;
    7. int** ppa = &pa;
    8. test(ppa);
    9. return 0;
    10. }

    5. 函数指针 

    5.1 函数指针定义既基本使用

    函数指针:存放函数地址的指针变量

     两个输出的都是test函数的地址

    1. void test()
    2. {}
    3. int main()
    4. {
    5. test();
    6. void (*p)() = &test; //函数指针,存放函数地址
    7. return 0;
    8. }

    pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

    函数指针基本使用

    1. int add(int x, int y)
    2. {
    3. return x + y;
    4. }
    5. int main()
    6. {
    7. int a = 0;
    8. int b = 0;
    9. int(*pa)(int, int) = &add;//存放add函数地址的函数指针
    10. printf("%d\n", (*pa)(10, 20));//直接通过解引用pa操作函数add
    11. return 0;
    12. }

    5.2 有趣的代码 

    下面是两个有趣的代码,简述大概意思。来源:《C陷阱与缺陷》 

    代码1:

     首先看到这么一个复杂的代码,我们先将其括号配对,使我们能更明确的看出所代表的含义

     简述:将整形0强制 类型转换成一个参数为无返回类型为void的函数指针地址,既0地址

    后调用地址为0的函数。

    代码2:

     简述:

    signal是一个函数声明
    signal函数的参数有2个,第一个是int。第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
    signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void

    6. 函数指针数组 

    6.1 函数指针数组定义

     函数指针数组:存放函数指针的数组

    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. int (*pf[4])(int, int) = { add,sub,mul,div };
    20. return 0;
    21. }

    pf 先和 [] 结合,说明 pf是数组,数组内容是:int (*) (int ,int );

    6.2 函数指针数组用法 

    函数指针数组用途:转移表 

    以计算器为例,函数指针数组用法:

    1. void mune()
    2. {
    3. printf("**********************\n");
    4. printf("** 1.add 2.sub **\n");
    5. printf("** 3.mul 4.div **\n");
    6. printf("******* 0.exit *******\n");
    7. }
    8. int add(int x, int y)
    9. {
    10. return x + y;
    11. }
    12. int sub(int x, int y)
    13. {
    14. return x - y;
    15. }
    16. int mul(int x, int y)
    17. {
    18. return x * y;
    19. }
    20. int div(int x, int y)
    21. {
    22. return x / y;
    23. }
    24. int main()
    25. {
    26. int n = 0;
    27. int x = 0;
    28. int y = 0;
    29. int(*pf[5])(int, int) = { 0,add,sub,mul,div };//定义5个下标,让数组下标和菜单选项对应
    30. //函数指针数组:转移表
    31. do
    32. {
    33. mune();
    34. printf("请选择:>");
    35. scanf("%d", &n);
    36. if (n >= 1 && n <= 4)
    37. {
    38. printf("请输入两位操作数:>");
    39. scanf("%d %d", &x, &y);
    40. printf("%d\n", pf[n](x, y));
    41. }
    42. else if (n == 0)
    43. {
    44. printf("退出\n");
    45. }
    46. else
    47. {
    48. printf("输入错误\n");
    49. }
    50. } while (n);
    51. return 0;
    52. }

    6.3 指向函数指针数组的指针 

    存放函数指针数组地址的指针变量

    1. void test(const char* str)
    2. {
    3. printf("%s\n", str);
    4. }
    5. int main()
    6. {
    7. //函数指针
    8. void(*p)(const char*) = &test;
    9. //函数指针的数组
    10. void(*parr[5])(const char*);
    11. //指向函数指针数组的指针
    12. void (*(*pfarr)[5])(const char*) = &parr;
    13. return 0;
    14. }

    7. 回调函数 

    7.1 回调函数定义

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

    1. void print(char* str)
    2. {
    3. printf("%s\n", str);
    4. }
    5. void test(void)
    6. {
    7. print("hello world!");
    8. }
    9. int main()
    10. {
    11. test();
    12. return 0;
    13. }

    7.2 回调函数使用 

    设定一个通用冒泡排序函数可以排序任意类型:参考函数qsort() 

    qsort ()函数详解:          http://t.csdn.cn/xkfQ8icon-default.png?t=M85Bhttp://t.csdn.cn/xkfQ8代码实现:

    1. //置换
    2. void permute(char* buf1, char* buf2, int width)//类型不同,一个字节一个字节进行置换
    3. {
    4. int i = 0;
    5. for (i = 0; i < width; i++)
    6. {
    7. char tmp = *buf1;
    8. *buf1 = *buf2;
    9. *buf2 = tmp;
    10. buf1++;
    11. buf2++;
    12. }
    13. }
    14. // base:任意类型数组地址,sz:数组长度,width:数组宽度(数组类型),比较参数函数
    15. void bubble_sort(void* base,int sz,int width,int(*cmp)(void* elem1,void*elem2))
    16. {
    17. //冒泡排序
    18. int i = 0;
    19. //趟数
    20. for (i = 0; i < sz-1; i++)
    21. {
    22. //每躺比较的对数
    23. int j = 0;
    24. for (j = 0; j < sz - i - 1; j++)
    25. {
    26. //比较两个参数
    27. if (cmp((char*)base+width*j, (char*)base+width*(j+1)) > 0)//比较该元素和后一个元素大小
    28. //传参任意类型,可通过一个字节+宽度就等于该数据类型,一个char类型加上一个int类型就等于跳过一个int类型
    29. {
    30. //置换
    31. permute((char*)base + width * j, (char*)base + width * (j + 1),width);
    32. }
    33. }
    34. }
    35. }
    36. int cmp_int(void* elem1, void* elem2)
    37. {
    38. return *(int*)elem1 - *(int*)elem2;
    39. }
    40. void test(void)
    41. {
    42. int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
    43. int sz = sizeof(arr) / sizeof(arr[0]);
    44. int i = 0;
    45. bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    46. for (i = 0; i < sz; i++)
    47. {
    48. printf("%d ", arr[i]);
    49. }
    50. }
    51. int main()
    52. {
    53. test();
    54. return 0;
    55. }

  • 相关阅读:
    软考 系统架构设计师系列知识点之软件可靠性基础知识(6)
    嵌入式linux sqlite3读写demo
    X86 SMAP(Supervisor Mode Access Prevention)机制引入的一个问题分析
    GPU深度学习环境搭建:Win10+CUDA 11.7+Pytorch1.13.1+Anaconda3+python3.10.9
    docker-compose 部署 flink
    uniapp h5 echarts 打包后图表点击失效/及其他失效
    Java教程之高阶源码分析-ConcurrentHashMap
    读取resources 目录资源文件的方法
    Excel转换为Lua的配置文件
    【Unity】rotation和Quaternion学习笔记
  • 原文地址:https://blog.csdn.net/qq_74009838/article/details/128032234