Ця ідіома природно випадає з розподілу 1D-масиву. Почнемо з виділення 1D масиву якогось довільного типу T
:
T *p = malloc( sizeof *p * N );
Просте, правда? Вираз *p
має тип T
, тому sizeof *p
дає той же результат , як і sizeof (T)
, таким чином , ми виділити достатньо місця для N
елементного масиву T
. Це справедливо для будь-якого типуT
.
Тепер давайте T
замінимо типу масиву типу R [10]
. Тоді наше виділення стає
R (*p)[10] = malloc( sizeof *p * N);
Семантика тут точно така ж, як метод 1D-розподілу; все, що змінилося, - це тип 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
допомогою з 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
Тепер давайте зробимо нашу операцію заміни ще раз і замінимо 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
вказує (у даному випадку масив 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. Пам'ятайте, що масиви не є покажчиками - натомість вирази масивів перетворюються на вирази вказівників, якщо це необхідно.