指针数组和数组指针是 C/C++ 中容易混淆的两个概念,以下是详细对比:
1. 指针数组(Array of Pointers)
- 定义:一个数组,其元素是 指针类型。
- 语法:
type* arr[元素个数];
- 例如:
int* ptr_array[5];
表示一个包含 5 个int*
类型指针的数组。
- 例如:
- 内存布局:
- 数组本身占用连续内存空间,每个元素存储的是 指针的值(即地址)。
- 每个指针可以指向任意地址(例如不同变量、不同数组的元素)。
- 用途:
- 存储多个指针(如字符串数组、函数指针数组)。
- 示例:
int a = 1, b = 2, c = 3; int* ptr_array[3] = {&a, &b, &c}; // 每个元素指向一个整型变量
2. 数组指针(Pointer to Array)
- 定义:一个指针,指向 数组类型。
- 语法:
type (*ptr)[数组长度];
- 例如:
int (*arr_ptr)[10];
表示一个指向int[10]
类型数组的指针。
- 例如:
- 内存布局:
- 指针本身只存储一个地址(指向数组的首元素)。
- 通过该指针访问时,编译器会根据数组长度计算元素偏移。
- 用途:
- 操作多维数组(如作为函数参数传递二维数组)。
- 示例:
int arr[5] = {1, 2, 3, 4, 5}; int (*arr_ptr)[5] = &arr; // 指向整个数组
关键区别总结
特性 | 指针数组 | 数组指针 |
---|---|---|
类型 | 数组元素是 type* (指针) | 指向 type[长度] (数组类型) |
声明语法 | type* arr[大小]; | type (*ptr)[大小]; |
内存占用 | 大小 × sizeof(指针) | sizeof(指针) (通常 8 字节) |
解引用操作 | arr[i] 是一个 type* 类型指针 | *ptr 是一个 type[大小] 类型数组 |
典型用途 | 存储多个指针(如字符串数组) | 操作多维数组或动态数组 |
示例对比
-
指针数组:
int a = 1, b = 2, c = 3; int* ptr_array[3] = {&a, &b, &c}; printf("%d", *ptr_array[0]); // 输出 1
-
数组指针:
int arr[3] = {10, 20, 30}; int (*arr_ptr)[3] = &arr; printf("%d", (*arr_ptr)[1]); // 输出 20
常见误区
- 语法陷阱:
int* ptr1[5]
(指针数组)和int (*ptr2)[5]
(数组指针)的括号位置不同,含义完全不同。 - 数组名 vs 指针:数组名
arr
是数组首元素的地址(类型为int*
),而&arr
是整个数组的地址(类型为int(*)[N]
)。
总结
- 指针数组:存指针的数组,每个元素是独立指针。
- 数组指针:指向整个数组的指针,通过它访问数组元素时需考虑数组长度。
代码
int arr[3] = {10, 20, 30};
int (*ptr)[3] = &arr; // ptr 是指向数组的指针,类型为 int(*)[3]
表达式 1:*((*ptr) + 1)
分步解析:
ptr
的类型:int(*)[3]
(指向长度为3的数组的指针)。*ptr
的类型:int[3]
(即数组arr
本身)。*ptr
的退化:在表达式中,数组名会退化为指向首元素的指针,因此*ptr
退化为int*
类型,指向arr[0]
。(*ptr) + 1
:*ptr
是int*
类型,指向arr[0]
。+1
操作会移动1 * sizeof(int)
字节(假设int
占4字节)。- 结果地址:
&arr[0] + 1
→&arr[1]
。
*((*ptr) + 1)
:- 解引用
&arr[1]
,得到arr[1]
的值 20。
- 解引用
内存布局图示:
arr 地址:0x1000
| 0x1000 | 0x1004 | 0x1008 |
| 10 | 20 | 30 |
ptr 地址:0x2000
| 0x2000 |
| 0x1000 | // ptr 存储的是数组 arr 的地址
(*ptr) + 1 → 0x1000 + 1 * 4 = 0x1004 → 指向 20
表达式 2:*(ptr + 1)
分步解析:
ptr
的类型:int(*)[3]
(指向长度为3的数组的指针)。ptr + 1
:ptr
的类型是int(*)[3]
,因此+1
操作会移动1 * sizeof(int[3])
字节。sizeof(int[3]) = 3 * 4 = 12
字节(假设int
占4字节)。- 结果地址:
0x1000 + 12 = 0x100C
(超出原数组范围)。
*(ptr + 1)
:- 解引用非法地址
0x100C
,导致 未定义行为(可能读取垃圾值或程序崩溃)。
- 解引用非法地址
内存布局图示:
ptr + 1 → 0x1000 + 12 = 0x100C(该地址未分配给 arr)
| 0x100C | 0x1010 | 0x1014 | ...(未知内存)
| ???? | ???? | ???? |
关键区别总结
表达式 | 类型 | 指针运算步长 | 访问的内存地址 | 结果 |
---|---|---|---|---|
*((*ptr) + 1) | int | 1 * sizeof(int) | &arr[1] (合法地址) | 20 |
*(ptr + 1) | int[3] (数组) | 1 * sizeof(int[3]) | &arr + 1 (非法地址) | 未定义行为(如崩溃) |
为什么会有这样的差异?
ptr
的类型决定了指针运算的步长:ptr
是int(*)[3]
,ptr + 1
跳过整个数组(3个int
)。*ptr
退化为int*
,(*ptr) + 1
跳过一个int
。
- 数组指针 vs 普通指针:
ptr
是数组指针,操作的是整个数组。*ptr
是普通指针,操作的是数组的元素。
类比解释
假设有一个书架:
ptr
:指向整个书架(书架地址)。ptr + 1
→ 移动到下一个书架(可能不存在)。
*ptr
:指向书架上的第一本书(书的地址)。(*ptr) + 1
→ 移动到书架上的第二本书。
代码验证
#include <stdio.h>
int main() {
int arr[3] = {10, 20, 30};
int (*ptr)[3] = &arr;
printf("表达式1: %d\n", *((*ptr) + 1)); // 输出 20
printf("表达式2: %d\n", *(ptr + 1)); // 未定义行为(可能输出垃圾值或崩溃)
return 0;
}
总结
*((*ptr) + 1)
:操作数组元素,合法且安全。*(ptr + 1)
:操作数组外的内存,危险且非法。