有人可以向我解释为什么从技术上讲二维数组实际上并没有衰减到,int**
而当你有一个一维数组时,你确实会得到指针衰减到int*
(如果你有数组int
)。
我知道指针衰减是什么,但我不明白为什么二维数组没有表现出这种衰减?
对于刚接触 C 或 C++ 的人来说,这是一个常见的困惑,但我发现自己也经常被它绊倒。
它们有不同的内存布局。二维数组是一块连续的内存,int**
而是一个指针数组。使用 2D 数组,到某个位置的偏移量计算为rownum * collength + colnum
(或反之亦然,具体取决于您如何标记行/列)。这不适用于int**
,它实际上会产生两次内存读取;首先获取列指针,然后从该内存的偏移处读取数据。
顺便说一句,这种不同的布局是为什么您必须在接受二维数组的函数中声明数组维度(除了最左边的);否则编译器不可能生成代码来计算偏移量。
下面是一张内存布局图的尝试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-element array of T
”类型的表达式将被转换为“pointer to T
”类型的表达式。如果T
是一个数组类型,那么你会得到一个指向数组的指针。给定一个像这样的声明
T arr[M][N];
该表达式arr
的类型为“N 元素数组的 M 元素数组T
”。除非它是 、 或 一元运算符的操作数sizeof
,_Alignof
否则&
它将被转换为“指向 N 元素数组的指针T
”或类型的表达式T (*)[N]
。
如有疑问,用英文写出类型,如“四元素数组五元素数组六元素数组int
”,然后将第一个“n元素数组”替换为“指针”,给出“指向int
“:”的 6 元素数组的 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
”,因此它不会进一步衰减。
人们可能会认为 if int* i
==int i[5]
比int** i
==是常见的错误,int i[5][5]
但由于两个原因,它并非如此。首先,anint**
是指向指针的指针,与数组的布局方式无关,其次,2D 数组实际上是内部的 1D 数组,它的作用是将第二个数组并排放置。
这意味着在[2][2]
内部数组中正在创建一个数组,[4]
当您尝试访问时,它将[0][0]
被翻译成[0]
,[0][1]
将被翻译成[1]
,[1][0]
将被翻译成[2]
并将[1][1]
被翻译成[3]
例子:
#include <iostream>
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,依此类推。
二维数组是数组的数组。
T
在大多数情况下,“数组”被转换为“指针” T
,因此“数组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]);
anint[M][N]
甚至不能转换为int**
.
二维数组是一个连续的内存块。将其作为二维数组访问时,您需要行长才能从列跳到列。指向指针的指针正是如此。如果第一个指针是指向第二维数组的指针数组,它可以模拟 2D 数组,但它与 2D 数组的连续内存块完全不同。