这个习惯用法自然属于一维数组分配。让我们开始分配一个任意类型的一维数组T
:
T *p = malloc( sizeof *p * N );
简单吧?该表达式 *p
具有类型T
,因此sizeof *p
给出的结果与相同sizeof (T)
,因此我们为的N
-element数组分配了足够的空间T
。任何类型T
都是如此。
现在,让我们T
用类似的数组类型代替R [10]
。然后我们的分配变成
R (*p)[10] = malloc( sizeof *p * N);
这里的语义与一维分配方法完全相同。所有更改的只是的类型p
。取而代之的T *
是现在R (*)[10]
。表达*p
具有类型T
是类型R [10]
,因此sizeof *p
等效于sizeof (T)
这相当于sizeof (R [10])
。因此,我们为的N
按10
元素数组分配了足够的空间R
。
如果需要,我们可以更进一步。假设R
本身就是数组类型int [5]
。替换为R
,我们得到
int (*p)[10][5] = malloc( sizeof *p * N);
相同的处理- sizeof *p
与相同sizeof (int [10][5])
,我们最终分配了一个连续的内存块,该内存块足以容纳N
by的10
by by 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
现在,让我们再次进行替换操作,并替换T
为数组类型R [10]
:
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
指向的内容(在本例中为array arr[0]
)。由于一元的*
优先级低于下标[]
运算符,因此我们必须使用括号将明确地分组p
为*
。但是请记住,从上面*p
与相同p[0]
,因此我们可以用来代替
R x = (p[0])[i];
要不就
R x = p[0][i];
因此,如果p
指向2D数组,我们可以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
得出与上述相同的结论,并代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 );
}
}
- 分配的数组的所有行都是“连续的”,上面的零碎分配方法不是这种情况。
- 只需调用即可取消分配数组的操作
free
。同样,对于零碎分配方法,这是不正确的,在这种方法中,必须先分配每个分配的对象,arr[i]
然后才能分配arr
。
有时,最好采用零散分配方法,例如,当堆严重碎片化并且无法将内存分配为连续的块时,或者要分配“锯齿状”数组,其中每行的长度可以不同。但总的来说,这是更好的方法。
1.请记住,数组不是指针-而是根据需要将数组表达式转换为指针表达式。