数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };
为例,该数组在内存中的分布如下图所示:
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针
,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:
数组下标为啥从0开始?
-
数组下标实际上是每个元素的地址相对于第一个元素地址的偏移量
访问数组元素除了可以通过下标法之外,还可以通过指针法访问。
-
下标法
for(int i = 0;i < 6;i++) { printf("%d ",arr[i]); //printf("%d ",i[arr]); }
-
指针法
for(int i = 0;i < 6;i++) { printf("%d ",*(arr + i)); }
这就是为什么在某些地方大家会看到 i[arr] 这种访问数组元素的方法的原因,实际上下标法就是通过指针法来实现的,只不过编译器帮助我们做了这个操作,简化了操作难度。
指向数组的指针
我们也可以定义一个指向数组的指针,例如:
int arr[] = { 99, 15, 100, 888, 252 }; int *p = arr;
arr 本身可以看做是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;
也可以写作int *p = &arr[0];
。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。
如果一个指针指向了数组,我们就称它为数组指针(Array Pointer)。
数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p 指向的数组元素是 int 类型,所以 p 的类型必须也是int *
。
反过来想,p 并不知道它指向的是一个数组,p 只知道它指向的是一个整数,究竟如何使用 p 取决于程序员的编码。
使用指针访问数组元素和使用函数名没有任何区别,值得注意的是我们不同通过指针获得数组的大小,但是通过数组名却可以。
printf("%d\n",sizeof(arr));//数组所占字节数 20 Byte printf("%d\n",sizeof(p));//指针所占字节数 4 Byte
也就是说,根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。
关于数组指针的谜题
假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p++、*++p、(*p)++ 分别是什么意思呢?
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。
数组名和数组指针的区别
虽然说数组名可以当做指针使用,但实际上数组名并不等价于指针。
-
数组名代表的是整个数组,具有确定数量的元素
-
指针是一个标量,不能确定指向的是否是一个数组
-
数组可以在某些情况下会自动转换为指针,当数组名在表达式中使用时,编译器会把数组名转换为一个指针常量,是数组中的第一个元素的地址,类型就是数组元素的地址类型(通过sizeof也可以看出来)
二维数组指针
二维数组可以理解为每一个元素都是一个一维数组的数组,这样就可以很好的理解二维数组与指针了。
下面定义了一个2行3列的二维数组,并画出了对应的内存模型。
我们可以使用arr[0]获得第0个一维数组,然后再加上一个小标就可以获取到对应的元素,如arr[0][0]获取了第0行第0列的元素。