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

C语言多维数组与指针的关联

2024-07-204.9k 阅读

C 语言多维数组与指针的关联

多维数组基础

在 C 语言中,多维数组是数组的数组。以二维数组为例,它可以被看作是一个表格,有行和列。一般的声明形式为 type arrayName[rowSize][columnSize];,其中 type 是数组元素的数据类型,arrayName 是数组名,rowSizecolumnSize 分别是行数和列数。

例如,声明一个二维整数数组:

#include <stdio.h>

int main() {
    int twoDArray[3][4];
    int i, j;

    // 初始化数组
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            twoDArray[i][j] = i * 4 + j;
        }
    }

    // 打印数组
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            printf("%d ", twoDArray[i][j]);
        }
        printf("\n");
    }

    return 0;
}

在上述代码中,twoDArray 是一个 3 行 4 列的二维数组。通过两层循环对其进行初始化并打印。

多维数组在内存中是按行顺序存储的。也就是说,对于二维数组 a[m][n],先存储第一行的 n 个元素,接着存储第二行的 n 个元素,以此类推。

指针基础回顾

在深入探讨多维数组与指针的关联之前,先来回顾一下指针的基本概念。指针是一个变量,其值为另一个变量的地址。声明指针的一般形式为 type *pointerName;,例如:

int num = 10;
int *ptr = &num;

这里 ptr 是一个指向 int 类型变量 num 的指针,& 是取地址运算符。通过指针可以间接访问和修改所指向的变量的值,例如 *ptr = 20; 就会将 num 的值修改为 20,* 在这里是解引用运算符。

多维数组与指针的联系

  1. 二维数组与指针

    • 数组名作为指针:在 C 语言中,二维数组名可以被看作是一个指向数组首行的指针。对于二维数组 a[m][n]a 是一个指向 a[0] 的指针,而 a[0] 本身又是一个包含 n 个元素的一维数组。也就是说,a 的类型是 int (*)[n](指向包含 nint 类型元素的数组的指针)。

    下面通过代码来验证:

#include <stdio.h>

int main() {
    int twoDArray[3][4];
    int i, j;

    // 初始化数组
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            twoDArray[i][j] = i * 4 + j;
        }
    }

    int (*ptr)[4] = twoDArray; // ptr 是一个指向包含 4 个 int 类型元素的数组的指针

    // 通过指针访问数组元素
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            printf("%d ", *(*(ptr + i) + j));
        }
        printf("\n");
    }

    return 0;
}

在这段代码中,int (*ptr)[4] = twoDArray;ptr 声明为一个指向包含 4 个 int 类型元素的数组的指针,并使其指向 twoDArray 的首行。通过 *(*(ptr + i) + j) 这种双重解引用的方式来访问二维数组的元素。ptr + i 指向第 i 行,*(ptr + i) 则得到第 i 行的首地址(即一个指向该行第一个元素的指针),再通过 *(ptr + i) + j 指向该行的第 j 个元素,最后通过外层的 * 解引用得到该元素的值。

- **行指针**:`a[i]`(其中 `i` 是行索引)可以被看作是指向第 `i` 行第一个元素的指针。它的类型是 `int *`。例如,`a[0]` 指向 `a[0][0]`,`a[1]` 指向 `a[1][0]`。可以使用 `*(a[i] + j)` 来访问 `a[i][j]` 元素,这与 `*(*(a + i) + j)` 是等效的。

以下代码展示了使用行指针访问二维数组元素:
#include <stdio.h>

int main() {
    int twoDArray[3][4];
    int i, j;

    // 初始化数组
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            twoDArray[i][j] = i * 4 + j;
        }
    }

    int *rowPtr;

    for (i = 0; i < 3; i++) {
        rowPtr = twoDArray[i]; // rowPtr 指向第 i 行的首元素
        for (j = 0; j < 4; j++) {
            printf("%d ", *(rowPtr + j));
        }
        printf("\n");
    }

    return 0;
}

在这段代码中,rowPtr = twoDArray[i];rowPtr 指向二维数组的第 i 行首元素,然后通过 *(rowPtr + j) 来访问该行的第 j 个元素。

  1. 三维数组与指针

    • 对于三维数组 a[x][y][z],数组名 a 是一个指向 a[0] 的指针,而 a[0] 是一个二维数组。所以 a 的类型是 int (*)[y][z](指向包含 yz 列的二维数组的指针)。

    下面的代码展示了三维数组与指针的关系:

#include <stdio.h>

int main() {
    int threeDArray[2][3][4];
    int i, j, k;

    // 初始化三维数组
    for (i = 0; i < 2; i++) {
        for (j = 0; j < 3; j++) {
            for (k = 0; k < 4; k++) {
                threeDArray[i][j][k] = i * 12 + j * 4 + k;
            }
        }
    }

    int (*ptr)[3][4] = threeDArray; // ptr 指向三维数组的首二维数组

    // 通过指针访问三维数组元素
    for (i = 0; i < 2; i++) {
        for (j = 0; j < 3; j++) {
            for (k = 0; k < 4; k++) {
                printf("%d ", *(*(*(ptr + i) + j) + k));
            }
            printf("\n");
        }
        printf("\n");
    }

    return 0;
}

在这段代码中,int (*ptr)[3][4] = threeDArray;ptr 声明为一个指向包含 3 行 4 列的二维数组的指针,并使其指向 threeDArray 的首二维数组。通过三重解引用 *(*(*(ptr + i) + j) + k) 来访问三维数组的元素。ptr + i 指向第 i 个二维数组,* (ptr + i) 得到该二维数组的首地址(即一个指向该二维数组首行的指针),*(*(ptr + i) + j) 指向该二维数组第 j 行的首元素,最后 *(*(*(ptr + i) + j) + k) 得到该元素的值。

- 类似二维数组的行指针概念,对于三维数组,`a[i]` 是一个指向二维数组 `a[i][0]` 的指针,其类型为 `int (*)[z]`。`a[i][j]` 则是指向一维数组 `a[i][j][0]` 的指针,类型为 `int *`。

指针数组与数组指针

  1. 指针数组 指针数组是一个数组,其元素都是指针。声明形式为 type *arrayName[size];,例如 int *ptrArray[5]; 声明了一个包含 5 个 int 类型指针的数组。

指针数组常被用于处理多个字符串。因为字符串在 C 语言中是以字符数组形式存储,而字符数组名可看作是指向首字符的指针。下面的代码展示了如何使用指针数组来处理多个字符串:

#include <stdio.h>

int main() {
    char *strArray[] = {"apple", "banana", "cherry"};
    int i;

    for (i = 0; i < 3; i++) {
        printf("%s\n", strArray[i]);
    }

    return 0;
}

在这段代码中,strArray 是一个指针数组,每个元素都是一个指向字符串首字符的指针。通过遍历指针数组,可以方便地访问和操作多个字符串。

  1. 数组指针 数组指针是一个指针,它指向一个数组。声明形式为 type (*pointerName)[size];,例如 int (*ptr)[10]; 声明了一个指向包含 10 个 int 类型元素的数组的指针。

数组指针在处理多维数组时非常有用,正如前面在二维数组和三维数组与指针的关联中所展示的。它能够更清晰地表达对多维数组的行或整个二维数组的指向关系。

动态分配多维数组与指针

  1. 动态分配二维数组 在实际编程中,有时需要在运行时确定二维数组的大小,这时就需要动态内存分配。可以使用 malloc 函数来实现。

方法一:使用指针数组来模拟二维数组。

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

int main() {
    int rows = 3, cols = 4;
    int **twoDArray = (int **)malloc(rows * sizeof(int *));

    if (twoDArray == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    for (int i = 0; i < rows; i++) {
        twoDArray[i] = (int *)malloc(cols * sizeof(int));
        if (twoDArray[i] == NULL) {
            printf("Memory allocation failed\n");
            for (int j = 0; j < i; j++) {
                free(twoDArray[j]);
            }
            free(twoDArray);
            return 1;
        }
    }

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

    // 打印数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", twoDArray[i][j]);
        }
        printf("\n");
    }

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

    return 0;
}

在这段代码中,首先分配一个包含 rowsint 类型指针的数组 twoDArray。然后,为每个指针分配一个包含 colsint 类型元素的数组,从而模拟出一个二维数组。最后记得释放分配的内存,以避免内存泄漏。

方法二:使用连续内存分配来实现二维数组。

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

int main() {
    int rows = 3, cols = 4;
    int *twoDArray = (int *)malloc(rows * cols * sizeof(int));

    if (twoDArray == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

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

    // 打印数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", twoDArray[i * cols + j]);
        }
        printf("\n");
    }

    // 释放内存
    free(twoDArray);

    return 0;
}

这种方法直接分配一块连续的内存来存储二维数组的所有元素。通过 i * cols + j 的计算来访问特定位置的元素。

  1. 动态分配三维数组 动态分配三维数组也有多种方法。一种常见的方法是先分配一个指针数组,每个指针再指向一个二维数组(其本身也是动态分配的)。
#include <stdio.h>
#include <stdlib.h>

int main() {
    int x = 2, y = 3, z = 4;
    int ***threeDArray = (int ***)malloc(x * sizeof(int **));

    if (threeDArray == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    for (int i = 0; i < x; i++) {
        threeDArray[i] = (int **)malloc(y * sizeof(int *));
        if (threeDArray[i] == NULL) {
            printf("Memory allocation failed\n");
            for (int j = 0; j < i; j++) {
                free(threeDArray[j]);
            }
            free(threeDArray);
            return 1;
        }

        for (int j = 0; j < y; j++) {
            threeDArray[i][j] = (int *)malloc(z * sizeof(int));
            if (threeDArray[i][j] == NULL) {
                printf("Memory allocation failed\n");
                for (int k = 0; k < j; k++) {
                    free(threeDArray[i][k]);
                }
                free(threeDArray[i]);
                for (int l = 0; l < i; l++) {
                    free(threeDArray[l]);
                }
                free(threeDArray);
                return 1;
            }
        }
    }

    // 初始化三维数组
    for (int i = 0; i < x; i++) {
        for (int j = 0; j < y; j++) {
            for (int k = 0; k < z; k++) {
                threeDArray[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 ", threeDArray[i][j][k]);
            }
            printf("\n");
        }
        printf("\n");
    }

    // 释放内存
    for (int i = 0; i < x; i++) {
        for (int j = 0; j < y; j++) {
            free(threeDArray[i][j]);
        }
        free(threeDArray[i]);
    }
    free(threeDArray);

    return 0;
}

在这段代码中,通过多层循环和 malloc 函数逐步分配三维数组所需的内存。首先分配一个包含 x 个指针的数组,每个指针再指向一个包含 y 个指针的数组,最后每个指针指向一个包含 zint 类型元素的数组。同样,在使用完后要按顺序释放内存,以防止内存泄漏。

多维数组与指针在函数参数传递中的应用

  1. 传递二维数组 当把二维数组作为函数参数传递时,可以有多种形式。

形式一:直接传递二维数组。

#include <stdio.h>

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

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

    printTwoDArray(twoDArray, 3);

    return 0;
}

在这个例子中,函数 printTwoDArray 的参数 arr 声明为 int arr[][4],其中列数必须明确指定,因为编译器需要知道每行的元素个数以便正确计算内存地址。行数可以作为另一个参数传递。

形式二:使用数组指针作为参数。

#include <stdio.h>

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

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

    printTwoDArray(twoDArray, 3);

    return 0;
}

这里函数 printTwoDArray 的参数 arr 是一个数组指针 int (*arr)[4],它同样指向一个包含 4 个 int 类型元素的数组。通过双重解引用 *(*(arr + i) + j) 来访问二维数组的元素。

  1. 传递三维数组 传递三维数组作为函数参数与二维数组类似,但更复杂一些。

形式一:直接传递三维数组。

#include <stdio.h>

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

int main() {
    int threeDArray[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}
        }
    };

    printThreeDArray(threeDArray, 2);

    return 0;
}

在这个例子中,函数 printThreeDArray 的参数 arr 声明为 int arr[][3][4],其中第二维和第三维的大小必须明确指定,第一维大小可以通过另一个参数传递。

形式二:使用数组指针作为参数。

#include <stdio.h>

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

int main() {
    int threeDArray[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}
        }
    };

    printThreeDArray(threeDArray, 2);

    return 0;
}

这里函数 printThreeDArray 的参数 arr 是一个数组指针 int (*arr)[3][4],通过三重解引用 *(*(*(arr + i) + j) + k) 来访问三维数组的元素。

多维数组与指针的常见错误及注意事项

  1. 数组越界 在使用多维数组和指针访问元素时,很容易发生数组越界错误。例如,对于二维数组 a[m][n],如果访问 a[i][j]i >= mj >= n,就会导致未定义行为。在动态分配多维数组时,也要注意正确计算内存大小,避免访问已释放或未分配的内存。

  2. 指针类型不匹配 在将多维数组名作为指针传递或声明数组指针时,要确保指针类型匹配。例如,不能将二维数组 int a[3][4] 的数组名 a 赋值给类型为 int * 的指针,因为 a 的实际类型是 int (*)[4]

  3. 内存泄漏 在动态分配多维数组时,如果忘记释放内存,就会导致内存泄漏。特别是在多层动态分配(如动态分配三维数组)时,要按正确的顺序释放内存,避免部分内存无法释放。

  4. 理解数组存储方式 要深入理解多维数组在内存中的存储方式(按行存储),这对于正确使用指针访问数组元素非常重要。例如,在使用连续内存分配实现二维数组时,需要根据行和列的索引正确计算元素在内存中的位置。

通过对 C 语言多维数组与指针关联的深入探讨,包括多维数组基础、指针基础回顾、两者的联系、指针数组与数组指针、动态分配以及在函数参数传递中的应用,并注意常见错误和事项,开发者能够更灵活、高效地使用多维数组和指针进行编程,充分发挥 C 语言的强大功能。无论是处理矩阵运算、图像处理还是其他需要多维数据结构的场景,掌握这些知识都将为开发工作带来极大的便利。