• C语言——指针完全版


    目录

    一、指针的运算

    1.1指针 +- 整数

    1.2指针 - 指针

    1.3void* 指针

    二、指针遍历数组

    2.1指针遍历数组

    1.了解数组名称的含义(&数组名和数组名的区别)。

    2.用指针遍历数组 

    三、指针数组、数组指针、函数指针

    3.1指针数组

    3.1.1指针数组的形式

    3.1.2指针数组的使用案例

    3.2数组指针

    3.3函数指针

    三、函数指针数组

    3.1函数指针数组的形式

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

    四、数组作为函数体参数

    五、函数中用数组作为返回值

    5.1返回静态局部数组的地址

    5.2返回文字常量区的字符串的地址

    5.3返回堆内容的地址

    5.4总结

    六、指针和数组笔试题

    6.1一维数组

    6.2指针存放字符串

    6.3二维数组

    七、指针笔试题

    7.1笔试题1

    7.2笔试题2

    7.3笔试题3

    7.4笔试题4

    7.5笔试题5 

    7.6笔试题6 

    7.7笔试题7 


    一、指针的运算

    1.1指针 +- 整数

    总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

    1.2指针 - 指针

    1. int main()
    2. {
    3. int arr[10] = { 0 };
    4. printf("%d\n", &arr[9] - &arr[0]);
    5. return 0;
    6. }

    当我们想用两个指针相减时,从上图我们可以猜测一下,输出的到底是两地址之间的元素个数还是两个地址的差值(byte)呢?

    通过运行我们可以知道,指针 - 指针的结果:指针和指针之间的元素个数。

    那么任何两个指针都可以相减吗?从上面的答案我们就可以得到:

    指针 - 指针的前提两个指针指向同一块区域,指针类型是相同的。

    那么如果用小指针 - 大指针,我们的结果就成了 -9 .

    所以我们可以得到更严谨一些的结论:

    指针 - 指针的结果:其绝对值为指针与指针之间的元素个数。

    1.3void* 指针

    void* 类型的指针 - 不能进行解引用操作符,也不能进行 + - 整数的操作

    void* 类型的指针是用来存放任意类型数据的地址

    void* 无具体类型的指针

    可以用 void* 指针接收任何类型指针,如果需要解引用 void* 指针,可以强制类型转换。

    二、指针遍历数组

    2.1指针遍历数组

    1.了解数组名称的含义(&数组名和数组名的区别)。

    数组名是数组首元素的地址。有两个例外:
    1. sizeof( 数组名 ) ,计算整个数组的大小, sizeof 内部单独放一个数组名,数组名表示整个数组。
    2. & 数组名,取出的是数组的地址。 & 数组名,数组名表示整个数组。
    除此 1,2 两种情况之外,所有的数组名都表示数组首元素的地址。

    我们来看一段代码了解&数组名和数组名的差别。

    1. int main()
    2. {
    3. int arr[10] = { 0 };
    4. printf("arr = %p\n", arr);
    5. printf("&arr= %p\n", &arr);
    6. printf("arr+1 = %p\n", arr+1);
    7. printf("&arr+1= %p\n", &arr+1);
    8. return 0;
    9. }

    根据上面的代码我们发现,其实 &arr arr ,虽然值是一样的,但是意义应该不一样的。
    实际上: &arr 表示的是 数组的地址 ,而不是数组首元素的地址。
    数组的地址 +1 ,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是 40.

    2.用指针遍历数组 

    既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。

    1. #include
    2. int main()
    3. {
    4.    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    5.    int *p = arr; //指针存放数组首元素的地址
    6.    int sz = sizeof(arr)/sizeof(arr[0]);//数组元素个数
    7.    for(int i = 0; i < sz; i++)
    8.   {
    9.        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
    10.   }
    11.    return 0;
    12. }

     可以看到,我们可以通过指针遍历数组的每一个元素:

    p+i 其实计算的是数组 arr 下标为i的地址
    那么 *(p+1) 就可以访问数组 arr 的每一个元素

    三、指针数组、数组指针、函数指针

    3.1指针数组

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

    3.1.1指针数组的形式

    我们先来看一下指针数组的样子(其他类型同理):

    int* arr[5];    //数组名称:arr  数组元素个数:5  数组元素的类型:int* 

    那么知道指针数组有什么用呢?

    3.1.2指针数组的使用案例

    我们已经知道数组名在绝大多数情况下表示的是数组首元素的地址,那么我们就可以用指针数组模拟出一个二维数组。

    1. int arr1[] = { 0,1,2,3,4,5 };
    2. int arr2[] = { 1,2,3,4,5,6 };
    3. int arr3[] = { 2,3,4,5,6,7 };
    4. int* arr[] = { arr1,arr2,arr3 };

    那我们应该怎么去使用这个模拟出来的二维数组呢?

    我们直接可以用它的下标进行访问。

    区别:

    我们模拟出这个数组和真正二维数组的区别其实就是真实的二维数组每个元素的元素地址都是连续的,而模拟出的二维数组它的每个一维数组地址并不是连续的。

    3.2数组指针

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

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

    我们知道整形、全精度浮点型等等都有一个数据类型,int、double......                                            当然在这里,数组指针也有它的数据类型。

    3.3函数指针

    我们可以先来看一段代码

    1. #include
    2. void test()
    3. {
    4. printf("hello!\n");
    5. }
    6. int main()
    7. {
    8. printf("%p\n", test);//打印test地址
    9. printf("%p\n", &test);//打印&test地址
    10. return 0;
    11. }

    从运行结果我们可以看出来,函数名和数组名其实具有一样的效果,但是,为了便于观察,以后在我们需要函数地址的时候,我们还是会用 &函数名 来实现

    如果我们想用指针存储上面的test函数,我们应该怎么办呢?

    void(*pf)() = &test;
    
    pf1先和*结合,说明pf1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

    由上面我们可以得到:

    1. int max(int a, int b)
    2. {
    3. if (a > b)
    4. return a;
    5. else
    6. return b;
    7. }
    8. int main()
    9. {
    10. int (*pf2)(int, int) = &max;
    11. return 0;
    12. }

    这样我们就可以清楚的了解到函数指针的形式。

    三、函数指针数组

    3.1函数指针数组的形式

    数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
    比如:
    int * arr [ 10 ];
    // 数组的每个元素是 int*
    那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
    (以上述 int max(int a, int b) 为例,假设函数指针数组存放的都是此类函数)
    int( * parr[ 10])( int, int);
    parr先和 [ ] 结合,表面parr是个数组。
    数组的内容是什么呢?
    int (*)(int, int) 类型的函数指针。

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

    我们以一个简单的计算器为例,来看看使用函数指针数组的便捷。
    准备内容:
    未使用函数指针数组时,我们需要根据用户输入情况,一个函数一个函数的调用:
    使用函数数组后,我们只需要把准备阶段的四个函数全部封装到一个函数指针数组中:

    再接下来使用的时候,我们就可以灵活运用:

    是不是简化了非常多!

    四、数组作为函数体参数

    定义函数体时,当参数为数组时,既可以用数组接收,也可以用指针接收,其中用数组接收与数组的元素下标无关。

    五、函数中用数组作为返回值

    返回数组其实就是返回指针。

    1. char* fun()
    2. {
    3. char str[100] = "hello world!";
    4. return str;
    5. }
    6. int main()
    7. {
    8. char* pf;
    9. pf = fun();
    10. printf("%s\n", pf);
    11. return 0;
    12. }

    5.1返回静态局部数组的地址

    上述代码有个明显缺陷:

    str 在 fun 函数体内创建,为临时变量,当我们在 main 函数中想用pf接收的时候, str 就已经被销毁了,这时候,我们就可以在 fun 函数内用 static 修饰我们的 str ,来延长它的生命周期。

    运行如图:

    5.2返回文字常量区的字符串的地址

    常量字符串是个常量,一直存在。

    5.3返回堆内容的地址

    malloc的头文件:

    #include

    malloc 为 str 动态申请内存,大小为100,单位是字节。                                                                动态申请的内存在函数结束后也不会被释放。

    堆区内容一直存在,直到 free 才释放。

    5.4总结

    返回的地址,地址指向的内存的内容得存在,才有意义。

    六、指针和数组笔试题

    6.1一维数组

    int a [ ] = { 1 , 2 , 3 , 4 };
    printf ( "%d\n" , sizeof ( a [ 1 ]));  //4    代表a[1]这个元素的大小
    printf ( "%d\n" , sizeof ( & a ));  //4/8  &数组名表示整个数组的地址,是地址大小就是4/8
    //字符数组 
    char arr [] = { 'a' , 'b' , 'c' , 'd' , 'e' , 'f' };
    printf ( "%d\n" , sizeof ( arr [ 1 ])); //1     代表arr[1]这个元素的大小
    printf ( "%d\n" , strlen ( * arr )); //error   编译器会报错,因为strlen用指针接收,应该传入数组
    如果 arr 为数组名(下列数据均放在 sizeof() 中)
    arr[0]  <----->  arr[1]   <----->   数组类型的大小
    arr[0] + 1  ==  arr[1]   <----->   数组类型的大小
    arr + x ==  &arr[x]      <----->  指针的大小

    6.2指针存放字符串

    char * p = "abcdef" ;
    printf ( "%d\n" , sizeof ( * p )); //1           p表示字符 'a' 的地址,*p为传入a
    printf ( "%d\n" , strlen ( p [ 0 ])); //error    p[0]表示字符 'a' ,并不是地址
    printf ( "%d\n" , strlen ( & p [ 0 ] + 1 )); //5   相当于 &p[1] , strlen 计算 "bcdef\0" 的大小
    如果 p 为指针(下列数据均放在 sizeof() 中)
    &p[0]  ==  p  !=  &p
    *p  == p[0]

    6.3二维数组

    int a [ 3 ][ 4 ] = { 0 };
    printf ( "%d\n" , sizeof ( a [ 0 ])); //16         数组名单独放在 sizeof 内部表示整个一维数组
    printf ( "%d\n" , sizeof ( * ( a [ 0 ] + 1 ))); //4   数组名表示首元素a[0][0]地址,+1代表a[0][1]
    printf ( "%d\n" , sizeof ( a [ 3 ])); //16         sizeof并不会访问数组,a[3]相当于a[1]

    七、指针笔试题

    笔试题原题:

    1. //笔试题1
    2. struct Test
    3. {
    4. int Num;
    5. char* pcName;
    6. short sDate;
    7. char cha[2];
    8. short sBa[4];
    9. }*p;
    10. //假设p 的值为0x100000。 如下表表达式的值分别为多少?
    11. //已知,结构体Test类型的变量大小是20个字节
    12. int main()
    13. {
    14. p = (struct Test*)0x10000000;
    15. printf("%p\n", p + 0x1);
    16. printf("%p\n", (unsigned long)p + 0x1);
    17. printf("%p\n", (unsigned int*)p + 0x1);
    18. return 0;
    19. }
    20. //笔试题2
    21. int main()
    22. {
    23. int a[4] = { 1, 2, 3, 4 };
    24. int* ptr1 = (int*)(&a + 1);
    25. int* ptr2 = (int*)((int)a + 1);
    26. printf("%x\n%x", ptr1[-1], *ptr2);
    27. return 0;
    28. }
    29. //笔试题3
    30. int main()
    31. {
    32. int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    33. int* p;
    34. p = a[0];
    35. printf("%d", p[0]);
    36. return 0;
    37. }
    38. //笔试题4
    39. int main()
    40. {
    41. int a[5][5];
    42. int(*p)[4];
    43. p = a;
    44. printf("%p\n%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    45. return 0;
    46. }
    47. //笔试题5
    48. int main()
    49. {
    50. int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    51. int* ptr1 = (int*)(&aa + 1);
    52. int* ptr2 = (int*)(*(aa + 1));
    53. printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    54. return 0;
    55. }
    56. //笔试题6
    57. int main()
    58. {
    59. char* a[] = { "work","at","alibaba" };
    60. char** pa = a;
    61. pa++;
    62. printf("%s\n", *pa);
    63. return 0;
    64. }
    65. //笔试题7
    66. int main()
    67. {
    68. char* c[] = { "ENTER","NEW","POINT","FIRST" };
    69. char** cp[] = { c + 3,c + 2,c + 1,c };
    70. char*** cpp = cp;
    71. printf("%s\n", **++cpp);
    72. printf("%s\n", *-- * ++cpp + 3);
    73. printf("%s\n", *cpp[-2] + 3);
    74. printf("%s\n", cpp[-1][-1] + 1);
    75. return 0;
    76. }

    7.1笔试题1

    p + 0x1 :已知 p 为 (struct test*) 类型指针,在前面我们也学习到 指针 +- 整数运算,指针变量的类型决定了其 +- 整数的步长,在这里struct test为20字节,故指针运算应加20,十六进制的20为14
    (unsigned long)p + 0x1 :我们知道只有指针类型 +- 整数时,才取决于其类型,故这里直接+1即可
    (unsigned int*)p + 0x1:这里 p 的类型变为 unsigned int* ,故 +- 整数的步长为 unsigned int 的大小 4

    7.2笔试题2

    对于第一个,我们比较容易理解:

    对于第二个,我们就要了解的更深入一些才能容易理解:

    我们已经知道 ptr2 指向的位置了,接下来就要了解数据在内存中是如何存储了。接下来我们来了解一下大端存储和小端存储。C语言——数据在内存中的存储_夜夜亮晶晶的博客-CSDN博客

    我们就简单介绍一下,想更详细了解的友友可以点击上方的链接。

     而我的计算机(大部分)使用的是小端存储,所以题中的数据其实是这样存储的:

    因为其类型为int*,所以还是从指向的位置开始向后拿去4个字节:00 00 00 02

    而编译器又会按照小端存储的逆方式输出:02 00 00 00

    7.3笔试题3

    这题有一个陷阱:二维数组中存放一维数组时,一维数组的数据是用 {  } 包住,而不是 (  ) 

    所以这题是逗号表达式,二维数组存放的数据是 1,3,5,0,0,0

    所以 a[0] 对应的就是 1 

    7.4笔试题4

     首先说明:

    a 的类型为 int(*)[5]   ||    p 的类型为 int(*)[4]                                                                                    两者虽然类型不同,但只会警告并不会报错

    7.5笔试题5 

    7.6笔试题6 

     

     

    7.7笔试题7 

    打印POINT 

    打印ER 

    打印ST

    打印EW

     

  • 相关阅读:
    Cannot find proj.db
    springboot+学生信息管理 毕业设计-附源码191219
    论文阅读09——《Deep Fusion Clustering Network》
    【云原生与5G】微服务加持5G核心网
    【python基础】python类的成员变量与成员函数的一些疑问
    47 VM.maxDirectMemory() 来自于哪里
    el-input限制输入整数等分析
    开源IDaaS方舟一账通ArkID系统内置OIDC 认证插件配置流程
    SpringMVC
    python uv 新一代包管理工具
  • 原文地址:https://blog.csdn.net/m0_75186846/article/details/132081257