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

C语言指向多维数组的指针运用

2021-12-024.6k 阅读

C 语言指向多维数组的指针运用

多维数组基础回顾

在深入探讨指向多维数组的指针之前,我们先来回顾一下多维数组的基本概念。在 C 语言中,多维数组实际上是数组的数组。以二维数组为例,它可以被看作是一个由多个一维数组组成的数组。

例如,定义一个二维数组 int arr[3][4];,这里 arr 是一个包含 3 个元素的数组,每个元素又是一个包含 4 个 int 类型数据的一维数组。

从内存角度来看,二维数组在内存中是按行顺序连续存储的。也就是说,先存储第一行的所有元素,接着存储第二行,以此类推。这一点对于理解指向多维数组的指针至关重要。

指向一维数组的指针

在理解指向多维数组的指针之前,先来看指向一维数组的指针。对于一维数组 int a[5];,我们可以定义一个指针 int *p = a;,这里 p 指向了数组 a 的首元素。

通过指针 p,我们可以像使用数组名一样来访问数组元素。例如,p[0] 等价于 a[0]p[1] 等价于 a[1] 等等。指针的算术运算也同样适用,p + 1 会指向下一个元素的地址。

代码示例:

#include <stdio.h>

int main() {
    int a[5] = {1, 2, 3, 4, 5};
    int *p = a;

    for (int i = 0; i < 5; i++) {
        printf("a[%d] = %d, p[%d] = %d\n", i, a[i], i, p[i]);
    }

    return 0;
}

在上述代码中,我们定义了一个一维数组 a 和一个指向它的指针 p。通过循环,我们分别使用数组名和指针来访问数组元素并输出,结果表明二者在访问元素上是等效的。

指向二维数组的指针

二维数组指针的定义

对于二维数组 int arr[3][4];,如果要定义一个指向它的指针,不能简单地使用 int *p = arr;,因为 arr 不是一个普通的 int 类型指针。arr 实际上是一个指向包含 4 个 int 类型元素的一维数组的指针。

正确的定义方式是 int (*p)[4] = arr;,这里 p 是一个指针,它指向一个包含 4 个 int 类型元素的一维数组。(*p) 两侧的括号是必需的,否则 int *p[4]; 定义的将是一个包含 4 个 int 类型指针的数组。

通过指针访问二维数组元素

有了指向二维数组的指针 p 后,如何通过它来访问数组元素呢?我们知道,二维数组按行存储,p 指向第一行。p + 1 会指向下一行,也就是第二行的起始地址。

要访问具体的元素,比如 arr[i][j],通过指针 p 可以表示为 *(*(p + i) + j)。这里 p + i 指向第 i 行,*(p + i) 等价于第 i 行的首地址(即一个指向该行第一个元素的指针),*(p + i) + j 指向第 i 行第 j 列元素的地址,再通过 * 解引用就得到了该元素的值。

代码示例:

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*p)[4] = arr;

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("arr[%d][%d] = %d, *(*(p + %d) + %d) = %d\n", i, j, arr[i][j], i, j, *(*(p + i) + j));
        }
    }

    return 0;
}

在上述代码中,我们定义了一个二维数组 arr 和一个指向它的指针 p。通过两层循环,我们分别使用数组下标的方式和指针的方式来访问二维数组的元素并输出,验证了通过指针访问二维数组元素的正确性。

指针运算与二维数组

对指向二维数组的指针进行指针运算时,需要注意其特殊性。因为 p 指向的是包含 4 个 int 类型元素的一维数组,所以 p + 1 移动的字节数是 4 * sizeof(int)

例如,如果 sizeof(int) 为 4 字节,p 的值为 0x1000,那么 p + 1 的值将是 0x10100x1000 + 4 * 4)。这是因为指针运算时,移动的单位是其所指向的数据类型的大小。在这种情况下,p 指向的是一个包含 4 个 int 类型元素的数组,所以移动的字节数是 4 个 int 类型元素所占的字节数。

指向三维数组的指针

三维数组指针的定义

三维数组可以看作是由多个二维数组组成的数组。例如,定义一个三维数组 int arr[2][3][4];,它包含 2 个二维数组,每个二维数组又包含 3 行 4 列元素。

要定义一个指向三维数组的指针,其形式为 int (*p)[3][4] = arr;。这里 p 是一个指针,它指向一个包含 3 行 4 列元素的二维数组。

通过指针访问三维数组元素

对于三维数组 arr[i][j][k],通过指针 p 访问的表达式为 *(*(*(p + i) + j) + k)。其原理与二维数组类似,p + i 指向第 i 个二维数组,*(p + i) 得到该二维数组的首地址,*(p + i) + j 指向该二维数组中的第 j 行,*(*(p + i) + j) 得到该行的首地址,*(*(p + i) + j) + k 指向该行第 k 列元素的地址,最后通过 * 解引用得到元素的值。

代码示例:

#include <stdio.h>

int main() {
    int arr[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 (*p)[3][4] = arr;

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 4; k++) {
                printf("arr[%d][%d][%d] = %d, *(*(*(p + %d) + %d) + %d) = %d\n", i, j, k, arr[i][j][k], i, j, k, *(*(*(p + i) + j) + k));
            }
        }
    }

    return 0;
}

在上述代码中,我们定义了一个三维数组 arr 和一个指向它的指针 p。通过三层循环,我们分别使用数组下标的方式和指针的方式来访问三维数组的元素并输出,验证了通过指针访问三维数组元素的正确性。

指针运算与三维数组

与二维数组类似,对指向三维数组的指针 p 进行运算时,p + 1 移动的字节数是 3 * 4 * sizeof(int)。因为 p 指向的是一个包含 3 行 4 列元素的二维数组,所以指针移动的单位是这个二维数组所占的字节数。

例如,如果 sizeof(int) 为 4 字节,p 的值为 0x2000,那么 p + 1 的值将是 0x20300x2000 + 3 * 4 * 4)。

多维数组指针作为函数参数

二维数组指针作为函数参数

在函数中使用二维数组指针作为参数,可以方便地对二维数组进行操作。例如,定义一个函数来计算二维数组所有元素的和:

#include <stdio.h>

int sum2D(int (*arr)[4], int rows) {
    int sum = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            sum += *(*(arr + i) + j);
        }
    }
    return sum;
}

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

    int result = sum2D(arr, 3);
    printf("Sum of 2D array elements: %d\n", result);

    return 0;
}

在上述代码中,sum2D 函数接受一个指向包含 4 个 int 类型元素的一维数组的指针 arr 和行数 rows 作为参数。通过指针访问二维数组的元素并计算它们的和。在 main 函数中,我们定义了一个二维数组 arr 并调用 sum2D 函数来计算其元素的和。

三维数组指针作为函数参数

同样,对于三维数组,也可以将指向它的指针作为函数参数。例如,定义一个函数来计算三维数组所有元素的和:

#include <stdio.h>

int sum3D(int (*arr)[3][4], int slices) {
    int sum = 0;
    for (int i = 0; i < slices; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 4; k++) {
                sum += *(*(*(arr + i) + j) + k);
            }
        }
    }
    return sum;
}

int main() {
    int arr[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 result = sum3D(arr, 2);
    printf("Sum of 3D array elements: %d\n", result);

    return 0;
}

在上述代码中,sum3D 函数接受一个指向包含 3 行 4 列元素的二维数组的指针 arr 和切片数 slices 作为参数。通过指针访问三维数组的元素并计算它们的和。在 main 函数中,我们定义了一个三维数组 arr 并调用 sum3D 函数来计算其元素的和。

指针数组与指向多维数组的指针的区别

指针数组是一个数组,其每个元素都是一个指针。例如,int *arr[5]; 定义了一个包含 5 个 int 类型指针的数组。

而指向多维数组的指针是一个指针,它指向一个多维数组。例如,int (*p)[4]; 定义了一个指向包含 4 个 int 类型元素的一维数组的指针。

在内存布局上,指针数组的每个指针元素可以指向不同的内存位置,而指向多维数组的指针指向的是一块连续的内存区域,该区域存储着多维数组的所有元素。

代码示例来展示它们的区别:

#include <stdio.h>

int main() {
    int a[5] = {1, 2, 3, 4, 5};
    int b[5] = {6, 7, 8, 9, 10};
    int *ptrArr[2] = {a, b};

    int multiArr[2][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10}
    };
    int (*p)[5] = multiArr;

    printf("Pointer array example:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 5; j++) {
            printf("ptrArr[%d][%d] = %d ", i, j, ptrArr[i][j]);
        }
        printf("\n");
    }

    printf("\nPointer to multi - dimensional array example:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 5; j++) {
            printf("*(*(p + %d) + %d) = %d ", i, j, *(*(p + i) + j));
        }
        printf("\n");
    }

    return 0;
}

在上述代码中,我们定义了一个指针数组 ptrArr,它的两个元素分别指向不同的一维数组 ab。同时,我们定义了一个二维数组 multiArr 和一个指向它的指针 p。通过循环输出,展示了指针数组和指向多维数组的指针在访问元素方式和内存布局上的不同。

动态分配多维数组与指针

动态分配二维数组

在实际编程中,有时需要动态分配二维数组。可以通过指针和 malloc 函数来实现。例如:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3;
    int cols = 4;

    int **arr = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        arr[i] = (int *)malloc(cols * sizeof(int));
    }

    // 初始化二维数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            arr[i][j] = i * cols + j;
        }
    }

    // 输出二维数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }

    // 释放内存
    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);

    return 0;
}

在上述代码中,我们首先通过 malloc 分配了一个包含 rowsint 类型指针的数组 arr。然后,为每个指针分配了一个包含 colsint 类型元素的一维数组,从而实现了动态分配二维数组。在使用完后,我们通过 free 函数释放了分配的内存。

动态分配三维数组

动态分配三维数组相对复杂一些,但原理类似。例如:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int slices = 2;
    int rows = 3;
    int cols = 4;

    int ***arr = (int ***)malloc(slices * sizeof(int **));
    for (int i = 0; i < slices; i++) {
        arr[i] = (int **)malloc(rows * sizeof(int *));
        for (int j = 0; j < rows; j++) {
            arr[i][j] = (int *)malloc(cols * sizeof(int));
        }
    }

    // 初始化三维数组
    for (int i = 0; i < slices; i++) {
        for (int j = 0; j < rows; j++) {
            for (int k = 0; k < cols; k++) {
                arr[i][j][k] = i * rows * cols + j * cols + k;
            }
        }
    }

    // 输出三维数组
    for (int i = 0; i < slices; i++) {
        printf("Slice %d:\n", i);
        for (int j = 0; j < rows; j++) {
            for (int k = 0; k < cols; k++) {
                printf("%d ", arr[i][j][k]);
            }
            printf("\n");
        }
    }

    // 释放内存
    for (int i = 0; i < slices; i++) {
        for (int j = 0; j < rows; j++) {
            free(arr[i][j]);
        }
        free(arr[i]);
    }
    free(arr);

    return 0;
}

在上述代码中,我们通过多层 malloc 来动态分配三维数组。首先分配一个包含 slicesint 类型二级指针的数组,然后为每个二级指针分配包含 rowsint 类型指针的数组,最后为每个一级指针分配包含 colsint 类型元素的数组。使用完后,通过多层 free 释放内存。

通过以上对 C 语言指向多维数组的指针运用的详细介绍,包括多维数组指针的定义、通过指针访问数组元素、指针运算、作为函数参数、与指针数组的区别以及动态分配多维数组等方面,相信读者对这一复杂但强大的编程概念有了更深入的理解。在实际编程中,合理运用指向多维数组的指针可以提高程序的灵活性和效率。