在理解指针与数组的各自含义之后,需要弄清二者在表达式中的联系:数组名在多数情况下表示首元素地址,因而可与指针算术、解引用配合使用;但数组与指针在类型与 sizeof 等行为上并不完全相同。
数组名与首元素地址
下列程序用三种方式打印字符数组各元素的地址与值:
#include <stdio.h>
int main(void) {
char vowels[] = {'A', 'E', 'I', 'O', 'U'};
char *pvowels = vowels;
int i;
for (i = 0; i < 5; i++) {
printf("&vowels[%d]: %p | pvowels+%d: %p | vowels+%d: %p\n",
i, (void *)&vowels[i], i, (void *)(pvowels + i), i, (void *)(vowels + i));
}
for (i = 0; i < 5; i++) {
printf("vowels[%d]: %c | *(pvowels+%d): %c | *(vowels+%d): %c\n",
i, vowels[i], i, *(pvowels + i), i, *(vowels + i));
}
return 0;
}典型输出中,&vowels[i]、pvowels + i、vowels + i 的地址一致;相邻 char 元素地址相差 1 字节。
可归纳两点:
vowels、&vowels[0]、vowels + 0均指向首元素。- 在表达式中,数组名常退化为指向首元素的指针;该指针为指针常量,不能对数组名重新赋值(例如
vowels = …非法)。
数组与指针并非同一类型
- 数组名不是可修改的指针变量;指针变量可以改指向其他对象。
- 在定义数组的作用域内,
sizeof(数组名)为整个数组的字节数;数组作为函数参数传入后退化,sizeof不再表示元素个数,见数组作为函数参数。 - 遍历与访问时,
arr[i]与*(arr + i)写法可互换,但类型与生命周期规则仍须区分。
用指针遍历数组
int arr[5] = {2, 4, 8, 12, 16};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i));
}
printf("\n");也可移动指针本身,注意循环上界应基于元素个数,避免越过数组末尾:
for (int *p = arr; p < arr + 5; p++) {
printf("%d ", *p);
}
printf("\n");指针加 1 时,地址增加量为所指类型的大小(例如 int * 加 1 跳过 sizeof(int) 字节)。
边界与安全
无论使用下标还是指针算术,都必须保证访问范围在数组有效元素之内;越界读写在 C 语言中属于未定义行为。
本节说明了数组名与指针在访问上的对应关系及二者差异。下一节介绍如何通过指针形参在函数中修改调用方的变量,见按地址传参。