MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C语言指向多维数组的指针及其应用

2022-06-213.9k 阅读

C语言指向多维数组的指针及其应用

多维数组基础回顾

在C语言中,多维数组是数组的数组。以二维数组为例,它可以看作是一个表格形式的数据结构,有行和列。定义一个二维数组的一般形式为:type arrayName[rowSize][columnSize];,例如:

int matrix[3][4];

这里matrix是一个二维数组,有3行4列,总共可以存储3 * 4 = 12个整数。

二维数组在内存中是按行存储的。也就是说,先存储第一行的所有元素,接着存储第二行的所有元素,以此类推。例如,对于matrix数组,内存中的存储顺序为matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], matrix[1][0], matrix[1][1], ..., matrix[2][3]

指向多维数组的指针定义

  1. 指向二维数组元素的指针 可以定义一个指针指向二维数组的单个元素。由于二维数组元素本质上就是普通变量,所以和指向普通变量的指针定义类似。例如:
int matrix[3][4];
int *ptr = &matrix[0][0];

这里ptr是一个指向int类型的指针,初始化为matrix数组第一个元素matrix[0][0]的地址。通过这个指针,可以像访问一维数组元素一样访问二维数组元素。例如,要访问matrix[1][2],可以通过指针运算:

int value = *(ptr + 1 * 4 + 2);

这里1 * 4 + 2是因为二维数组按行存储,第一行有4个元素,所以要先跳过第一行的4个元素,再加上第二列的偏移。

  1. 指向一维数组的指针(行指针) 还可以定义一种指针,它指向二维数组的一行,也就是一个一维数组。这种指针被称为行指针。定义行指针的一般形式为:type (*pointerName)[columnSize];。例如:
int matrix[3][4];
int (*rowPtr)[4] = &matrix[0];

这里rowPtr是一个行指针,它指向一个包含4个int类型元素的一维数组。&matrix[0]表示matrix数组第一行的地址。注意(*rowPtr)的括号不能省略,否则就变成了指针数组的定义。

行指针的应用

  1. 遍历二维数组 利用行指针可以更方便地遍历二维数组。下面是一个示例代码:
#include <stdio.h>

int main() {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*rowPtr)[4] = matrix;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", *(*(rowPtr + i)+j));
        }
        printf("\n");
    }
    return 0;
}

在这段代码中,外层循环通过rowPtr + i来移动到不同的行,内层循环通过*(rowPtr + i)+j来获取当前行中不同列的元素地址,再通过*取值。

  1. 作为函数参数 当二维数组作为函数参数传递时,实际上传递的是数组首元素的地址,也就是第一行的地址。这时候使用行指针作为函数参数是很合适的。例如:
#include <stdio.h>

void printMatrix(int (*rowPtr)[4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", *(*(rowPtr + i)+j));
        }
        printf("\n");
    }
}

int main() {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    printMatrix(matrix, 3);
    return 0;
}

printMatrix函数中,int (*rowPtr)[4]作为参数接收二维数组的首地址,rows参数指定行数。这样可以灵活地处理不同行数但列数固定为4的二维数组。

指向多维数组的指针与数组名的关系

  1. 二维数组名的含义 对于二维数组int matrix[3][4];matrix代表数组首元素的地址,也就是第一行的地址,其类型是int (*)[4]matrix + 1会跳过一行的元素,也就是跳过4个int类型的元素,因为一行有4个元素。

    &matrix[0][0]是数组第一个元素的地址,类型是int *&matrix[0][0] + 1会跳过一个int类型的元素。

  2. 数组名与指针的互换性 在很多情况下,数组名可以当作指针使用。例如,在函数调用中,printMatrix(matrix, 3);printMatrix(&matrix[0], 3);效果是一样的,因为matrix&matrix[0]都表示第一行的地址。

    但是需要注意,数组名和指针还是有区别的。数组名是一个常量指针,不能对其进行赋值操作,例如matrix = matrix + 1;是错误的,而指针变量可以进行赋值操作。

三维及更高维数组的指针

  1. 三维数组的指针 三维数组可以看作是二维数组的数组。定义三维数组的一般形式为:type arrayName[xSize][ySize][zSize];。例如:
int cube[2][3][4];

这是一个三维数组,有2个“面”,每个“面”是一个3行4列的二维数组。

可以定义指向三维数组元素的指针,和二维数组类似:

int *elementPtr = &cube[0][0][0];

也可以定义指向二维数组(“面”)的指针,即:

int (*twoDArrayPtr)[3][4] = &cube[0];

这里twoDArrayPtr是一个指针,指向一个3 * 4的二维数组。

  1. 遍历三维数组 下面是一个遍历三维数组的示例代码:
#include <stdio.h>

int main() {
    int cube[2][3][4] = {
        {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12}
        },
        {
            {13, 14, 15, 16},
            {17, 18, 19, 20},
            {21, 22, 23, 24}
        }
    };
    int (*twoDArrayPtr)[3][4] = cube;
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 4; k++) {
                printf("%d ", *(*(*(twoDArrayPtr + i)+j)+k);
            }
            printf("\n");
        }
        printf("\n");
    }
    return 0;
}

在这个代码中,通过多层循环和指针运算来遍历三维数组的所有元素。

动态分配多维数组与指针

  1. 动态分配二维数组 在C语言中,可以使用malloc函数动态分配二维数组。一种常见的方法是先分配一个指针数组,每个指针再指向一个动态分配的一维数组。例如:
#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3;
    int cols = 4;
    int **matrix = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
    }
    // 初始化数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }
    // 输出数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
    // 释放内存
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    return 0;
}

在这段代码中,首先使用malloc分配了一个包含rowsint *类型元素的数组matrix,然后对每个matrix[i]再分配一个包含colsint类型元素的一维数组。最后记得释放分配的内存,先释放每一行的一维数组,再释放matrix数组。

  1. 动态分配三维数组 动态分配三维数组类似,先分配一个指针数组,每个指针指向一个二维数组(也是动态分配的)。例如:
#include <stdio.h>
#include <stdlib.h>

int main() {
    int x = 2;
    int y = 3;
    int z = 4;
    int ***cube = (int ***)malloc(x * sizeof(int **));
    for (int i = 0; i < x; i++) {
        cube[i] = (int **)malloc(y * sizeof(int *));
        for (int j = 0; j < y; j++) {
            cube[i][j] = (int *)malloc(z * sizeof(int));
        }
    }
    // 初始化数组
    for (int i = 0; i < x; i++) {
        for (int j = 0; j < y; j++) {
            for (int k = 0; k < z; k++) {
                cube[i][j][k] = i * y * z + j * z + k;
            }
        }
    }
    // 输出数组
    for (int i = 0; i < x; i++) {
        for (int j = 0; j < y; j++) {
            for (int k = 0; k < z; k++) {
                printf("%d ", cube[i][j][k]);
            }
            printf("\n");
        }
        printf("\n");
    }
    // 释放内存
    for (int i = 0; i < x; i++) {
        for (int j = 0; j < y; j++) {
            free(cube[i][j]);
        }
        free(cube[i]);
    }
    free(cube);
    return 0;
}

这里先分配一个包含xint **类型元素的数组cube,然后对每个cube[i]分配一个包含yint *类型元素的数组,最后对每个cube[i][j]分配一个包含zint类型元素的数组。释放内存时按照相反的顺序进行。

指向多维数组指针的易错点

  1. 指针类型匹配问题 在使用指向多维数组的指针时,指针类型必须和数组类型匹配。例如,不能将一个指向一维数组的指针(int *)赋值给一个指向二维数组一行的指针(int (*)[4])。下面的代码是错误的:
int matrix[3][4];
int *ptr = matrix; // 错误,类型不匹配
  1. 内存释放问题 对于动态分配的多维数组,一定要注意正确释放内存。如前面动态分配二维和三维数组的例子,如果忘记释放某一层分配的内存,就会导致内存泄漏。特别是在复杂的程序中,多层动态分配的情况下,要仔细检查内存释放的顺序。

  2. 指针运算的理解 对指向多维数组的指针进行运算时,要清楚其移动的实际元素数量。例如,对于行指针int (*rowPtr)[4]rowPtr + 1会跳过4个int类型的元素,而不是1个。如果理解错误,可能会导致访问越界等问题。

实际应用场景

  1. 图像处理 在图像处理中,图像数据通常可以表示为二维或三维数组。例如,对于灰度图像,可以用一个二维数组表示,每个元素代表一个像素的灰度值。彩色图像可以用三维数组表示,第三维表示颜色通道(如RGB三个通道)。指向多维数组的指针可以方便地对图像数据进行遍历、处理和传递给图像处理函数。

  2. 矩阵运算 在数学计算中,矩阵运算是常见的操作。矩阵可以用二维数组表示,通过指向二维数组的指针,可以高效地实现矩阵的乘法、转置等运算。例如,矩阵乘法函数可以接收两个二维数组(矩阵)的指针作为参数,进行相应的计算。

  3. 游戏开发 在游戏开发中,地图数据、角色位置等信息可能会用多维数组表示。例如,一个二维数组可以表示游戏地图的地形,每个元素代表不同的地形类型。指向多维数组的指针可以用于快速访问和修改这些数据,以实现游戏的各种逻辑,如角色移动、碰撞检测等。

总结

指向多维数组的指针是C语言中一个强大而灵活的特性。通过正确理解和使用指向多维数组不同层面的指针,如指向元素的指针、行指针等,可以更高效地处理多维数据结构。在动态分配多维数组时,要注意内存的分配和释放,避免内存泄漏。同时,在实际应用中,指向多维数组的指针在图像处理、数学计算、游戏开发等多个领域都有着广泛的应用。在使用过程中,要特别注意指针类型匹配、指针运算的正确性以及内存管理等问题,以编写健壮、高效的C语言程序。