这个习惯用法自然属于一维数组分配。让我们开始分配一个任意类型的一维数组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]),我们最终分配了一个连续的内存块,该内存块足以容纳Nby的10by 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];
如果指向常规数组,或者指向通过分配的内存,则其工作原理相同。 pmalloc
这种习语具有以下优点:
- 很简单-只需一行代码,而不是零散的分配方法
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.请记住,数组不是指针-而是根据需要将数组表达式转换为指针表达式。