1D数组衰减到指针,但2D数组​​不这样做,为什么?

有人可以向我解释为什么技术上2D数组实际上不会衰减到int**而当你有一个单维数组时,你会得到指针衰减到int* (如果你有int数组)。

我理解指针衰减是什么,但我不明白为什么2D数组不会出现这种衰变?

对于C或C ++新手而言,这是一个常见的混淆,但我发现自己也常常绊倒它。

它们具有不同的内存布局。 2D数组是一个连续的内存,而int**是一个指针数组。 对于2D数组,到位置的偏移量计算为rownum * collength + colnum (反之亦然,具体取决于标记行/列的方式)。 这不适用于int** ,它实际上产生两个内存读取; 第一个获取列指针,然后读取与该内存偏移的数据。

顺便提一下,这种不同的布局是必须在接受2D数组的函数中声明数组维度(除了最左边)之外的原因; 否则,编译器无法生成计算偏移量的代码。

以下是对int**的内存布局图片的尝试。 左列是一个连续的指针数组,每个指针都包含一个连续的内存块和数据的地址。 请注意,每列中的数据长度不必相同(尽管对于代码可读性而言可能不一定是好事):

 int **array; int i, j; int cntr = 1; array = malloc( 3 * sizeof( int* )); for ( i = 0; i < 3; i++ ) { array[i] = malloc( 4 * sizeof( int )); for ( j = 0; j < 4; j++ ) array[i][j] = cntr++; } 

给出这样的东西:

 array ==> |addr1| ==> [1,2,3,4] |addr2| ==> [5,6,7,8] |addr3| ==> [9,10,11,12] 

相比之下,这就是int[3][4]的布局。 括号只显示每列数据的逻辑中断。 整数值在内存中是连续的:

 int array[3][4]; int i, j; int cntr = 1; for ( i = 0; i < 3; i++ ) for ( j = 0; j < 4; j++ ) array[i][j] = cntr++; 

给出这样的东西:

 array ==> [1,2,3,4][5,6,7,8][9,10,11,12] 

该模式是“N元素数组T ”的表达式将被转换为“指向T指针”类型的表达式。 如果T是一个数组类型,那么你最后会得到一个指向数组的指针。 给出类似的声明

 T arr[M][N]; 

表达式arr具有类型“ T元素的N元素数组的M元素arrays”。 除非它是sizeof_Alignof或一元&运算符的操作数,否则它将被转换为“指向N元素数组T ”或T (*)[N]的类型的表达式。

如果有疑问,请用英语写出类型,例如“ int的六元素数组的五元素数组的四元素数组”,然后将第一个“n元素数组”替换为“指向”,给出“指向6个元素int数组的5元素数组的指针”:

 Declaration Expression Type Decays to ----------- ---------- ---- --------- int arr[4][5][6]; arr int [4][5][6] int (*)[5][6] arr[i] int [5][6] int (*)[6] arr[i][j] int [6] int * &arr int (*)[4][5][6] n/a 

将二维数组转换为指向其第一个元素的指针的结果是指针,而不是数组。 由于它不是数组,因此不会进一步转换。

它确实腐烂了。 类型为“T的数组”的表达式衰减为指向其第一个元素的指针。 对于T的二维数组,第一个元素具有T类型数组。 所以:

 int arr[5]; 

在大多数情况下, arr都有类型“指向int指针”。 现在将相同的规则应用于二维数组:

 int arr2[5][5]; 

这里, arr2具有类型“指向5 int数组的指针”。 请注意,此对象的类型不是T数组”,因此它不会进一步衰减。

二维数组是一个数组数组

在大多数情况下,“ T的数组”被转换为“指向T[N]指针,因此” T[N] “的数组被转换为”指向T[N]指针“。

自从内存表示一个

 int arr[M][N]; 

完全不同于

 int **dptr = malloc(M*sizeof *dptr); for(int i = 0; i < M; ++i) { dptr[i] = malloc(N*sizeof *dptr[i]); 

int[M][N]甚至不能转换为int**

人们常常会认为如果int* i == int i[5]不是int** i == int i[5][5]但由于两个原因,它不是那样的。 首先, int**是指向指针的指针 ,与数组的布局方式无关,其次2D数组实际上是内部的1D数组,它的作用是将第二个数组并排放置。

这意味着在[2][2]的数组内部创建了一个[4]数组,当你尝试访问[0][0] ,它被转换为[0][0][1]将翻译为[1][1][0]将被翻译为[2][1][1]将被翻译为[3]

例:

 #include  void Print(int* i, int element) { std::cout << i[element] << std::endl; } int main() { int i[5][5]; i[2][2] = 125; Print((i[0]), 12); } 

这将打印125,因为我将[2][2]为125并且将被视为等于12因为第一个[0]将占用0 - 4, [1]将占用5-10,依此类推等等。

2D数组是连续的内存块。 当它作为2D数组访问时,您需要行长度从列跳到列。 指向指针的指针就是这样。 如果第一个指针是指向第二维数组的指针数组,它可以模拟2D数组,但它与2D数组的连续内存块非常不同。