我的理解是,如果一个人在本地声明一个2d数组:int 2darr[x][y]
,它不是一个指针数组,其中每个指针指向它自己的1d数组,而是它是一个1d数组,在该数组上处理器执行类型为*(2darr + (row x nCols) + col)
的指针算法。
在这种情况下,语法糖2darr[row][col]
后面的指针算术是有意义的,因为我们的2d数组实际上只是一个大小为nRows x nCols
的单个连续内存块。
然而,动态分配2d数组的一种方法是首先分配大小为nRows
的指针数组,然后为每个指针分配一个大小为nCols
的数组,我们想要任何类型。在这种情况下,我们的行不一定是连续存储在内存中;每行可以存储在内存中完全不同的位置,其中指针数组中的一个指针指向其第一个元素。
鉴于此,我不明白我们如何通过2darr[row][col]
仍然可以访问二维数组中的数据。由于不保证我们的行是连续存储的,因此不应保证类型*(2darr + (row x nCols) + col)
的指针算法完全可用。
答案 0 :(得分:2)
您的数组2darr
是一个数组的数组。
例如,像
这样的定义int aa[2][3];
是一个包含两个元素的数组,每个元素又是一个包含三个int
值的数组。
在内存中它看起来像这样
+----------+----------+----------+----------+----------+----------+ | aa[0][0] | aa[0][1] | aa[0][2] | aa[1][0] | aa[1][1] | aa[1][2] | +----------+----------+----------+----------+----------+----------+
关于可能让你感到困惑的指针算法的部分是对于任何数组(或指针!)a
和索引i
,表达式a[i]
等于{{1 }}
使用上面没有数组数组的“公式”,*(a + i)
得到的是另一个数组。即aa[i]
是另一个数组,您可以使用索引,例如*(aa + i)
。第二级索引当然也可以使用指针算法编写,如(*(aa + i))[j]
。
当你遇到一个数组数组时,你所显示的表达式(没有数组*(*(aa + i) + j)
将是aa
)得到的结果是不正确的。我的意思是它不会语义正确。这是因为*(aa + i * 3 + j)
与*(aa + i * 3 + j)
实际上相同,在aa[i * 3 + j]
的情况下是数组。表达式aa
(因此aa[i * 3 + j]
)的类型为*(aa + i * 3 + j)
。它不是一个int[3]
元素。
如果您有一个数组,那么表单int
上的表达式才是正确的。像
*(a + row * ncol + col)
现在可以使用int bb[6]; // 6 = 2 * 3
(或*(bb + i * 3 + j)
)对此数组建立索引,结果将是一个bb[i * 3 + j]
值。
使用指针指针实现的“二维”数组(实际上并非如此)也称为jagged array,并且它不必是连续的。这意味着int
表达式确实无效。
再举一个简单的例子:
*(2darr + (row x nCols) + col)
上面的代码创建了一个与上面int **pp;
pp = malloc(sizeof *pp * 2); // Two elements in the "outer" array
for (size_t i = 0; i < 2; ++i)
{
pp[i] = malloc(sizeof **pp * 3); // Three elements in the "inner" array
}
类似的“二维”数组。最大的区别是它的内存布局,类似于
+-------+-------+ | pp[0] | pp[1] | +-------+-------+ | | | v | +----------+----------+----------+ | | pp[1][0] | pp[1][1] | pp[1][2] | | +----------+----------+----------+ v +----------+----------+----------+ | pp[0][0] | pp[0][1] | pp[0][2] | +----------+----------+----------+
对于外部数组,aa
仍然等于pp[i]
,但*(pp + i)
导致三个aa[i]
元素的数组,int
是指向pp[i]
的指针(即int
)。
由于您可以将数组索引语法与指针一起使用,因此可以将int *
的指针编入索引,然后使用“二维”语法pp[i]
。
虽然pp[i][j]
表达式无效,但由于内存不连续,所以上面显示的所有其他指针算法都是。例如(如图所示)*(pp + i * 3 + j)
等于pp[i]
。但由于这是一个可以编入索引的指针,*(pp + i)
也有效,(*(pp + i))[j]
也是如此。
答案 1 :(得分:2)
使用SomeType A[M][N]
定义的数组和使用指向指针数组的指针实现的数组都可以作为A[i][j]
访问的原因是由于下标运算符如何工作,指针运算如何工作,以及将数组自动转换为指针。
一个关键的区别是,在带有指针的A[i][j]
中,A[i]
是一个指针,其值从内存中获取,然后与[j]
一起使用。相反,在带有数组的A[i][j]
中,A[i]
是一个数组,其作为指针的值基于数组本身;在表达式中使用数组将转换为指向其第一个元素的指针。用于指针的A[i]
和用于数组的A[i]
都需要使用指针用于下一步,但第一个是从内存中的指针加载而第二个是从数组存储在内存中的位置计算的。
首先,考虑使用以下内容定义的数组:
SomeType A[M][N];
鉴于此,当评估表达式A[i][j]
时,评估将继续进行:
p
。 A
是一个M
元素数组,每个元素都是N
SomeType
元素的数组。因此p
是指向N
的第一个SomeType
元素数组的指针。p
取代A
,因此表达式现为p[i][j]
。E1[E2]
与(*(E1+E2))
相同。 (正式定义有括号,为简洁起见,我省略了。)当我们将其应用于第一个下标时,p[i][j]
变为(*(p+i)[j]
。p+i
。指针算术以指向类型为单位工作。由于p
指向N
元素的数组,p+i
从第一个数组(索引为0)移动到索引为i
的数组。我们称之为q
。(*q)[j]
,其中q
指向i
的元素A
。请注意,此元素q
指向的是N
SomeType
元素的数组。q
指向数组,*q
数组。r
。 r
指向数组q
的第一个元素指向。(r)[j]
,或者删除括号r[j]
,其中r
指向i
元素A
的数组的元素0 }。(*(r+j))
相同。r+j
指向数组的元素j
。r+j
指向元素j
,*(r+j)
是数组的j
元素。A[i][j]
是j
中由i
编制索引的数组的元素A
。现在考虑使用指针指针实现的二维数组,如下代码所示:
SomeType **A = malloc(M * sizeof *A);
for (size_t i = 0; i < M; ++j)
A[i] = malloc(N * sizeof *A[i]);
(我们假设所有malloc
次调用都成功。在生产代码中,应对它们进行测试。)
鉴于此,当评估表达式A[i][j]
时,评估将继续进行:
SomeType
的指针的指针。A[i][j]
与(*(A+i))[j]
相同。A+i
从A
指向i
以外的A
元素移动。在这种情况下,A+i
指向指针(特别是指向SomeType的指针),因此指针算术的元素就是那些指针。所以i
指向超出第一个指针的q
个指针。我们称之为(*q)[j]
。q
,其中i
指向我们创建的指针数组中的元素q
。*q
指向指针,r
是指针。我们称之为r
。 SomeType
指向分配了malloc
次调用之一的第一个元素((r)[j]
)。r[j]
,或者删除括号r
,其中i
指向指针数组中的元素(*(r+j))
。r+j
相同。j
指向第一个元素r
所指向的数组的元素r+j
。j
指向元素*(r+j)
,j
是数组的A[i][j]
元素。j
是i
中由A
编制索引的数组的元素sizeof
。 1 类型为“ type ”数组的表达式将转换为指向数组第一个元素的指针,除非它是{{1}的操作数},_Alignof
或一元&
或是用于初始化数组的字符串文字。