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数组的连续内存块非常不同。