C语言,多维数组:元素为一维数组的数组?

3

这个语句是否有意义,来自于C程序设计现代方法 第二版269页的书中。

Just as the name of a one-dimensional array can be used as a pointer, so can the name of any array, regardless of how many dimensions it has. Some care is required, though. Consider the following array:

int a[NUM_ROWS][NUM_COLS];

a is not a pointer to a[0][0]; instead, it's a pointer to a[0]. This makes more sense if we look at it from the standpoint of C, which regards a not as a two-dimensional array but as a one-dimensional array whose elements are one-dimensional arrays. When used as a pointer, a has type int (*) [NUM_COLS] (pointer to an integer array of length NUM_COLS).

我感到困惑,因为当我想到“元素是一维数组的数组”时,我想到的是jagged-array,但这里情况并非如此。这更像是带有指针算术运算的宏定义?
这是否涉及类型系统以及它如何处理多维数组?有人能解释一下吗?

要理解这个引用,您需要对声明 int (*ptr)[7]; 有透彻的理解,它创建了一个指向数组-7-int的指针。不要与 int *ptr[7]; 混淆,它创建了一个包含7个指向int的指针的数组。这种区别应该在本书早期就进行详细讨论。 - user3386109
2个回答

7

是的,这很有道理,但并不是在谈论“不规则”或“锯齿形”数组。实际上,当我们说到

int a[NUM_ROWS][NUM_COLS];

我们正在创建一个数组a,而它所包含的是其他数组。您可以将其想象为:
        +---------------------------------------+
        | +--------+--------+--------+--------+ |
a: [0]: | |        |        |        |        | |
        | +--------+--------+--------+--------+ |
        +                                       +
        | +--------+--------+--------+--------+ |
   [1]: | |        |        |        |        | |
        | +--------+--------+--------+--------+ |
        +                                       +
        | +--------+--------+--------+--------+ |
   [2]: | |        |        |        |        | |
        | +--------+--------+--------+--------+ |
        +---------------------------------------+

(这里显然 NUM_COLS 是 4,NUM_ROWS 是 3。)
二维(或更高维)数组与简单的一维数组100%类似——您只需要仔细考虑类比。如果 a 是一个数组,那么在需要其值的表达式中提到 a 会导致指向数组第一个元素的指针 &a[0]。因此,对于我们讨论的二维数组 aa 的值是 &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));

这给我们带来了非常不同的内存布局:
    +-----+
a2: |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+

这通常被称为“不规则”或“锯齿状”数组,因为在这种情况下,并不需要所有行的长度相同。然而,几乎神奇的是,“不规则”数组中的单元格也可以使用a2 [i] [j]表示法进行访问。如果要实现完全的动态性,我们可以使用

int **a3 = malloc(NUM_ROWS * sizeof(int *));
for(i = 0; i < NUM_ROWS; i++)
    a3[i] = malloc(NUM_COLS * sizeof(int));

导致了这种内存布局:
    +-----+
a3: |     |
    |  *  |
    |  |  |
    +--|--+
       |
       |
       V
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+
    |     |     +--------+--------+--------+--------+
    |  *------->|        |        |        |        |
    |     |     +--------+--------+--------+--------+
    +-----+

a3[i][j]也能在这里使用。

(当然,在实际构建像a2a3这样的"动态数组"的真正代码中,我们需要检查确保malloc没有返回NULL。)


3

换个角度看...

对于任意类型 T,我们创建一个数组:

T arr[N];

其中T可以是intchardoublestruct foo等任何类型,并且读作“由T组成的N个元素的数组”。它也可以是另一个数组类型。因此,假设T是由int组成的M元素数组,那么我们可以将其写为:

int arr[N][M];

这段文字的意思是:“arr 是一个 N 个 M 元素数组的数组,元素类型为 int。这不是一个嵌套数组 - 所有的“行”都是相同大小的。但它也不完全是一个二维数组 - 它是一个数组的数组。表达式 arr[i] 有一个数组类型 (int [M])。”
这种观点帮助我们理解指向数组类型的指针。除非它是 sizeof 或一元 & 运算符的操作数,或者是用于声明中初始化字符数组的字符串字面值,否则类型为“N 元素数组的 T”(T [N]) 的表达式将被转换(“衰减”)为类型为“指向 T 的指针”(T *)的表达式。同样,如果您将 T 替换为数组类型 int [M],那么您就有了一个类型为“N 个 M 元素数组的数组的数组”,即 int [N][M],它会“衰减”为类型为“指向 M 元素数组的指针”的类型(int (*)[M])。

我们可以采用最简单的方法,将指针传递给 arr (int (**)[M])。这是一个很好的解释。 - David C. Rankin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接