指针和数组这块一直是我的薄弱点,平时写代码靠惯性,一旦题目换个花样就拿不准。 最近找了几道小练习,把每道题的输出自己推一遍再上机验证, 有几个一直模糊的地方终于清楚了,记录一下。
一、数组名到底是不是指针
常听到「数组名就是指针」,这句话不准确。看这个例子:
#include <stdio.h>
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
printf("sizeof(arr) = %zu\n", sizeof(arr));
printf("sizeof(&arr) = %zu\n", sizeof(&arr));
printf("sizeof(arr + 0) = %zu\n", sizeof(arr + 0));
return 0;
}
在 64 位机器上的输出:
sizeof(arr) = 20
sizeof(&arr) = 20
sizeof(arr + 0) = 8
arr 是数组类型的首地址常量,sizeof 得到的是整个数组的字节数
(5 个 int,20 字节)。而 arr + 0 参与运算时,数组名就退化成了
int *,所以是 8 字节(指针大小)。
&arr 的类型是 int (*)[5],指向整个数组的指针,
sizeof 得到 20 是因为它解引用后是整个数组。这几个结果要能各自解释清楚才算过关。
二、&arr + 1 跳多远
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = (int *)(&arr + 1);
int *p2 = arr + 5;
printf("p1 - arr = %ld\n", p1 - arr);
printf("p2 - arr = %ld\n", p2 - arr);
输出是:
p1 - arr = 5
p2 - arr = 5
结果虽然相同,原因不一样。&arr 的步长是整个数组,
&arr + 1 一步就跳过了 5 个 int;arr + 5 是 int *
按单个 int 步进,走了 5 步。一个是跳了一个数组,一个是跳了五个元素。
三、二维数组怎么传参
二维数组传参时,形参的第一维可以省略,第二维不行:
void print(int a[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", a[i][j]);
}
printf("\n");
}
}
// 也可以写成指针形式
void print2(int (*a)[3], int rows) {
/* 内部用法完全一样 */
}
原因是内存里二维数组是按行连续存放的,知道一行有几个元素,
才能算出 a[i][j] 的偏移量 i * 3 + j。第二维缺失就无法定位,
所以编译器要求写明。
四、一个容易混的写法
int *p[3]; // 指针数组:3 个 int* 组成的数组
int (*p)[3]; // 数组指针:指向 int[3] 的一个指针
记法很简单:看 p 先跟谁结合。p[3] 是下标运算,优先级高于
*,所以前者是数组;加了括号就先跟 * 结合,是指针。
小结
- 数组名是首地址常量,参与运算才退化成指针,
sizeof结果因此不同。 &arr + 1的步长是整个数组,arr + 1的步长是单个元素。- 二维数组传参必须给出第二维,因为要靠它计算地址偏移。
- 指针数组和数组指针,看
p先跟谁结合。