函数篇里说过指针能存某一类型变量的地址。数组是一大块连续的同类型数据,那指针能指向数组吗?能,而且这两者关系密到经常被当成一回事,但严格说它们不是同一样东西。

数组名就是首元素的地址

看一组对照实验。下面代码用三种写法打印同样 5 个字符数组的地址和值:

#include <stdio.h>

int main() {
    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, &vowels[i], i, pvowels + i, i, 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[0]: 0x7ffe...17, pvowels+0: 0x7ffe...17, vowels+0: 0x7ffe...17
&vowels[1]: 0x7ffe...18, pvowels+1: 0x7ffe...18, vowels+1: 0x7ffe...18
...(地址逐个差 1 字节,因为 char 占 1 字节)
vowels[0]: A, *(pvowels+0): A, *(vowels+0): A
vowels[1]: E, *(pvowels+1): E, *(vowels+1): E
...

结论有两条:

  • &vowels[i]pvowels + ivowels + i 三个是同一个地址。
  • 数组名本身是"指向首元素的常量指针",vowels&vowels[0]vowels + 0 都指着同一块位置。

那么指针和数组是一回事吗

不是。名字骗人,地位不同:

  • 数组名是常量:不能重新赋值,vowels = someone_else; 是错的;指针变量可以。
  • 数组名只在声明它的作用域里有体积;传进函数退化成指针后,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));   // 2 4 8 12 16
}
printf("\n");

也可以推着指针走(指针每次加 1 实际跨过一个元素的字节数,下一节细讲):

for (int *p = arr; p < arr + 5; p++) {
    printf("%d ", *p);
}
printf("\n");

指针能过下标瘾,但要注意边界

指针加下标和不加边界,跟数组越界是同一类危险。写循环锁死 i < 元素个数,不管用指针还是下标都是一样的纪律。

下一篇讲指针的另一个大用途,按引用传给函数,让函数真正改得动外面的变量。