本节我们主要讲述数组和指针之间的关联,包括指针访问数组,一维数组传参,二级指针,以及指针数组和用指针模拟二维数组等
一、数组名的理解
我们之前初学指针时对于数组的访问一直是&arr[0] 但实际上数组名本来就是地址,而且是首元素地址
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
printf("&arr = %p\n", &arr[0]);
printf("&arr = %p\n", arr);
return 0;
}
我们发现结果一模一样 这也说明了数组名就是数组首元素的地址
但是并不是所有情况下数组名都是数组首元素地址
这时就会有疑问了,那数组名到底怎么理解呢?
其实在一般情况下,数组名就是数组首元素地址,但是有两个例外
1、sizeof(数组名) 在sizeof中单独存放的数组名,这里的数组名表示整个数组 因此这个式子计算的是整个数组的大小 单位是字节 上例中10个整型40个字节
2、&数组名 这里的数组名也表示整个数组,取出的是整个数组的地址,然后在+1后会跳过该数组,而不是跳过一个元素 这里在后面的二级指针中会有体现
二、指针访问数组
有了前面的支持,我们可以使用指针来访问数组
我们可以思考一下,p得到的是数组首元素地址,而数组名也是数组首元素地址,所有在这里arr和p是完全等价的。 因此p+i也可写为arr[i] *(p+i)也可写为*(arr+i) 但是数组可以arr[i]这样访问元素,那么p是否也可以呢,经过验证发现 完全可以写成p[i]的形式
不过,我们要知道的是编译器在处理访问数组元素的过程中时都是转换成首元素地址+偏移量来求出元素地址,然后解引用访问的
三、一维数组传参本质
问题引入:之前都是在主函数main中求解数组长度的,如果是在一个函数内部求解外部数组的长度,那么在传参的时候究竟是传递指针变量呢还是传递数组呢
我们先来看一个神奇的现象:
可以看到,在函数内外分别求解数组长度有偏差 这是因为数组传参本质上就是传递数组首元素的地址,既然是个地址那么我们sizeof()求的就是一个地址的大小 只能为4或8个字节 这里我们是x64环境是8个字节下 8/4==2 因此结果为2
如果我们把函数参数改成int* arr 发现结果一样
当然函数内部能否求解数组长度并不是我们讨论的重点,这里是为了说明一维数组传参,形参部分可以写成数组也可以写成指针变量。
四、二级指针
我们前面用指针变量来接收地址,那么指针变量也是一个变量,那么能不能接收这个指针变量的地址呢,当然可以,这时就用到二级指针。
那么我们自然理解,对二级指针解引用就得到一级指针 那么连续两次解引用就拿到变量a 于是就可以对a进行访问
类似地,我们可以有三级指针,四级指针的概念,不过更高级指针不常用,此处不再赘述
五、指针数组
光从这个名字看 这是一个数组,其元素类型为指针变量,事实也确实如此
对于其他概念 如整型数组,字符数组,结构体数组等意思相同 均为数组,只是元素类型各不相同
六、指针数组模拟二维数组
二维数组结构如图所示,这是一个三行五列的二维数组,写成数组形式就是int arr[3][5],并且真实的二维数组在内存中是连续存储的
这里我们用指针数组来模拟 其中该数组的每个元素是一个指针类型变量 那么可以考虑用这个指针变量来指向一个数组的地址,用三个一维数组拼接起来,成为目标二维数组
成功达到模拟二维数组的效果,但注意,这并非是真正的二维数组,因为三个一维数组的地址不一定是连续的,而二维数组的每一行都是连续的。