指针与数组是C语言的“灵魂”,也是新手最易混淆的核心概念——很多人误以为“数组名就是指针”,但二者在底层实现、内存特性上存在本质差异。本文从底层视角拆解指针与数组的关联和区别,帮你避开典型陷阱。
一、数组名的本质:常量指针
C语言中,数组名并非普通变量,而是指向数组首元素的常量指针(const pointer)。它固定指向数组第一个元素的内存地址,无法被修改;而普通指针变量则可以自由指向不同地址。
示例(数组名的常量特性):
#include <stdio.h>
int main() {
int arr[3] = {
10, 20, 30};
int *p = arr; // 合法:数组名arr隐式转换为指向首元素的指针
// arr = arr + 1; // 编译报错!数组名是常量指针,不能被赋值
p = p + 1; // 合法:普通指针变量可修改指向
printf("*(arr+1) = %d\n", *(arr+1)); // 输出20:数组名可参与指针运算
printf("*p = %d\n", *p); // 输出20:指针指向数组第二个元素
return 0;
}
关键补充:arr与&arr的区别——arr指向数组首元素(类型为int*),&arr指向整个数组(类型为int (*)[3]),二者地址值相同,但含义和运算规则不同:
printf("arr = %p\n", arr); // 输出数组首元素地址
printf("&arr = %p\n", &arr); // 输出同一地址值
printf("arr+1 = %p\n", arr+1); // 地址+4(int占4字节)
printf("&arr+1 = %p\n", &arr+1); // 地址+12(整个数组占12字节)
二、指针与数组的“等价性”与底层差异
1. 表面等价:下标访问的本质
C语言规定,数组下标访问arr[i]等价于指针运算*(arr+i),普通指针的下标访问p[i]也等价于*(p+i)——这是二者“看起来一样”的核心原因。
2. 底层差异:不可忽视的核心区别
| 维度 | 数组名(arr) | 指针变量(p) |
|---|---|---|
| 内存占用 | 存储整个数组数据(如3个int占12字节) | 仅存储一个地址(如8字节,64位系统) |
| 可修改性 | 常量指针,不可赋值 | 变量,可自由修改指向 |
| sizeof计算 | 返回数组总字节数(如sizeof(arr)=12) | 返回指针本身字节数(如sizeof(p)=8) |
典型陷阱:数组退化为指针后丢失长度信息
#include <stdio.h>
void test(int arr[]) {
// 此处arr已退化为普通指针,sizeof(arr)返回指针字节数(8),而非数组总长度
printf("sizeof(arr) in func = %zu\n", sizeof(arr));
}
int main() {
int arr[3] = {
1,2,3};
printf("sizeof(arr) in main = %zu\n", sizeof(arr)); // 输出12
test(arr); // 输出8
return 0;
}
三、实战避坑指南
- 不要试图修改数组名的指向,它是常量指针;
- 函数传参时,数组会退化为指针,若需获取数组长度,需额外传递长度参数;
- 区分
arr(首元素指针)和&arr(整个数组指针),避免指针运算错误。
总结
- 数组名是指向首元素的常量指针,与普通指针变量有本质区别,核心差异体现在可修改性和sizeof计算上;
arr[i]的本质是*(arr+i),这是指针与数组表面等价的底层逻辑;- 函数传参时数组会退化为指针,需手动传递长度才能正确获取数组大小,这是新手最易踩的坑。