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 = arrarr 是数组首元素的地址,类型是 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] 定义方式 与二维数组的关系 作为函数参数 int (*p)[5]; // 指向5个int的数组 p = &arr; // 取整个数组的地址 int matrix[3][4]; matrix 的类型是 int (*)[4] void func(int (*p)[4], int rows) VLA: void func(int rows, int cols, int (*p)[cols]) 核心:数组指针 = 指向数组的指针,步长 = 整个数组

总结一下

数组指针这东西,说白了就是“指向数组的指针”。它跟二维数组是天生一对,因为二维数组的数组名本身就是数组指针类型。用数组指针作为函数参数,可以优雅地传递二维数组。

记住几个要点:

  • 定义时括号不能省:int (*p)[N]
  • 数组指针的步长是整个数组的大小
  • 二维数组名可以直接赋值给对应的数组指针
  • 函数参数中用数组指针时,第二维大小必须明确

嗯,这些搞明白了,数组指针这块就算拿下了。


公众号:蓝海资料掘金营,微信deep3321