核心概念速览
| 概念 | 英文名 | 定义/声明 | 核心特点 | 内存模型 |
|---|---|---|---|---|
| 一维数组 | One-dimensional Array | int arr[5]; |
一块连续的内存空间,存放多个相同类型的元素。 | arr[0] arr[1] arr[2] arr[3] arr[4] (连续) |
| 数组指针 | Pointer to an Array | int (*p)[5]; |
一个指针,它指向一个完整的数组。 | p -> [arr[0] arr[1] arr[2] arr[3] arr[4]] (指向整个数组) |
| 指针数组 | Array of Pointers | int *p[5]; |
一个数组,它的每个元素都是一个指针。 | p[0] p[1] p[2] p[3] p[4] (每个元素都是一个指针地址) |
一维数组
这是最基础的概念。

(图片来源网络,侵删)
定义
一维数组是用于存储相同类型元素的一系列连续内存位置。
声明
数据类型 数组名[元素个数];
int arr[5]; // 定义一个包含5个整型元素的数组,名为arr
关键点
- 连续内存:系统会为数组分配一块连续的内存空间。
arr[0]的地址最低,arr[4]的地址最高。 - 数组名
arr的双重身份:- 代表整个数组:在大多数情况下(如
sizeof(arr)或&arr),arr代表整个数组。 - 代表首元素地址:在表达式(如
arr + 1或作为函数参数传递时),arr会“退化”为其首元素arr[0]的地址。
- 代表整个数组:在大多数情况下(如
示例与内存图
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 1. sizeof(arr) 计算的是整个数组的字节大小
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 输出 20 (5 * 4)
// 2. &arr 获取的是整个数组的地址
printf("&arr = %p\n", (void*)&arr);
// 3. arr 在表达式中,代表首元素 arr[0] 的地址
printf("arr = %p\n", (void*)arr);
printf("&arr[0] = %p\n", (void*)&arr[0]);
// 4. arr + 1 地址跳过的是整个数组的大小 (sizeof(int) * 5)
printf("arr + 1 = %p\n", (void*)(arr + 1));
printf("&arr[1] = %p\n", (void*)&arr[1]); // 与上者不同!
return 0;
}
内存图解:
内存地址 (递增)
+---------------------+
| 0x7ffd... | 10 | <-- arr[0], *arr
+---------------------+
| 0x7ffd... | 20 | <-- arr[1]
+---------------------+
| 0x7ffd... | 30 | <-- arr[2]
+---------------------+
| 0x7ffd... | 40 | <-- arr[3]
+---------------------+
| 0x7ffd... | 50 | <-- arr[4]
+---------------------+
^
|
|--- arr (首元素地址)
|
|--- &arr (整个数组的地址)
数组指针
定义
数组指针是一个指针变量,它指向一个完整的数组,而不是单个元素。

(图片来源网络,侵删)
声明
数据类型 (*指针名)[元素个数];
注意括号 ! 它是必须的,因为它将 和指针名 p 绑定在一起,表示 p 是一个指针,如果没有括号,int *p[5] 就成了“指针数组”。
int (*p)[5]; // p 是一个指针,它指向一个包含5个int元素的数组
关键点
- 指向一个整体:
p指向的不是arr[0],而是整个arr数组。 - 指针运算的跨度:
p + 1时,指针的值会加上sizeof(整个数组),也就是sizeof(int) * 5,这与arr + 1的行为一致。
示例与内存图
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int (*p)[5] = &arr; // p 指向整个 arr 数组
printf("p = %p\n", (void*)p);
printf("&arr = %p\n", (void*)&arr);
printf("arr = %p\n", (void*)arr); // arr 和 &arr 的值可能相同,但类型和含义不同
// 解引用 p 得到的是它指向的整个数组
// 在表达式中,这个数组会“退化”为首元素的地址
printf("*p = %p\n", (void*)*p); // *p 等价于 arr
printf("**p = %d\n", **p); // **p 等价于 *arr,即 arr[0] 的值 10
// p + 1 跳过整个数组
printf("p + 1 = %p\n", (void*)(p + 1));
// 遍历数组 (用法不常见,但能说明问题)
for (int i = 0; i < 5; i++) {
printf("(*p)[%d] = %d\n", i, (*p)[i]); // (*p) arr
}
return 0;
}
内存图解:
内存地址 (递增)
+---------------------+
| 0x7ffd... | 10 | <-- arr[0]
+---------------------+
| 0x7ffd... | 20 | <-- arr[1]
+---------------------+
| 0x7ffd... | 30 | <-- arr[2]
+---------------------+
| 0x7ffd... | 40 | <-- arr[3]
+---------------------+
| 0x7ffd... | 50 | <-- arr[4]
+---------------------+
^
|
|--- &arr (整个数组的地址)
|
|--- p (p 的值是 &arr 的值)
指针数组
定义
指针数组是一个数组,它的所有元素都是指针变量。
声明
数据类型 *指针名[元素个数];
注意没有括号 ! 由于 [] 的优先级高于 ,编译器会先解析 p[5],认为这是一个数组, 表示这个数组的元素是指针。
int *p[5]; // p 是一个包含5个元素的数组,每个元素都是一个指向 int 的指针
关键点
- 一个数组,装满了指针:它本身是一个数组,不是指针。
- 元素的类型:每个元素
p[0],p[1], ... 都是一个指针,可以用来存储其他变量的地址。 - 最常见的用途:用于存储多个字符串(字符数组的首地址)。
示例与内存图
#include <stdio.h>
int main() {
int a = 10, b = 20, c = 30, d = 40, e = 50;
int *p[5]; // 定义一个指针数组
// 让每个指针元素指向一个整型变量
p[0] = &a;
p[1] = &b;
p[2] = &c;
p[3] = &d;
p[4] = &e;
// sizeof(p) 计算的是整个指针数组的字节大小
printf("sizeof(p) = %zu\n", sizeof(p)); // 输出 40 (5 * 8,在64位系统上)
// 遍历指针数组,通过指针间接访问其指向的值
for (int i = 0; i < 5; i++) {
printf("*p[%d] = %d\n", i, *p[i]);
}
// p[i] 等价于 *(p + i)
printf("*(p + 2) = %d\n", *(p + 2)); // 输出 c 的值 30
return 0;
}
内存图解:
栈内存
+-------------------+ <-- p (数组首地址)
| 0x... (a的地址) | <-- p[0]
+-------------------+
| 0x... (b的地址) | <-- p[1]
+-------------------+
| 0x... (c的地址) | <-- p[2]
+-------------------+
| 0x... (d的地址) | <-- p[3]
+-------------------+
| 0x... (e的地址) | <-- p[4]
+-------------------+
|
v
+-------------------+ <-- a 的内存
| 10 |
+-------------------+
+-------------------+ <-- b 的内存
| 20 |
+-------------------+
... 以此类推 ...
总结与对比
| 特性 | 一维数组 int arr[5] |
数组指针 int (*p)[5] |
指针数组 int *p[5] |
|---|---|---|---|
| 本质 | 一块连续的内存空间 | 一个指针变量 | 一个数组 |
| 指向 | 存放数据元素 | 指向一个完整的数组 | 存放多个指针 |
sizeof |
sizeof(类型) * 元素个数 |
sizeof(指针) (通常是 8 字节) |
sizeof(指针) * 元素个数 |
p + 1 |
跳过 sizeof(类型) 个字节 |
跳过 sizeof(类型) * 元素个数 个字节 |
跳过 sizeof(指针) 个字节 |
| 典型用途 | 存储同类型数据集合 | 处理二维数组(作为行指针) | 存储字符串数组或一组变量的地址 |
| 记忆口诀 | 数据的集合 | 指向数组的指针 | 指针的集合 |
何时使用?
- 一维数组:当你需要一组固定数量的、同类型的数据时,例如存储一个班级的5个学生的成绩。
- 数组指针:
- 主要用途:处理二维数组,二维数组在内存中是连续存储的,每一行都可以看作一个一维数组,数组指针(行指针)可以方便地遍历二维数组的每一行。
int matrix[3][4]; int (*p)[4] = matrix; // p 指向 matrix 的第一行 p++; // p 指向 matrix 的第二行
- 作为函数参数,表示函数期望接收一个二维数组。
- 主要用途:处理二维数组,二维数组在内存中是连续存储的,每一行都可以看作一个一维数组,数组指针(行指针)可以方便地遍历二维数组的每一行。
- 指针数组:
- 主要用途:管理多个字符串,C语言中字符串通常用字符数组表示,指针数组可以方便地管理这些字符串数组的首地址,实现类似“字符串列表”的功能。
char *names[] = {"Alice", "Bob", "Charlie"}; // names[0] 指向 "Alice" 的首字符 'A' - 当你需要一组指针来指向不同的变量时。
- 主要用途:管理多个字符串,C语言中字符串通常用字符数组表示,指针数组可以方便地管理这些字符串数组的首地址,实现类似“字符串列表”的功能。
希望这个详细的解释能帮助你彻底理解这三个概念!关键在于区分“什么是指针”和“什么是数组”,以及它们是如何组合在一起的。
