核心比喻:一个图书馆
想象一下图书馆:

(图片来源网络,侵删)
- 一维数组:就是一排连续的书架,每一本书(元素)都放在一个固定的位置,它们紧挨着,编号是连续的(0, 1, 2...),你不能把一个书架上的书和另一个书架上的书混在一起,它们是整体的一部分。
- 指针数组:就是一排书架,但每个书架上只放了一本书的索引卡片(指针),这些卡片指向图书馆里任意位置的书(可以是连续的,也可以是分散的),书架本身是连续的,但它们指向的内容是离散的。
- 数组指针:就像一个移动的、可伸缩的“书架车”,这个书架车本身就是一个指针,它指向一整排连续的书架(一个数组),你可以推着这个书架车在图书馆里移动,让它指向不同的书排。
一维数组
这是最基础的概念。
- 定义:一组类型相同的元素的集合,这些元素在内存中连续存储。
- 本质:一个整体,包含多个连续的元素。
- 关键点:
sizeof(数组名)得到的是整个数组所占的总字节数。&数组名得到的是整个数组的起始地址。数组名在大多数情况下(除了作为sizeof或&的操作数)会退化为指向其第一个元素的指针。
代码示例
#include <stdio.h>
int main() {
// 定义一个一维数组,包含5个int类型的元素
int arr[5] = {10, 20, 30, 40, 50};
// 1. 数组名 'arr' 在这里会退化,等同于 &arr[0]
printf("arr 的地址: %p\n", (void*)arr);
printf("&arr[0] 的地址: %p\n", (void*)&arr[0]);
// 2. sizeof(数组名) 计算整个数组的大小
printf("sizeof(arr): %zu bytes\n", sizeof(arr)); // 5 * sizeof(int) = 20 bytes
// 3. &数组名 得到的是整个数组的地址
printf("&arr 的地址: %p\n", (void*)&arr);
printf("arr + 1 的地址: %p\n", (void*)(arr + 1)); // 地址 + 4 (一个int的大小)
printf("&arr + 1 的地址: %p\n", (void*)(&arr + 1)); // 地址 + 20 (整个数组的大小)
return 0;
}
内存模型:
+-----------+-----------+-----------+-----------+-----------+
| arr[0] | arr[1] | arr[2] | arr[3] | arr[4] |
| (value 10)| (value 20)| (value 30)| (value 40)| (value 50)|
+-----------+-----------+-----------+-----------+-----------+
^
|
arr (指向第一个元素的指针)
指针数组
从名字看,“指针的数组”,它是一个数组。
- 定义:一个数组,其每个元素都是一个指针。
- 本质:一个数组,存储的是指针。
- 关键点:
- 它是一个数组,
sizeof(指针数组名)得到的是整个数组所占的总字节数(即指针个数 * sizeof(指针类型))。 - 它的元素可以指向不同类型的内存地址(通常是同类型,但技术上可以不同)。
- 常用于处理多个字符串(字符串字面量本质上是字符指针)。
- 它是一个数组,
代码示例
#include <stdio.h>
int main() {
// 定义一个指针数组,包含3个int*类型的元素
int *ptr_arr[3];
int a = 100;
int b = 200;
int c = 300;
// 让指针数组的每个元素指向一个整型变量
ptr_arr[0] = &a;
ptr_arr[1] = &b;
ptr_arr[2] = &c;
// 访问指针数组中的指针,再解引用获取值
printf("Value via ptr_arr[0]: %d\n", *ptr_arr[0]); // 100
printf("Value via ptr_arr[1]: %d\n", *ptr_arr[1]); // 200
// 遍历指针数组
for (int i = 0; i < 3; i++) {
printf("ptr_arr[%d] points to value: %d\n", i, *ptr_arr[i]);
}
// sizeof 计算的是整个指针数组的大小
// 3 * sizeof(int*) = 3 * 8 = 24 (在64位系统上)
printf("sizeof(ptr_arr): %zu bytes\n", sizeof(ptr_arr));
return 0;
}
内存模型:

(图片来源网络,侵删)
+-------+ +-------+ +-------+
| &a | | &b | | &c |
+-------+ +-------+ +-------+
^ ^ ^
| | |
+---------+---------+---------+---------+---------+
| ptr_arr[0] | ptr_arr[1] | ptr_arr[2] | ... (其他可能的内存)
+---------+---------+---------+---------+---------+
(指针数组本身在内存中是连续的)
数组指针
从名字看,“指向数组的指针”,它是一个指针。
- 定义:一个指针,它指向一个完整的数组。
- 本质:一个指针,其指向的数据类型是一个数组。
- 关键点:
- 它是一个指针,
sizeof(数组指针名)得到的是指针本身的大小(通常是 4 或 8 字节)。 - 定义时必须明确它指向的数组的维度(即包含多少个元素)。
- 语法上有点奇怪:
int (*p)[N],括号是关键,它告诉编译器p是一个指针,而不是一个数组。
- 它是一个指针,
代码示例
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 定义一个数组指针 p,它指向一个包含5个int元素的数组
// 语法:int (*p)[5]
// ^ ^
// | +---- p指向的数组有5个int元素
// +-------- p是一个指针
int (*p)[5] = &arr; // 注意,这里必须取 &arr,因为p指向的是整个数组
// 1. *p 解引用后,就得到了它所指向的整个数组
// *p 和 arr 行为类似,会退化为其首元素地址
printf("Value via *p: %d\n", (*p)[2]); // 30, 等同于 arr[2]
// 2. p 是一个指针,sizeof(p) 得到的是指针的大小
printf("sizeof(p): %zu bytes\n", sizeof(p)); // 8 bytes (64位系统)
// 3. p + 1 的地址会跳过整个数组的大小
printf("p's address: %p\n", (void*)p);
printf("(p + 1)'s address: %p\n", (void*)(p + 1)); // 地址 + 5 * sizeof(int) = 20
return 0;
}
内存模型:
+-----------+-----------+-----------+-----------+-----------+
| arr[0] | arr[1] | arr[2] | arr[3] | arr[4] |
+-----------+-----------+-----------+-----------+-----------+
^
|
&arr (整个数组的地址)
^
|
p (数组指针,指向了 &arr)
核心区别与总结
| 特性 | 一维数组 (int arr[5]) |
指针数组 (int *ptr_arr[3]) |
数组指针 (int (*p)[5]) |
|---|---|---|---|
| 名称 | 数组 | 指针数组 | 数组指针 |
| 本质 | 一个数组,包含多个同类型元素 | 一个数组,其元素都是指针 | 一个指针,指向一个数组 |
| 定义语法 | type name[size]; |
type *name[size]; |
type (*name)[size]; |
sizeof |
sizeof(name) -> 整个数组大小 |
sizeof(name) -> 整个指针数组大小 |
sizeof(name) -> 指针本身大小 |
&name |
&name -> 整个数组的地址 |
&name -> 指针数组的地址 |
&name -> 指针的地址 |
name + 1 |
name + 1 -> 指向下一个元素 |
name + 1 -> 指向下一个指针元素 |
p + 1 -> 指向下一个相同类型的数组 |
| 用途 | 存储一组连续的数据 | 存储多个指向不同内存地址的指针(如字符串) | 指向并操作一个固定大小的多维数组行 |
| 记忆口诀 | 数组的元素 | 数组的元素是指针 | 指针指向的是数组 |
如何区分 int *p[5] 和 int (*p)[5]?
这是最经典的语法辨析题,关键在于运算符的优先级。
-
*`int p[5];`**
(图片来源网络,侵删)[](数组下标运算符) 的优先级高于 (指针解引用运算符)。int *p[5]被解释为int *(p[5])。- 意思是:
p是一个包含5个元素的数组,这个数组的每个元素都是int*(指向整型的指针)。 - 指针数组。
-
*`int (p)[5];`**
- 括号 的优先级最高。
int (*p)[5]被解释为p是一个指针,这个指针指向int[5](一个包含5个整型的数组)。- 数组指针。
简单记忆法: 和 [] 中间没有括号,[] 优先,p 是数组; 被 包围, 优先,p 是指针。
希望这个详细的解释和比喻能帮助你彻底理解这三个概念!
