第5章 指针与一维数组:数组名作为指针、通过指针遍历数组、指针与数组下标的关系

数组和指针,在C语言里就像一对双胞胎。长得像,脾气也像。很多初学者搞不清它们到底啥关系。我当年刚学的时候也迷糊过——数组名到底是不是指针?能不能直接拿它做运算?

今天咱们就把这事儿彻底说清楚。

5.1 数组名就是指针?嗯,差不多

先看一个最简单的例子:

int arr[5] = {10, 20, 30, 40, 50};
printf("%p\n", arr);      // 输出数组首地址
printf("%p\n", &arr[0]);  // 输出第一个元素的地址

你会发现,arr&arr[0] 打印出来的值一模一样。这说明什么?数组名就是数组首元素的地址

但注意,它不完全等于指针变量。数组名是个常量,你不能给它赋值。比如:

int arr[5];
arr = NULL;   // 编译报错!arr是常量

这一点我踩过坑。有一次写代码,想复用同一个数组名指向另一块内存,结果编译器直接报错。后来才意识到,数组名不是变量,不能重新指向。

核心结论:

  • 数组名代表数组首元素的地址
  • 数组名是地址常量,不是指针变量
  • arr 等价于 &arr[0]

5.2 通过指针遍历数组

既然数组名就是地址,那我们可以把它赋给一个指针变量,然后用指针来遍历数组。

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;  // p指向数组首元素

for (int i = 0; i < 5; i++) {
    printf("%d ", *(p + i));  // 通过指针偏移访问
}

这里 *(p + i) 等价于 p[i],也等价于 arr[i]。为什么?因为指针加整数,跳过的字节数由指针类型决定。pint*,所以 p + 1 跳过4个字节(假设int占4字节),正好指向下一个元素。

我个人习惯用指针遍历数组,尤其是在嵌入式开发里。为什么?因为指针操作更底层,有时候能省几条指令。比如在循环里,用指针自增比用下标访问效率高一点点:

int *p = arr;
for (int i = 0; i < 5; i++) {
    printf("%d ", *p++);  // 先取值,再自增
}

你看,*p++ 这个写法很常见。它先取出 p 指向的值,然后 p 自增指向下一个元素。一步到位。

小技巧: 在嵌入式系统中,如果数组很大,用指针自增遍历比用下标访问快。因为下标访问每次都要计算 arr + i * sizeof(type),而指针自增只是加法。

5.3 指针与数组下标的关系

其实,C语言里 arr[i] 这个写法,本质上就是 *(arr + i) 的语法糖。编译器看到 arr[i],直接翻译成 *(arr + i)

所以下面这几种写法完全等价:

写法 含义
arr[0] *(arr + 0)
arr[3] *(arr + 3)
p[2] *(p + 2)
*(p + 4) p[4]

有意思的是,你甚至可以写成 2[arr],它等价于 *(2 + arr),也就是 arr[2]。虽然没人这么写,但语法上是对的。我见过一些老代码里真有这种写法,当时看得一愣一愣的。

注意: 指针访问数组时,一定要确保不越界。C语言不会检查数组边界,越界访问可能导致野指针、数据损坏甚至系统崩溃。我在调试一个电机控制程序时,就因为指针多偏移了一位,导致写到了别的变量上,电机直接乱转。从那以后,我每次用指针遍历数组都会格外小心边界条件。

5.4 数组名作为函数参数

当数组名作为函数参数时,它退化为指针。也就是说,下面两种写法完全等价:

void func(int arr[]) { ... }
void func(int *arr) { ... }

在函数内部,arr 就是一个指针变量,不再是数组名常量。所以你可以对它赋值、自增等操作。但这也意味着,函数内部无法通过 sizeof(arr) 得到数组长度——它只会返回指针的大小(通常是4或8字节)。

我曾经犯过这个错:

void print_array(int arr[]) {
    int len = sizeof(arr) / sizeof(arr[0]);  // 错误!arr是指针
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

结果只打印了两个数就停了,因为 sizeof(arr) 是8(64位系统),sizeof(arr[0]) 是4,算出来 len = 2。正确的做法是把数组长度作为参数传进去。

避坑指南: 数组作为函数参数时,一定要同时传递数组长度。不要指望在函数内部用 sizeof 算长度,那是行不通的。

5.5 知识体系图

下面这张图帮你理清本章的核心逻辑:

指针与一维数组知识体系 数组名 arr 等价于 &arr[0] 赋值给 int *p = arr 指针运算 *(p+i) 下标访问 arr[i] 完全等价:*(p+i) == p[i] == arr[i]

5.6 总结

指针和数组的关系,说白了就是地址和内存的映射。数组名是地址常量,指针变量可以指向数组元素,然后通过指针运算或下标来访问数据。

记住几个关键点:

  • 数组名就是首元素地址,但不能修改
  • 指针遍历数组效率高,但要注意边界
  • arr[i] 本质上是 *(arr + i)
  • 数组作为函数参数时退化为指针,长度要单独传

嗯,这些搞明白了,指针和数组这块儿就算入门了。下一章咱们聊聊指针和二维数组,那才是真正考验理解能力的地方。


公众号:蓝海资料掘金营,微信 deep3321