C语言的数组,指针,二级指针,指针数组和数组指针的简单理解

什么是指针

  C语言中的所有变量都是存储在一块内存中的。以32位机器为例,char型的变量存储在一块1字节的内存中,int型的变量存储在一块4字节的内存中。指针本质上也是个变量,也存储在一块4字节的内存中。只不过指针那块内存中存储的是一个地址而已。我们可以把某个变量的4字节地址存储在指针的那块内存中。想要操作变量的时候,先从指针的内存中得到那个变量的地址,然后在根据地址操作对应的那个变量的那块内存。

  int num = 10;

  int *p = #   // 取num的地址存在p指针的内存中

  *p = 100;          // 指针的解引用语法,从指针的内存中的到num的地址,然后操作num对应的内存。相当于num=100

数组名和指针的区别联系

  int array[10] = {0};

  int *p = array;

  array是数组名,数组第一个元素的地址,是一个常量,不能用于算数运算,array++, array+=1这些运算都是不允许的。指针和它的区别是,指针是一个变量,也就是说,指针的那块内存中你想存啥就存啥。比如 p += 1,,就相当于把指针那块内存中存的值改变了。int *p = array 相当于把array中第一个元素的地址存在指针对应的那块内存中。对数组名使用sizeof,会返回整个数组的大小。但是对指针使用sizeof,只会返回4字节(32位机器)。所以数组名和指针完全是两码事,数组名是常量,而指针本质上是个变量。它们唯一的联系是如下代码中展示的:

  void func(int a[]);

  void func(int *a);

  以上两种声明方式编译器都会把a理解为一个指针,所以这两个声明是一样的。C语言传参方式是按值传递,当把数组名作为参数传进去的时候,因为把数组名直接作为右值会得到第一个元素的地址,所以相当于把数组第一个元素的地址存储到了a对应的内存中。这也是常说的数组名转换为指针的本质。其实和上面的代码: int *p = array 本质上是一样的,都是把数组名代表的地址赋值给了指针。

数组指针和指针数组

  <1> 数组指针

    int (*p)[3] ;   因为()的优先级高,所以*运算符先把p声明为一个指针。然后后面的方括号和3使编译器把p解释为一个指向长度为3的一维数组的指针。数组指针一般用于指向二维数组如:

    int a[2][3];    // 相当于2个长度为3的一维数组。

    int (*p)[3];

    p = a;

    p++;  // 指针加减是让指针移动sizeof(指针类型)个单位,p的类型指向长度为3的int型一维数组的i指针。是所以p移动了4 * 3字节

   <2>指针数组

    int *p[3];    []优先级高,所以[]先把p声明为一个数组。然后*使编译器把p解释为,p是一个一维数组,其中每个元素的类型是int *。

   这里需要理解清楚,数组指针是一个指针,在32位机器上就是一块4字节的内存,所以只能保存一个地址。在C语言中一般用来指向二维数组。但是指针数组是一个数组,每个元素是一个指针,所以指针数组能用数组的方式保存多个指针,也就是每个数组元素对应内存中存储的其实是某个变量的地址。

二级指针

  二级指针是指针的指针。说白了,就是二级指针对应的那块内存中存储的是另外一个指针的地址。比如:

  int num1 = 10;

  int num2 = 20;

  int *p1 = &num1;

  int **p2 = &p1;

  *p2 = &num2;    //1

  *p1 = 30;           //2

  **p2 = 40;          //3

  这是,p2对应的那块内存中存储的是p1的地址,而p1那块内存中存储的是num1的地址。*p2代表p1那块内存的地址,所以代码1表示p1那块内存中存储num2的地址。代码1之后,*p1对应内存中的值被改成num2的地址了,所以*p1代表num2对应的那块内存。所以代码2的意思是让num2的那块内存存储30。*p2取到的是p1那块内存的值,也就是num2的地址。**p2代表的是num2对应的那块内存。所以代码2代表把num2的值改为30.

  从这里可以看到,我们没有动p1, 但是p1的指向改变了,这就是二级指针最重要的作用。比如有以下场景:

  int getMem1(int *p)

  {

    p = (int *)malloc(sizeof(int));

  } 

  int getMem2(int **p)

  {

    *p = (int *)malloc(sizeof(int));

  }

  int main()

  {

    int *buf;

    //getMem1(buf);    // 错误,C语言中按值传参,形参p对应的内存在getMem1中存储了新申请内存的起始地址,和buf一点关系都没有。

    getMem2(&buf);    // 正确,在getMem2中,形参p中存储的是指针buf的地址,*p代表的是指针buf对应的那块内存,也就是buf那块内存被赋值为新申请内存的起始地址,在子函数中改变了main函数中buf指针的指向。

  }

  因为一级指针不能再子函数中改变自己的指向,所以只能通过二级指针来改变。这也是二级指针最有用的地方。

指针数组和二级指针的联系

  int *p[10];

  指针数组中的每个元素是一级指针,如果我想改变某个元素(某个指针)的指向,只需要这样:

    p[0] = (int *)malloc(sizeof(int));

    p[1] = (int *)malloc(sizeof(int));

  二级指针的作用也是改变一级指针的指向,这样就把指针数组和二级指针联系起来了。

  int **p2 = p;

  *p2 = (int *)malloc(sizeof(int));

  *(p2 + 1) = (int *)malloc(sizeof(int));

  和上面的代码效果是一样的。*p2代表指针p第一个元素的那块内存。p2是指针的指针,它指向的类型是int *, 所以p2+1表示移动一个int*的位移,这和p+1移动的原理是一样的。

  

猜你喜欢

转载自www.cnblogs.com/MyOnlyBook/p/9484745.html