C 语言指针与数组的深度关联及实战应用
在 C 语言开发中,指针和数组的纠缠关系往往是新手最容易踩坑的地方。这篇文章不讲枯燥的定义,直接聊聊数组名的本质、指针算术运算的实际用法,以及指针数组和数组指针的区别。搞懂这些,你在处理内存、遍历数据时会更从容。
数组名与指针的关系
很多初学者容易把数组名当成普通的变量,其实它在大多数情况下会被编译器隐式转换为指向首元素的常量指针。这意味着你可以用指针的方式去访问它,但它本身不能变。
数组名的本质
看这段代码:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
printf("数组首元素地址:%p\n", arr);
printf("数组首元素地址:%p\n", &arr[0]);
printf("数组首元素值:%d\n", *arr);
return 0;
}
运行结果会显示 arr 和 &arr[0] 的地址完全一致,都是数组第一个元素的地址。通过 *arr 解引用也能拿到第一个元素的值。这验证了数组名确实能当指针用。
⚠️ 注意:数组名是常量指针,你不能修改它的值。下面这种写法是非法的:
int arr[5];
arr++; // 错误!数组名是常量,不能自增
下标访问与指针等价性
C 语言规定,arr[i] 在底层完全等价于 *(arr + i)。这是指针操作数组的核心逻辑。指针算术运算会根据元素类型自动计算偏移量,比如 int 占 4 字节,arr + 1 就会跳过 4 个字节指向下一个元素。
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int i;
for (i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(arr + %d) = %d\n", i, arr[i], i, *(arr + i));
}
return 0;
}
两种写法输出结果完全一样,但理解背后的偏移机制对调试很有帮助。
指针操作数组的实战案例
理解了原理,我们来看看怎么用指针让代码更简洁高效。
案例一:指针遍历求和
需求是用指针遍历数组并计算总和。这里有个小技巧,利用 sizeof 动态获取长度。
#include <stdio.h>
int main() {
int arr[6] = {1, 2, 3, 4, 5, 6};
int sum = 0;
int *p = arr; // 指针 p 指向数组首元素
int len = sizeof(arr) / sizeof(arr[0]); // 通用长度计算方法
for (int i = 0; i < len; i++) {
sum += *p;
p++; // 指针自增,指向下一个元素
}
printf("数组元素总和:%d\n", sum);
return 0;
}
运行结果是 21。注意 sizeof(arr) / sizeof(arr[0]) 这种写法适用于任意类型的静态数组,比硬编码数字更安全。
案例二:指针逆序存放
不用额外空间,直接用双指针交换实现数组逆序。
#include <stdio.h>
void reverseArray(int *arr, int len) {
int *left = arr; // 指向首元素
int *right = arr + len - 1; // 指向尾元素
int temp;
while (left < right) {
temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int len = sizeof(arr) / sizeof(arr[0]);
int *p = arr;
printf("原数组:");
for (int i = 0; i < len; i++) {
printf("%d ", *(p + i));
}
printf("\n");
reverseArray(arr, len);
printf("逆序数组:");
for (int i = 0; i < len; i++) {
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
⚠️ 坑点提醒:在函数内部使用 sizeof(arr) 得到的只是指针的大小(通常是 4 或 8 字节),而不是数组实际大小。所以传递数组给函数时,必须同时传递长度参数。
指针数组与数组指针的区别
这两个概念名字很像,但用途完全不同,面试和工作中经常考到。
指针数组
定义:存放指针的数组。
格式:类型 *数组名 [长度];
每个元素都是一个指针变量,常用于管理多个字符串。
#include <stdio.h>
int main() {
char *strArr[3] = {"C 语言", "指针", "数组"};
for (int i = 0; i < 3; i++) {
printf("%s\n", strArr[i]);
}
return 0;
}
数组指针
定义:指向整个数组的指针。
格式:类型 (*指针名)[长度];
它指向的是一个完整的数组结构,常用于二维数组操作。
#include <stdio.h>
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // 数组指针,指向包含 3 个 int 元素的数组
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
return 0;
}
⚠️ 关键细节:定义数组指针时,(*p) 的括号绝对不能省。如果写成 int *p[3],那就变成了指针数组,含义天差地别。
常见问题与解决方案
问题一:指针越界访问
❌ 错误示范:
int arr[3] = {1, 2, 3};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("%d\n", *p++); // 越界访问,读取随机值
}
✅ 解决思路:
严格根据数组长度控制循环次数。永远不要假设指针后面还有合法数据,使用 sizeof 计算长度是最稳妥的方式。
问题二:混淆两者定义
❌ 原因:定义时漏掉括号,或者没分清是指针的集合还是指向集合的指针。
✅ 口诀:
指针数组是 类型 *数组名 [](存指针);
数组指针是 类型 (*指针名) [](指整体)。
本章小结
- 数组名本质是常量指针,
arr[i]等价于*(arr + i)。 - 指针算术运算能高效完成遍历、求和、逆序等操作,但要注意函数传参时的长度丢失问题。
- 指针数组存的是地址,数组指针存的是数组整体,定义格式差一个括号。
- 操作内存务必小心越界,控制好指针边界是基本功。


