把每个概念看作一个“盒子”,我们要搞清楚这个盒子里装的是什么,以及如何使用这个盒子。

(图片来源网络,侵删)
一维数组
这是最基础的概念。
- 定义:一组相同类型的数据的集合,在内存中是连续存放的。
- 声明:
类型 数组名[大小]; - 本质:一个变量集合,数组名
arr在大多数情况下会“衰变”为其首元素的地址,但它本身也代表整个数组。
示例代码:
#include <stdio.h>
int main() {
// 声明一个包含5个int元素的一维数组
int arr[5] = {10, 20, 30, 40, 50};
// 1. 通过下标访问元素
printf("arr[2] = %d\n", arr[2]); // 输出: 30
// 2. 通过指针访问元素 (数组名arr会衰变为指向首元素的指针)
int *p = arr; // 等同于 int *p = &arr[0];
printf("*p = %d\n", *p); // 输出: 10
printf("*(p+1) = %d\n", *(p+1)); // 输出: 20
// 3. arr和&p的区别 (关键!)
// arr: 代表数组,&arr代表整个数组的地址
// arr的值和&arr[0]的值相同,但类型和含义不同
printf("arr 的地址: %p\n", (void*)arr);
printf("&arr[0] 的地址: %p\n", (void*)&arr[0]);
printf("&arr 的地址: %p\n", (void*)&arr);
// 指针运算验证
printf("arr + 1 的地址: %p\n", (void*)(arr + 1)); // 地址 + sizeof(int)
printf("&arr + 1 的地址: %p\n", (void*)(&arr + 1)); // 地址 + sizeof(整个数组)
return 0;
}
小结: 一维数组就是一个固定大小的、连续的内存块。
指针数组
这个名字有点误导性,它不是一个数组的指针,而是一个存放指针的数组。

(图片来源网络,侵删)
- 定义:一个数组,它的每个元素都是一个指针。
- 声明:
*数组名[大小];注意:[]的优先级高于,所以是先和数组名结合成一个数组,再由说明其元素是指针。 - 本质:一个指针的集合,它是一个数组,所以它的元素在内存中是连续的,但每个指针指向的内存地址可以是任意的。
用途:常用于处理多个字符串(字符串字面量就是char*类型),或者管理一组不同位置的同类型变量。
示例代码:
#include <stdio.h>
int main() {
// 声明一个包含3个int指针的数组
int *ptr_arr[3];
int a = 100;
int b = 200;
int c = 300;
// 让数组的每个指针指向一个int变量
ptr_arr[0] = &a;
ptr_arr[1] = &b;
ptr_arr[2] = &c;
// 遍历指针数组,通过指针获取它指向的值
for (int i = 0; i < 3; i++) {
printf("*(ptr_arr[%d]) = %d\n", i, *(ptr_arr[i]));
}
// 更直观的写法
printf("*(ptr_arr[0]) = %d\n", *ptr_arr[0]); // 输出: 100
return 0;
}
内存模型示意图:
+-------+ +-------+ +-------+
| 100 | | 200 | | 300 | <-- a, b, c 变量所在的内存
+-------+ +-------+ +-------+
^ ^ ^
| | |
+-------+ +-------+ +-------+
| &a | | &b | | &c | <-- 指针数组 ptr_arr 的元素
+-------+ +-------+ +-------+
^ ^ |
| | |
+-----------------------------------+
| ptr_arr[0] | ptr_arr[1] | ptr_arr[2] | <-- 指针数组 ptr_arr 本身(连续内存)
+-----------------------------------+
小结: 指针数组是一个数组,装满了指针。

(图片来源网络,侵删)
数组指针
这个名字也容易混淆,它不是一个指针数组,而是一个指向数组的指针。
- 定义:一个指针,它指向一个完整的数组。
- 声明:
(*指针名)[大小];*注意:是必须的,它强制`先和指针名结合,说明这是一个指针,再[大小]`说明它指向的数组类型。** - 本质:一个指针,这个指针的类型是“指向一个包含N个XX类型元素的数组的指针”。
用途:在函数参数中传递多维数组,或者需要以“数组”为单位进行操作时使用。
示例代码:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 声明一个数组指针,它指向一个包含5个int元素的数组
int (*p_arr)[5] = &arr; // 注意是 &arr,取整个数组的地址
// 使用数组指针访问元素
// p_arr 指向整个数组,*p_arr 就代表这个数组本身
// (*p_arr)[i] 就是对数组 arr[i] 的访问
printf("(*p_arr)[2] = %d\n", (*p_arr)[2]); // 输出: 30
// 指针运算
// p_arr + 1 会跳过整个数组,指向下一个(不存在的)5个int的数组
// (p_arr + 1) 的地址 = &arr 的地址 + sizeof(整个数组)
printf("p_arr 的地址: %p\n", (void*)p_arr);
printf("(p_arr + 1) 的地址: %p\n", (void*)(p_arr + 1));
// 对比普通指针
int *p = arr;
printf("p 的地址: %p\n", (void*)p);
printf("(p + 1) 的地址: %p\n", (void*)(p + 1)); // 地址 + sizeof(int)
return 0;
}
小结: 数组指针是一个指针,它指向一个完整的数组。
指针数组指针
这是最复杂的一个,但只要拆解开来,就不难理解。
- 定义:一个指针,它指向一个“指针数组”。
- 声明:
*(*指针名)[大小];或者(*指针名)[大小]再加一个,我们可以分两步看。 - 本质:一个指针,这个指针的类型是“指向一个包含N个XX类型指针的数组的指针”。
用途:相对少见,通常出现在更复杂的结构体嵌套或函数指针返回复杂类型时,一个典型场景是处理一个指针数组的数组,比如一个字符串数组的数组。
示例代码:
#include <stdio.h>
int main() {
// 1. 先定义一个指针数组 (一个装满了char*的数组)
char *strs[3] = {
"Hello",
"World",
"C Language"
};
// 2. 再定义一个指针,让它指向这个指针数组
// strs 是一个 char*[3] 类型的数组
// &strs 是一个 char*(*)[3] 类型的地址(一个指向3个char指针的数组的指针)
char *(*p_strs)[3] = &strs;
// 3. 使用这个“指针数组指针”来访问内容
// *p_strs 解引用后,就得到了 strs 数组本身
// (*p_strs)[i] 就得到了 strs[i],也就是一个 char* (字符串)
// (*p_strs)[i][j] 就得到了字符串的第j个字符
printf("(*p_strs)[1] = %s\n", (*p_strs)[1]); // 输出: World
printf("*(*p_strs)[1] = %c\n", *(*p_strs)[1]); // 输出: W
// 通过指针运算访问第二个字符串
// p_strs + 1 会指向下一个 char*[3] 的数组
// *(p_strs + 1) 会得到下一个 char*[3] 的数组
// (*(p_strs + 1))[0] 会得到那个数组的第一个 char*
// 但我们的 strs 是最后一个了,所以这里只是演示语法
// printf("(*(p_strs + 1))[0] = %s\n", (*(p_strs + 1))[0]); // 越界访问
return 0;
}
内存模型示意图:
+-------+ +-------+ +-------+
| "Hello" | | "World" | | "C Language" | <-- 字符串常量
+-------+ +-------+ +-------+
^ ^ |
| | |
+-------+ +-------+ +-------+
| &s1 | | &s2 | | &s3 | <-- strs 指针数组的元素
+-------+ +-------+ +-------+
^ ^ |
| | |
+-----------------------------------+
| strs[0] | strs[1] | strs[2] | <-- strs 指针数组本身
+-----------------------------------+
^
|
+------------------|-----------------+
| p_strs (存放 &strs 的地址) |
+-------------------------------------+
小结: 指针数组指针是一个指针,它指向的是一个“指针数组”。
总结与区分
| 名称 | 语法 | 本质 | 通俗解释 |
|---|---|---|---|
| 一维数组 | int arr[5]; |
变量集合 | 一排连续的抽屉,每个抽屉里放一个int。 |
| 指针数组 | int *p_arr[3]; |
指针的集合 | 一个架子,架子上放了3个标签,每个标签指向一个int。 |
| 数组指针 | int (*p_arr)[5]; |
指向数组的指针 | 一个遥控器,它控制的是一整排5个抽屉。 |
| 指针数组指针 | int *(*p_p_arr)[3]; |
指向指针数组的指针 | 一个遥控器,它控制的是一个放标签的架子。 |
*区分技巧:看中间的`和[]`如何结合**
-
*`int p[3];`**
p先和[3]结合,是p[3](一个数组)。- 再和 结合,是
*p[3](一个数组,其元素是 ,即指针)。 - 指针数组。
-
*`int (p)[3];`**
p先和 结合,是*p(一个指针)。- 再和
[3]结合,是(*p)[3](一个指针,指向[3]数组)。 - 数组指针。
-
`int p[3];`**
p先和[3]结合,是p[3](一个数组)。- 再和 结合,是
**p[3](一个数组,其元素是 ,即指向指针的指针)。 - 二级指针数组。
-
`int (p)[3];`**
p先和 结合,是**p(一个指向指针的指针)。- 再和
[3]结合,是(**p)[3](一个指向指针的指针,这个指针指向[3]数组)。 - 指向数组指针的指针,这和“指针数组指针”
*(*p)[3]在概念上非常接近,都是指向一个数组指针。
希望这个从简到繁的拆解和比喻能帮助你彻底理解这几个概念!
