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

这个陈述是否有意义,请参阅“ C编程:现代方法”,第269页第2版

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

int a[NUM_ROWS][NUM_COLS]; 

a 不是指向a[0][0];的指针a[0][0]; 相反,它是指向a[0]的指针。 如果我们从C的角度来看它会更有意义,它不是将二维数组视为二维数组,而是将元素视为一维数组的一维数组。 当用作指针时, a具有类型int (*) [NUM_COLS] (指向长度为NUM_COLS的整数数组的NUM_COLS )。

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

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

是的,它是有道理的,不,它甚至没有谈论“衣衫褴褛”或“锯齿状”arrays。 就在我们说的时候

 int a[NUM_ROWS][NUM_COLS]; 

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

  +---------------------------------------+ | +--------+--------+--------+--------+ | a: [0]: | | | | | | | | +--------+--------+--------+--------+ | + + | +--------+--------+--------+--------+ | [1]: | | | | | | | | +--------+--------+--------+--------+ | + + | +--------+--------+--------+--------+ | [2]: | | | | | | | | +--------+--------+--------+--------+ | +---------------------------------------+ 

(此处NUM_COLS显然为4, NUM_ROWS为3.)

两个(或更多)维数组100%类似于简单的单维数组 – 您只需要仔细考虑类比。 如果a是一个数组,那么在需要其值的表达式中提及a会导致指向数组的第一个元素&a[0]的指针。 因此,给定我们所讨论的二维数组, 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的子arrays,然后j可以选择子arrays中的一个单元格 – int单元格。

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


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

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

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

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

  +-----+ a2: | | +--------+--------+--------+--------+ | *------->| | | | | | | +--------+--------+--------+--------+ +-----+ | | +--------+--------+--------+--------+ | *------->| | | | | | | +--------+--------+--------+--------+ +-----+ | | +--------+--------+--------+--------+ | *------->| | | | | | | +--------+--------+--------+--------+ +-----+ 

这就是通常所说的“粗糙”或“锯齿状”arrays,因为在这种情况下,所有行显然不必是相同的长度。 然而,几乎神奇地,“衣衫褴褛”arrays中的单元也可以使用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 。)

另一种看待它的方式……

对于任何类型T ,我们创建一个数组作为

 T arr[N]; 

其中T可以是intchardoublestruct foo ,等等,并读作“ T元素的N元素”。 它也可以是另一种数组类型 。 因此,假设TintM元素数组 ,而不仅仅是int ,我们将其写为

 int arr[N][M]; 

这读作“ arrint元素的M元素数组的N元素数组”。 这不是一个锯齿状的数组 – 所有“行”都是相同的大小。 但它也不完全是一个二维数组 – 它是一个数组数组。 表达式arr[i]具有数组类型( int [M] )。

这个视图也帮助我们找出指向数组类型的指针。 除非它是sizeof或一元&运算符的操作数,或者是用于初始化声明中的字符数组的字符串文字,否则将转换类型为“N元素数组T ”( T [N] )的表达式 (“衰变”)到“指向T指针”( T * )类型的表达式。 同样,如果用数组类型int [M]替换T ,那么你有一个类型为“N元素数组的M元素数组int ”( int [N][M] )的表达式,它“衰变”为输入“指向M的元素数组的指针”( int (*)[M] )。