数组指针:从定义到实战
数组指针,这名字听着就有点绕。很多人刚学的时候,容易把它和指针数组搞混。我当年也踩过这个坑,面试时被问到“数组指针和指针数组的区别”,脑子一热就答反了……嗯,今天咱们就把这个硬骨头啃下来。
1. 数组指针的定义
说白了,数组指针就是一个指针,它指向的是一个数组。注意,不是指向数组里的某个元素,而是指向整个数组。
定义格式有点特别:
类型 (*指针名)[数组大小];
比如:
int (*p)[5]; // p是一个指针,指向一个包含5个int元素的数组
这里括号不能省。如果写成 int *p[5],那就变成指针数组了——一个数组,里面存了5个int*指针。我刚开始写代码时,就经常把括号漏掉,编译器报错还一脸懵。
核心记忆点:
int (*p)[5]→ 数组指针(指针)int *p[5]→ 指针数组(数组)
怎么用呢?看个例子:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int (*p)[5] = &arr; // 取整个数组的地址
printf("arr[2] = %d\n", (*p)[2]); // 输出30
return 0;
}
注意,这里用的是 &arr,不是 arr。虽然 arr 和 &arr 的值一样,但类型不同。arr 是 int*,指向第一个元素;&arr 是 int(*)[5],指向整个数组。这个区别在指针运算时特别明显——arr+1 跳过一个int,&arr+1 跳过整个数组(5个int)。
我的习惯: 在需要传递整个数组的地址时,我才会用数组指针。平时遍历元素,直接用数组名就行,没必要搞复杂。
2. 数组指针与二维数组
这才是数组指针真正发光的地方。二维数组在内存里是连续存储的,但它的类型本质上是“数组的数组”。比如:
int matrix[3][4]; // 3行4列
matrix 的类型是 int[3][4],退化后是 int(*)[4]——没错,就是一个数组指针,指向一个长度为4的int数组。所以,用数组指针来操作二维数组,非常自然。
看代码:
#include <stdio.h>
void print_matrix(int (*p)[4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d ", p[i][j]); // p[i] 就是第i行,类型是int[4]
}
printf("\n");
}
}
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
print_matrix(matrix, 3); // 数组名直接传,自动退化为 int(*)[4]
return 0;
}
这里 p[i][j] 的写法,和直接用二维数组一模一样。为什么?因为 p[i] 等价于 *(p+i),拿到的是第i行的首地址,类型是 int*,然后再用 [j] 取第j个元素。
我曾经踩过的坑: 在函数参数里写 int p[][4] 和 int (*p)[4] 是等价的,但很多人只记得第一种写法。有一次我接手一个遗留代码,里面全是 int **p 来传二维数组,结果一运行就段错误。因为 int** 和 int(*)[4] 的内存布局完全不同,不能混用。
下面这张图,帮你理清数组指针和二维数组的关系:
3. 数组指针作为函数参数
在实际项目中,我们经常需要把二维数组传给函数处理。用数组指针做参数,是最地道的方式。
函数声明有三种等价形式:
| 写法 | 说明 |
|---|---|
void func(int (*p)[4], int rows) |
数组指针形式,最明确 |
void func(int p[][4], int rows) |
数组形式,编译器自动解释为指针 |
void func(int p[3][4], int rows) |
指定行数,但实际会被忽略 |
我个人偏爱第一种写法,因为它明确告诉读者:这里传进来的是一个指针,不是数组。第二种写法容易让人误以为在传值(数组会复制),其实C语言里数组传参都是传地址。
看一个实际点的例子——计算二维数组所有元素的和:
#include <stdio.h>
// 数组指针作为参数
int sum_matrix(int (*p)[4], int rows) {
int total = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
total += p[i][j]; // 或者 *(*(p+i)+j)
}
}
return total;
}
int main() {
int data[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int result = sum_matrix(data, 3);
printf("总和 = %d\n", result); // 输出78
return 0;
}
这里有个细节:p[i][j] 和 *(*(p+i)+j) 完全等价。你想想看,p+i 跳过了 i 个长度为4的int数组,拿到第i行的地址;再解引用得到第i行的首元素地址;最后加 j 取元素。这套逻辑,其实就是二维数组下标运算的底层实现。
避坑指南:
- 函数参数里,第二维的大小必须指定(比如这里的4),因为编译器需要知道每行有多少个元素才能正确计算偏移。
- 如果第二维不确定,那就不能用数组指针了,得用
int **配合手动计算偏移,但那样代码会复杂很多。
我的经验: 在嵌入式开发中,经常要处理固定大小的图像数据(比如 320x240 的灰度图)。我会用 void process_image(uint8_t (*img)[240], int rows) 这样的接口,清晰又高效。曾经有个同事用 uint8_t ** 来传,结果每次都要自己算行偏移,还容易越界。后来我帮他改成数组指针,代码立马清爽了。
最后总结一下:数组指针就是“指向数组的指针”,它和二维数组是天作之合。用数组指针做函数参数,能让接口更清晰,也能避免很多低级错误。嗯,这部分内容就到这里,你可以在自己的代码里试试看。