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移动的原理是一样的。