深入理解指向数组的指针以及寻址运算
文章目录
- 深入理解指向数组的指针以及寻址运算
-
- 一、为什么需要指向数组的指针?
- 二、基础概念回顾
-
- [1. 数组名是什么?](#1. 数组名是什么?)
- [2. 指针运算](#2. 指针运算)
- 三、一维数组的指针与寻址
- 四、二维数组的指针与寻址
-
- [1. 行指针(指向一维数组的指针)](#1. 行指针(指向一维数组的指针))
- 示例2:通过行指针访问二维数组
- [2. 另一种理解:二级指针?](#2. 另一种理解:二级指针?)
- [五、数组指针 vs 指针数组](#五、数组指针 vs 指针数组)
- 六、实际应用场景
-
- [1. 函数参数传递](#1. 函数参数传递)
- [2. 动态分配多维数组](#2. 动态分配多维数组)
- 七、常见陷阱与注意事项
- 八、总结
在C语言的学习中,指针和数组是两个核心概念,而它们之间的关系更是初学者的一大难点。尤其是"指向数组的指针"和"指针的寻址运算",理解透彻了,才能真正掌握C语言的精髓。今天我们就来系统地探讨这个话题,并辅以丰富的代码示例,帮你彻底搞懂。
一、为什么需要指向数组的指针?
数组是内存中一块连续的空间,而指针则用来存储内存地址。通过指针操作数组,可以实现:
- 更灵活的数组访问(如动态遍历)
- 高效地传递数组给函数
- 实现动态多维数组
- 理解底层内存布局
二、基础概念回顾
1. 数组名是什么?
在大多数表达式中,数组名会被转换为指向其第一个元素的指针。例如:
c
int arr[5] = {1,2,3,4,5};
int *p = arr; // arr 等价于 &arr[0]
2. 指针运算
指针加1,实际增加的字节数等于指针所指类型的字节数。即:
p + i = p + i * sizeof(类型)
三、一维数组的指针与寻址
示例1:通过指针遍历一维数组
c
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 指向第一个元素
printf("数组首地址: %p\n", arr);
printf("p 的值: %p\n", p);
printf("&arr[0]: %p\n\n", &arr[0]);
// 指针遍历
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(p+%d) = %d, 地址: %p\n",
i, arr[i], i, *(p + i), p + i);
}
// 指针移动演示
printf("\n初始 *p = %d\n", *p);
p++; // 移动到下一个元素
printf("p++ 后 *p = %d\n", *p);
p += 2; // 向后移动两个元素
printf("p+=2 后 *p = %d\n", *p);
return 0;
}
输出说明 :
p + i 的地址每次增加4个字节(假设 int 占4字节),且 *(p + i) 与 arr[i] 等价。
四、二维数组的指针与寻址
二维数组本质上是"数组的数组"。例如 int matrix[3][4] 是一个包含3个元素的数组,每个元素又是一个包含4个int的数组。
1. 行指针(指向一维数组的指针)
要指向整个一行(即一个一维数组),需要使用行指针:
c
int (*p)[4] = matrix; // p 指向包含4个int的一维数组
这里的 p 就是一个指向数组的指针,每次 p+1 会跳过一行(4个int)。
示例2:通过行指针访问二维数组
c
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9,10,11,12}
};
int (*p)[4] = matrix; // 行指针
printf("matrix: %p\n", matrix);
printf("matrix[0]: %p\n", matrix[0]);
printf("&matrix[0][0]: %p\n\n", &matrix[0][0]);
// 通过行指针访问元素
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
// 三种等价写法
printf("matrix[%d][%d] = %d ", i, j, matrix[i][j]);
printf("= *(*(p+%d)+%d) = %d ", i, j, *(*(p + i) + j));
printf("地址: %p\n", *(p + i) + j);
}
printf("\n");
}
// 行指针的步长
printf("p 的地址: %p\n", p);
printf("p+1 的地址: %p\n", p + 1);
printf("一行大小: %ld 字节\n", sizeof(int) * 4);
return 0;
}
关键点:
p + i指向第 i 行的首地址。*(p + i)得到第 i 行的首元素地址(相当于matrix[i]或&matrix[i][0])。*(p + i) + j得到第 i 行第 j 列元素的地址。*(*(p + i) + j)取出该元素的值。
2. 另一种理解:二级指针?
很多人会把二维数组名赋值给 int **,这是错误的。因为 matrix 不是指针的指针,而是连续的二维内存块。int ** 适用于指针数组,后面会讲。
五、数组指针 vs 指针数组
这两个概念极易混淆,我们来区分一下:
| 类型 | 定义 | 含义 |
|---|---|---|
| 数组指针 | int (*p)[n]; |
p 是一个指针,指向一个包含 n 个 int 的数组 |
| 指针数组 | int *p[n]; |
p 是一个数组,包含 n 个指向 int 的指针 |
示例3:指针数组
c
#include <stdio.h>
int main() {
int a = 1, b = 2, c = 3;
int *pArr[3] = {&a, &b, &c}; // 指针数组
for (int i = 0; i < 3; i++) {
printf("pArr[%d] 指向的值: %d, 地址: %p\n", i, *pArr[i], pArr[i]);
}
// 二维数组 vs 指针数组的内存布局完全不同
return 0;
}
指针数组的每个元素独立指向一个 int,内存不连续;而二维数组是连续存储的。
六、实际应用场景
1. 函数参数传递
当需要将二维数组传递给函数时,必须指明第二维的大小,或者使用数组指针:
c
void printMatrix(int rows, int cols, int (*mat)[cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", mat[i][j]); // 或 *(*(mat+i)+j)
}
printf("\n");
}
}
// 调用:printMatrix(3, 4, matrix);
2. 动态分配多维数组
利用数组指针可以方便地分配动态二维数组:
c
int (*p)[4] = malloc(3 * sizeof(*p)); // 分配 3 行,每行 4 个 int
// 使用 p[i][j] 访问
free(p);
七、常见陷阱与注意事项
-
不要把二维数组名直接赋给
int **,会导致编译错误或运行时崩溃。 -
数组指针的括号不能少 :
int (*p)[4]与int *p[4]天差地别。 -
指针加减运算的单位是指针所指类型的大小,理解这一点才能正确寻址。
-
arr和&arr的区别 :arr是第一个元素的地址,&arr是整个数组的地址,数值相同但步长不同。cint arr[5]; printf("%p %p\n", arr, arr+1); // 相差 4 字节 printf("%p %p\n", &arr, &arr+1); // 相差 20 字节
八、总结
指向数组的指针是C语言中强大而灵活的工具,它让我们能够以指针的方式操作数组,特别是在多维数组和动态内存管理中不可或缺。理解寻址运算的关键在于:
- 明确指针的类型,知道每次移动的字节数。
- 分清"数组指针"和"指针数组"。
- 掌握
*(*(p+i)+j)的层层递进关系。
希望通过本文的讲解和代码示例,你能对指向数组的指针以及寻址运算有更深刻的理解。多动手写代码,多调试观察内存地址,才能真正融会贯通。
欢迎在评论区留言讨论,分享你的学习心得或疑问!