T (*p)[N];
объявляет p
как указатель на массив из N элементов с T
. В основном вы видите их в следующих контекстах:
- В результате выражения массива более высокого размера "затухает" в выражении указателя;
- При выделении памяти для блока более высокого размерамассив;
В качестве примера первого рассмотрим следующий код:
void foo( int (*p)[3] )
{
// do something with p
}
int main( void )
{
int arr[2][3];
foo( arr );
}
За исключением случаев, когда он является операндом операторов sizeof
или унарных &
,или является строковым литералом, используемым для инициализации массива символов в объявлении, выражение типа «массив N-элементов из T
» будет преобразовано («распад») в выражение указателя типа «указатель»в T
", а значением выражения будет адрес первого элемента массива.
Когда вы передаете выражение arr
в качестве параметра в foo
он преобразуется из типа «2-элементный массив из 3-элементного массива int
» (int [2][3]
) в «указатель на 3-элементный массив из int
» (int (*)[3]
). Таким образом, функция фактически получает значение указателя, а не массив.
Поскольку указатель на синтаксис массива немного уродлив и громоздок, вы можете поочередно использовать
void foo( int p[][3] ) { ... }
для объявления, но только в объявлении параметра функции.
В качестве примера последнего, если вы хотите динамически объявить 2D-массив, вы можете сделать это:
int (*p)[3] = malloc( 2 * sizeof *p );
, который выделяет пространство для массива 2x3 int
и назначаетуказатель на это пространство на p
. Типом выражения p
является «указатель на массив из 3 элементов int
», поэтому из *p
следует указать «массив из 3 элементов int
».
Помните, что операция индексации массива a[i]
определяется как *(a + i)
- с учетом начального адреса a
, смещений i
элементов ( не байтов ) из этого адреса и разыменованиярезультат 1 . Следовательно, из этого следует, что
*p == *(p + 0) == p[0]
означает
(*p)[i] == (*(p + 0))[i] == (p[0])[i] == p[0][i]
Таким образом, после того, как вы выделите память для p
, вы можете индексировать ее как любой двумерный массив:
for ( size_t i = 0; i < 2; i++ )
for ( size_t j = 0; j < 3; j++ )
p[i][j] = some_value;
Общие правила:
T *a[N]; // a is an N-element array of pointer to T
T (*a)[N]; // a is a pointer to an N-element array of T
T *f(); // f is a function returning a pointer to T
T (*f)(); // f is a pointer to a function returning T
Поскольку операторы нижнего индекса []
и вызова функции ()
имеют более высокий приоритет, чем унарные *
, *a[i]
будет обрабатываться как *(a[i])
и *f()
будет проанализирован как *(f())
. Если вы хотите трактовать a
как указатель на массив и f
как указатель на функцию, вы должны явно сгруппировать оператор *
с идентификатором, используя скобки - (*a)[i]
и (*f)()
.
C был получен из более раннего языка, называемого B. В B массивы имели явный указатель на первый элемент, и этот указатель был привязан кпеременная массива. Итак, в B синтаксис *(a + i)
имел смысл, потому что a
всегда был указателем. Когда Ричи разрабатывал C, он не хотел сохранять этот явный указатель в массиве, поэтому вместо этого он ввел правило, согласно которому выражения в массиве «затухают» для указателей, за исключением особых случаев.