11. 数组指针:数组指针的定义、数组指针与二维数组、数组指针作为函数参数
数组指针,这名字听着就有点绕。很多人刚学的时候,容易跟指针数组搞混。我当年也踩过这个坑——面试的时候被问到“数组指针和指针数组的区别”,脑子一热答反了,场面一度很尴尬。
说白了,数组指针是一个指针,它指向的是一个数组。而指针数组呢,是一个数组,里面存的是指针。嗯,记住这个核心区别就够了。
11.1 数组指针的定义
先看定义方式:
// 定义一个数组指针,指向包含5个int元素的数组
int (*p)[5];
注意括号的位置。没有括号的话,int *p[5] 就成了指针数组。括号让 * 先和 p 结合,说明 p 是一个指针,然后它指向 int [5] 这个类型。
我个人习惯这样理解:从变量名出发,先看它和谁结合。(*p) 说明 p 是指针,剩下的 int [5] 就是它指向的类型。
来个完整的例子:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5]; // 数组指针
p = &arr; // 指向整个数组
// 通过数组指针访问元素
for (int i = 0; i < 5; i++) {
printf("%d ", (*p)[i]); // 注意写法
}
printf("\n");
return 0;
}
这里有个细节:p = &arr 而不是 p = arr。arr 是数组首元素的地址,类型是 int *;而 &arr 是整个数组的地址,类型是 int (*)[5]。虽然数值上一样,但类型不同,步长也不同。
关键区别:p+1 会跳过整个数组(5个int),而 arr+1 只跳过一个int。
11.2 数组指针与二维数组
数组指针最常用的场景,就是配合二维数组。为什么?因为二维数组的数组名,本质上就是一个数组指针。
看这个:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// matrix 的类型是 int (*)[4]
// 它指向一个包含4个int的数组
你想想看,matrix 是二维数组的数组名,它代表的是第一行的地址,也就是一个 int [4] 类型的数组的地址。这不就是数组指针吗?
所以用数组指针来遍历二维数组,非常自然:
#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; // 数组指针指向二维数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d ", p[i][j]); // 直接用下标访问
}
printf("\n");
}
return 0;
}
这里 p[i][j] 等价于 *(*(p + i) + j)。p 指向一行,p+i 指向第 i 行,再解引用得到这一行的首地址,然后偏移 j 个元素。
小技巧:用数组指针处理二维数组时,可以把二维数组“降维”成一维数组的集合来理解。每一行就是一个一维数组,数组指针就是指向这个一维数组的指针。
11.3 数组指针作为函数参数
这是实际项目中最常用的地方。当你要写一个函数处理二维数组时,参数类型怎么写?
我曾经在写一个图像处理模块时,需要传递一个像素矩阵。一开始我写的是 void process(int **data, int rows, int cols),结果发现根本不对——二维数组和 int ** 不是一回事。
正确的做法是用数组指针:
// 正确的写法:用数组指针
void print_matrix(int (*matrix)[4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
print_matrix(matrix, 3);
return 0;
}
注意参数 int (*matrix)[4] 中的 4 是固定的。这意味着这个函数只能处理列数为4的二维数组。如果想处理任意列数呢?
嗯,这里有个限制。C语言的数组指针必须知道第二维的大小。如果你需要更灵活的方式,可以考虑:
- 用一维数组模拟二维(手动计算偏移)
- 用指针数组(每行单独分配)
- 用变长数组(C99支持,但要注意编译器兼容性)
变长数组的写法是这样的:
void print_matrix_vla(int rows, int cols, int (*matrix)[cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%2d ", matrix[i][j]);
}
printf("\n");
}
}
这里 cols 参数必须在 matrix 之前声明,这样编译器才能知道 int (*matrix)[cols] 中的 cols 是什么。
避坑指南:我曾经在嵌入式项目中用过VLA,结果换了个编译器就编译不过了。如果你的代码需要跨平台或跨编译器,建议慎用变长数组。老老实实把二维数组当一维数组传,手动算偏移,反而更可靠。
知识结构图
下面这张图帮你理清数组指针的核心脉络:
总结一下
数组指针这东西,说白了就是“指向数组的指针”。它跟二维数组是天生一对,因为二维数组的数组名本身就是数组指针类型。用数组指针作为函数参数,可以优雅地传递二维数组。
记住几个要点:
- 定义时括号不能省:
int (*p)[N] - 数组指针的步长是整个数组的大小
- 二维数组名可以直接赋值给对应的数组指针
- 函数参数中用数组指针时,第二维大小必须明确
嗯,这些搞明白了,数组指针这块就算拿下了。
公众号:蓝海资料掘金营,微信deep3321