怪异的方式分配二维数组?
在一个项目中,有人推动了这一行:
double (*e)[n+1] = malloc((n+1) * sizeof(*e));
据推测,它可以创build(n + 1)*(n + 1)双精度的二维数组。
据说 ,因为到目前为止,我所说的没有人能够告诉我这到底是什么,也不是它起源于什么地方,为什么它应该起作用(据说它确实有,但我还没有买)。
也许我错过了一些显而易见的东西,但是如果有人能够向我解释上述问题,我会很感激。 因为个人而言,如果我们使用我们真正理解的东西,我会感觉好多了。
variablese
是指向double
types的n + 1
元素的数组的指针。
在e
上使用反引用运算符给出了e
的基types,即“ double
types的n + 1
元素的数组”。
malloc
调用只需要e
的基types(如上所述)并获取其大小,将其乘以n + 1
,然后将该大小传递给malloc
函数。 基本上分配一个n + 1
数组的n + 1
数组。
这是您应该dynamic分配二维数组的典型方法。
-
e
是一个指向double [n+1]
types数组的数组指针。 -
sizeof(*e)
因此给出了指向types的types,这是一个double [n+1]
数组的大小。 - 你为
n+1
这样的数组分配空间。 - 您将数组指针设置为指向此数组数组中的第一个数组。
- 这使您可以使用
e
e[i][j]
来访问2D数组中的各个项目。
我个人认为这种风格更容易阅读:
double (*e)[n+1] = malloc( sizeof(double[n+1][n+1]) );
这个成语自然是脱离了一维数组的分配。 让我们从分配一些任意typesT
一维数组开始:
T *p = malloc( sizeof *p * N );
很简单,对吧? expression式 *p
具有typesT
,所以sizeof *p
给出了与sizeof (T)
相同的结果,所以我们为T
的N
元素数组分配足够的空间。 对于任何typesT
都是如此。
现在,让我们用一个像R [10]
这样的数组types来replaceT
那我们的分配就变成了
R (*p)[10] = malloc( sizeof *p * N);
这里的语义与1D分配方法完全一样 , 所有改变的是p
的types。 现在是R (*)[10]
,而不是T *
。 expression式*p
typesT
是typesR [10]
,所以sizeof *p
等于sizeof (T)
,它等于sizeof (R [10])
。 所以我们为R
10
元素的数组分配足够的空间。
如果我们愿意,我们可以更进一步。 假设R
本身就是一个数组typesint [5]
。 替代R
,我们得到
int (*p)[10][5] = malloc( sizeof *p * N);
同样的处理 – sizeof *p
和sizeof (int [10][5])
,我们结束分配一个足够大的内存块来保存一个N
乘10
乘5
的int
数组。
这就是分配方面; 接入方面呢?
请记住, []
下标操作是根据指针运算来定义的: a[i]
被定义为*(a + i)
1 。 因此,下标运算符[]
隐式地解引用一个指针。 如果p
是指向T
的指针,则可以通过使用一元*
运算符显式取消引用来访问指向的值:
T x = *p;
或者使用[]
下标运算符:
T x = p[0]; // identical to *p
因此,如果p
指向数组的第一个元素,则可以使用指针p
上的下标来访问该数组的任何元素:
T arr[N]; T *p = arr; // expression arr "decays" from type T [N] to T * ... T x = p[i]; // access the i'th element of arr through pointer p
现在,让我们再次做我们的replace操作,并用数组typesR [10]
replaceT
:
R arr[N][10]; R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10] ... R x = (*p)[i];
一个立即明显的差异; 在应用下标运算符之前,我们明确地解引用了p
。 我们不想下标成p
,我们要下标到p
指向的地方 (在这种情况下是数组 arr[0]
)。 由于一元*
优先级低于下标[]
运算符,因此我们必须使用括号将p
明确地与*
。 但从上面记得*p
和p[0]
是一样的,所以我们可以用
R x = (p[0])[i];
要不就
R x = p[0][i];
因此,如果p
指向一个二维数组,我们可以像这样通过p
索引到该数组中:
R x = p[i][j]; // access the i'th element of arr through pointer p; // each arr[i] is a 10-element array of R
采取与上述相同的结论,并用int [5]
代替R
int [5]
:
int arr[N][10][5]; int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5] ... int x = p[i][j][k];
如果p
指向一个常规的数组,或者指向通过malloc
分配的内存,这个工作原理是一样的 。
这个习语有以下好处:
- 这很简单 – 只是一行代码,而不是零碎的分配方法
T **arr = malloc( sizeof *arr * N ); if ( arr ) { for ( size_t i = 0; i < N; i++ ) { arr[i] = malloc( sizeof *arr[i] * M ); } }
- 分配数组的所有行都是*连续的*,这与上面的零碎分配方法不同;
- 释放数组对于释放数据同样简单。 再一次,不正确的零碎分配方法,你必须释放每个
arr[i]
然后才能释放arr
。
有时零碎的分配方法是可取的,比如当你的堆碎片很差,你不能把你的内存分配成一个连续的块时,或者你想分配一个“锯齿”的数组,每一行的长度可能不一样。 但总的来说,这是更好的方法。
1.请记住,数组不是指针,而是根据需要将数组expression式转换为指针expression式。