在 C 语言中,指针和数组有着非常紧密的联系,但它们本质上是 不同的概念。理解它们的关系是掌握 C 语言内存操作的关键。下面我会从多个角度帮你梳理 指针和数组的直接联系,并解释它们的异同点。
1. 数组和指针的本质区别
概念 | 本质 | 存储方式 | 能否修改指向 | 典型用途 |
---|---|---|---|---|
数组(Array) | 一组 连续存储的同类型数据 | 在栈或静态区分配固定大小的内存 | ❌ 不能整体修改(数组名是常量指针) | 存储固定数量的数据 |
指针(Pointer) | 一个 变量,存储内存地址 | 可以指向任意内存位置(栈、堆、静态区) | ✅ 可以修改指向(指向不同地址) | 动态内存操作、函数传参 |
关键区别:
- 数组名
arr
在大多数情况下会退化为指向首元素的指针(&arr[0]
),但它本身不是指针变量(不能重新赋值,如arr = &x
是错误的)。 - 指针是一个变量,可以存储任意地址,并且可以修改指向(如
int *p = &x; p = &y;
)。
2. 数组和指针的直接联系
(1) 数组名在大多数情况下退化为指针(指向首元素)
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 int *p = &arr[0];
- **
arr
的类型是int[5]
(数组),但在表达式里(如赋值给指针时),它会 退化为int*
(指向arr[0]
的指针)**。 - **
arr
和&arr[0]
是等价的**,都表示数组首元素的地址。
(2) 数组名 arr
和 &arr
的区别
表达式 | 类型 | 含义 |
---|---|---|
arr |
int* (退化) |
指向 首元素 arr[0] 的指针 |
&arr |
int (*)[5] |
指向 整个数组 arr 的指针(类型是 int[5] 的指针) |
arr + 1 |
移动 1 个 int 大小(4 字节) |
指向 arr[1] |
&arr + 1 |
移动 整个数组大小(5 * 4 = 20 字节) | 指向 arr 的下一个数组(如果有的话) |
示例:
int arr[5] = {1, 2, 3, 4, 5};
printf("%p\n", arr); // 数组首元素地址(等价于 &arr[0])
printf("%p\n", &arr); // 整个数组的地址(值和 arr 相同,但类型不同)
printf("%p\n", arr + 1); // 指向 arr[1](地址 + 4 字节)
printf("%p\n", &arr + 1); // 指向 arr 的下一个位置(地址 + 20 字节)
输出:
0x7ffd12345670 (arr)
0x7ffd12345670 (&arr,值相同)
0x7ffd12345674 (arr + 1,+4 字节)
0x7ffd12345684 (&arr + 1,+20 字节)
结论:
arr
和&arr
的 值相同(都是数组的起始地址),但 类型不同:arr
是int*
(指向int
)。&arr
是int (*)[5]
(指向int[5]
数组)。
arr + 1
和&arr + 1
的 步长不同:arr + 1
移动 1 个int
大小(4 字节)。&arr + 1
移动 整个数组大小(5 * 4 = 20 字节)。
(3) 数组访问方式 vs 指针访问方式
数组方式
int arr[3] = {10, 20, 30};
printf("%d\n", arr[1]); // 20(数组下标访问)
指针方式
int *p = arr;
printf("%d\n", *(p + 1)); // 20(指针偏移访问)
等价关系:
arr[i]
等价于*(arr + i)
。p[i]
等价于*(p + i)
。
结论:
- 数组下标访问
arr[i]
底层就是指针偏移*(arr + i)
。 - 指针可以像数组一样使用
[]
运算符(因为[]
本质是指针算术)。
(4) 函数传参时数组退化为指针
void printArray(int arr[], int size) { // 实际上 arr 是 int*
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]); // 等价于 *(arr + i)
}
}
int main() {
int myArr[3] = {1, 2, 3};
printArray(myArr, 3); // 数组名退化为指针
return 0;
}
关键点:
- **函数参数中的
int arr[]
实际上等价于int *arr
**(编译器不会把数组完整传进去,而是传首地址)。 - 所以 在函数内部无法通过
sizeof(arr)
获取数组大小(只能得到指针大小,通常是 4 或 8 字节)。
3. 指针和数组的常见操作对比
操作 | 数组方式 | 指针方式 |
---|---|---|
访问第 i 个元素 | arr[i] |
*(p + i) 或 p[i] |
遍历数组 | for (int i = 0; i < n; i++) { arr[i]; } |
for (int *p = arr; p < arr + n; p++) { *p; } |
函数传参 | void func(int arr[]) (实际是 int* ) |
void func(int *p) |
获取首地址 | arr 或 &arr[0] |
p (指针本身) |
获取数组大小 | sizeof(arr) / sizeof(arr[0]) (仅限数组定义处) |
❌ 无法直接获取(只能手动传大小) |
4. 总结
(1) 指针和数组的联系
✅ 数组名在大多数情况下会退化为指向首元素的指针(如 arr
→ &arr[0]
)。
✅ 数组访问 arr[i]
底层就是指针算术 *(arr + i)
。
✅ 指针可以像数组一样使用 []
运算符(如 p[i]
)。
✅ 函数传参时,数组会退化为指针(无法在函数内获取数组真实大小)。
(2) 指针和数组的区别
❌ 数组名不是指针变量(不能重新赋值,如 arr = &x
是错误的)。
❌ **arr
和 &arr
类型不同(arr
是 int*
,&arr
是 int (*)[n]
)。
❌ 数组在栈/静态区分配固定大小,指针可以指向任意内存(堆、栈、静态区)**。
(3) 关键结论
- 数组名
arr
在大多数情况下可以当作指针使用,但它本质不是指针变量。 - 指针更灵活,可以指向任意内存,而数组名是固定的。
- 函数传参时,数组会退化为指针,所以无法在函数内获取数组真实大小(必须额外传
size
参数)。