C,多维数组:元素是一维数组的数组?

Eva*_*oll 3 c arrays pointers multidimensional-array

C编程:现代方法,第269页第2版这本书中,这个陈述是否有意义

就像一维数组的名称可以用作指针一样,任何数组的名称都可以使用,无论它具有多少维度.但是,需要注意一些事项.考虑以下数组:

int a[NUM_ROWS][NUM_COLS];
Run Code Online (Sandbox Code Playgroud)

a不是一个指针a[0][0];代替,这是一个指针a[0].如果我们从C的角度来看这是更有意义的,C a不是二维数组而是一维数组,其元素是一维数组.当用作指针时,a有类型int (*) [NUM_COLS](指向长度为整数的数组NUM_COLS).

我很困惑,因为当我认为"其元素是一维数组的数组"时,我认为是一个锯齿状数组,但这不是这里发生的事情.这更像是一个带指针算术的宏?

这是参考类型系统以及它如何处理多维数组?任何人都能解释一下吗?

Ste*_*mit 7

是的,它是有道理的,不,它甚至没有谈论"衣衫褴褛"或"锯齿状"阵列.就在我们说的时候

int a[NUM_ROWS][NUM_COLS];
Run Code Online (Sandbox Code Playgroud)

我们正在创建的是一个数组a,它的数组是......其他数组.你可以这样想:

        +---------------------------------------+
        | +--------+--------+--------+--------+ |
a: [0]: | |        |        |        |        | |
        | +--------+--------+--------+--------+ |
        +                                       +
        | +--------+--------+--------+--------+ |
   [1]: | |        |        |        |        | |
        | +--------+--------+--------+--------+ |
        +                                       +
        | +--------+--------+--------+--------+ |
   [2]: | |        |        |        |        | |
        | +--------+--------+--------+--------+ |
        +---------------------------------------+
Run Code Online (Sandbox Code Playgroud)

(这里NUM_COLS显然是4,而且NUM_ROWS是3.)

两个(或更多)维数组100%类似于简单的单维数组 - 您只需要仔细考虑类比.如果a是一个数组,那么a在表达式中提及其值的任何提及都会导致指向数组的第一个元素的指针&a[0].因此,考虑到a我们所讨论的二维数组,其a值是&a[0]并且是指向NUM_COLS整数数组的指针.

如果多维数组下标要正常工作,它必须以这种方式工作.如果我们写a[i][j],那就被解释为(a[i])[j]. a像往常一样,变成指向数组的第一个元素的指针,但a[i]相当于*(a + i)指针运算最终被指向元素的大小缩放 - 也就是说,在引擎盖下,它更像是*(a+ i * sizeof(*a)).所以sizeof(*a)必须是sizeof(int [NUM_COLS]),或NUM_COLS * sizeof(int).这样a[i]就可以获得i第一个子阵列,然后j可以选择子阵列中的一个单元格 - int大小单元格.

最后一点说明:我曾经俗称"多维数组",但严格来说,正如许多常客所指出的那样,C没有多维数组; 它只有一维数组,而我们所认为的二维数组实际上就像我们在这里看到的那样,它的元素碰巧是其他一维数组.(如果C有真正的多维数组,下标可能看起来像a[i,j]而不是a[i][j].)


附录:尽管你提到了指针算法,并且我提到了指针算法,但重要的是要意识到定义中没有涉及指针a.指针只有在我们试图"取值"时才会出现a,或解释如何a[i]相当于*(a + i).

对于涉及指针的数据结构,我们可以对比代码描述的情况

int *a2[NUM_ROWS];
for(i = 0; i < NUM_ROWS; i++)
    a2[i] = malloc(NUM_COLS * sizeof(int));
Run Code Online (Sandbox Code Playgroud)

这给了我们一个非常不同的内存布局:

    +-----+
a2: |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
Run Code Online (Sandbox Code Playgroud)

这就是通常所谓的"粗糙"或"锯齿状"阵列,因为在这种情况下,所有行显然都不一定是相同的长度.然而,几乎神奇地,"衣衫褴褛"阵列中的单元格也可以使用a2[i][j]符号进行访问.为了充满活力,我们可以使用

int **a3 = malloc(NUM_ROWS * sizeof(int *));
for(i = 0; i < NUM_ROWS; i++)
    a3[i] = malloc(NUM_COLS * sizeof(int));
Run Code Online (Sandbox Code Playgroud)

导致这种内存布局:

    +-----+
a3: |     |
    |  *  |
    |  |  |
    +--|--+
       |
       |
       V
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
Run Code Online (Sandbox Code Playgroud)

而且a3[i][j]也在这里工作.

(当然,在真正的代码构建"动态数组"像a2a3,我们会进行检查,以确保malloc没有回来NULL.)