张银峰的编程课堂

多维数组

数组由类型相同的元素构成,将离散的数据聚合起来并建立了迭代基础;二维数组需要表达两个维度,它允许我们以表格的形式组织数据,如:

int tab[3][4];  // 一个存储int型元素,3行4列名为tab的二维数组

这里用二维视图展示数组,便于我们直观地想象具有两个索引的数组。

glimix.com

但事实上,多维数组仍就是以顺序而非表格存储的。表格只是我们给予的便于观察数据的视角。

tab[0][0] tab[0][1] tab[0][2] tab[0][3] tab[1][0] tab[1][1] ... tab[2][2] tab[2][3]

从这个角度讲,二维数组实际上是一种特殊的一维数组,它的每个元素是一个一维数组。数组tab可以看作3个由4个整型组成的数组的数组。理解这个声明时可以先查看中间的部分。

int tab[3][4];

这部分说明tab是一个包含3个元素的数组,至于每个元素的情况,需要查看声明的其余部分。

int tab[3][4];

这说明每个元素的类型是int[4];也就是说,tab具有3个元素,每个元素都是包含4个int数值的数组。

对二维数组的初始化是建立在对一维数组的初始化之上的。针对tab[3][4]的初始化,我们可以假设有3个一维数组,定义如下:

int A[4] = {11, 12, 13, 14};
int B[4] = {21, 22, 23, 24};
int C[4] = {31, 32, 33, 34};

数组tab的元素由则是由这个三个数组构成,使用伪代码表达如下:

int tab[3][4] = { A, B, C };

接入来使用代入法,替换各个元素,则tab的初始化可以表达式,

int tab[3][4] =          // 从一维数组角度讲
{                        // 初始化以'{'开始
     {11, 12, 13, 14},   // 有tab[0]、tab[1]、tab[2]三个元素
     {21, 22, 23, 24},   // 元素之间以逗号分隔
     {31, 32, 33, 34}    // 每个元素包含4个数据
};                       // 以'}'完成初始化

对于每个元素的初始化,如果初始化数值的个数少于所需数量,则这些未指定值默认初始化为0。

#include <stdio.h>

int main()
{
    int tab[3][4] =
    {
        {11},           // 相当于:{11, 0, 0, 0}
        {21, 22},       // 等价于:{21, 22, 0, 0,}
        {31, 32, 33}    // 等同于:{31, 32, 33, 0}
    };

    for (int r = 0; r < 3; r++)
    {
        for (int c = 0; c < 4; c++)
            printf("%2d ", tab[r][c]);
        printf("\n");
    }

    return 0;
}

glimix.com

初始化时可以省略内部的花括号,只需保证元素数量的正确性即可。当数量不足时,就按照顺序逐行赋值,前面的元素依次被赋值,后面的元素被初始化为0。

#include <stdio.h>

int main()
{
    int a1[2][3] = {{10, 11, 12}, {20, 21, 22}};
    int a2[2][3] = {10, 11, 12, 20, 21, 22};    // 等价于a1的初始化
    int a3[2][3] = {10, 11, 12, 20};            // 逐行赋值,不够的元素以0初始化。

    int tab[3][4] =
    {
        {11},
        {21, 22},
        {31, 32, 33}
    };

    for (int r = 0; r < 2; r++)
    {
        for (int c = 0; c < 3; c++)
            printf("%2d ", a2[r][c]);
        printf("\n");
    }

    printf("\n");

    for (int r = 0; r < 2; r++)
    {
        for (int c = 0; c < 3; c++)
            printf("%2d ", a3[r][c]);
        printf("\n");
    }

    return 0;
}

glimix.com

初始化时也可以省略数组的行数维度信息,但列维度的信息必须指定。这时行数将等于数据个数除以列数。

float v[][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // 5行2列

与函数交互

如果将二维数组作为参数传递到函数,那么在函数的参数声明中必须指明数组的列数,而数组的行数则没有任何关系。如果将数组tab作为参数传递给函数f,理论上f的原型应该编写为:

f(int tab[3][4])

回想关于一维数组与函数的交互,数组传递到函数时会丢失掉维度信息。多维数组本质也是一维数组,所以与一维数组关联的维度信息同样会丢失,继而退化为指针形式,但后续的维度(如列数)必须指定,因此f的声明也可写成:

f(int tab[][4])     // ok
f(int tab[][])      // error

这种表达下,函数的实现通常会硬编码行计数。如:

void print_tab(int tab[][4])
{
    for (int r = 0; r < 3; r++)
    {
        for (int c = 0; c < 4; c++)
            printf("%2d ", tab[r][c]);
        printf("\n");
    }
}

你可能希望编写一个通用的二维数组访问函数,这时函数需要数组两个维度信息,但是这样的表达并不优雅,而且不能适应其它数组,如:

int arr1[2][3];
int arr2[3][4];
void print_2darray(int arr[][3], int rows, int cols);

由于列维度不能丢失,print_2darray的第一个参数导致数组arr2并不是它所能接收的类型。这也是下面程序中,对于a2的定义,我们不能将其退化为一维数组的原因:函数接收与输入的参数类型不匹配;即不能将a2定义为a[3]={1, 2, 3}

#include <stdio.h>

void print_2darray(int arr[][3]/*列数固定行数可变的数组*/, int rows)
{
    for (int r = 0; r < rows; r++)
    {
        for (int c = 0; c < 3; c++)
            printf("%2d\t", arr[r][c]);
        printf("\n");
    }
}

int main()
{
    int a1[][3] = {10, 11, 12, 20, 21, 22};
    int a2[][3] = {1, 2, 3};

    print_2darray(a1, 2);
    print_2darray(a2, 1);

    return 0;
}

glimix.com

更高维数组

2维或2维以上的数组可以称之为多维数组,维数越多理解就越复杂。对于多维数组的描述,使用组的概念会更加清晰,如3维数组arr[2][3][4],我们可以读作:数组arr有2个大组,每个大组包含3个小组,每个小组包含4个元素。因为4后面没有维度了,所以它是元素数了。多维数组中元素的个数就是各个维度相乘的结果,把步骤写出来会更清晰一些。

// #1 arr是一个数组
arr[2][3][4] =
{
};

arr[2][3][4] =
{
    // #2 它包含两个大组,组与组之间使用逗号分隔。
    {
    },

    {
    }
};

arr[2][3][4] =
{
    {
        //#3 每个大组包含3个小组
        {},
        {},
        {}
    },

    {
        //#3 每个大组包含3个小组
        {},
        {},
        {}
    }
};

arr[2][3][4] =
{
    {
        { 1, 2, 3, 4 },         //#4 每个小组包含4个元素。
        { 5, 6, 7, 8 },
        { 9, 0, 1, 2 }
    },

    {
        { 'a', 'b', 'c', 'd' }, //#4 每个小组包含4个元素。
        { 'e', 'f', 'g', 'h' },
        { 'i', 'j', 'k', 'l' }
    }
};

掌握了这个概念之后,那这个4维数组就没有任何难度了。

#include <stdio.h>

int main()
{
    int array_c[3][2][2][3] =
    {
        //==================================
        {
            {
                { 1, 2, 3 },
                { 3, 4, 5 }
            },
            {
                { 5, 6, 7 },
                { 7, 8, 9 }
            }
        },

        //==================================
        {
            {
                { 11, 12, 13 },
                { 13, 14, 15 }
            },
            {
                { 15, 16, 17 },
                { 17, 18, 19 }
            }
        },

        //==================================
        {
            {
                { 21, 22, 23 },
                { 23, 24, 25 }
            },
            {
                { 25, 26, 27 },
                { 27, 28, 29 }
            }
        }
    };

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

glimix.com