第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]。为什么?因为指针加整数,跳过的字节数由指针类型决定。p 是 int*,所以 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 知识体系图
下面这张图帮你理清本章的核心逻辑:
5.6 总结
指针和数组的关系,说白了就是地址和内存的映射。数组名是地址常量,指针变量可以指向数组元素,然后通过指针运算或下标来访问数据。
记住几个关键点:
- 数组名就是首元素地址,但不能修改
- 指针遍历数组效率高,但要注意边界
- arr[i] 本质上是 *(arr + i)
- 数组作为函数参数时退化为指针,长度要单独传
嗯,这些搞明白了,指针和数组这块儿就算入门了。下一章咱们聊聊指针和二维数组,那才是真正考验理解能力的地方。
公众号:蓝海资料掘金营,微信 deep3321