В объявлении унарный *
указывает на то, что объявленная вещь имеет тип указателя:
T *p; // p has type "pointer to T"
T *p[N]; // p has type "array of pointer to T"
T (*p)[N]; // p has type "pointer to array of T"
T *p(); // p has type "function returning pointer to T"
T (*p)(); // p has type "pointer to function returning T"
Операторы нижнего индекса []
и вызова функции ()
имеют более высокий приоритет, чем унарные *
, поэтому такое выражение, как *p[i]
, будет проанализировано как *(p[i])
; вы разыменовываете результат p[i]
. Если p
является указателем на массив чего-либо, то вы должны написать (*p)[i]
- вам нужно разыменовать p
и затем добавить в результат.
Вы можете иметь несколько уровней косвенности:
T **p; // p has type "pointer to pointer to T"
T ***p; // p has type "pointer to pointer to pointer to T"
T *(*p)[N]; // p has type "pointer to array of pointer to T"
T *(*(*p)())[N]; // p is a pointer to a function returning a pointer
// to an N-element array of pointer to T
В выражении унарный оператор *
разыменовывает указатель для доступа к указанному объекту, как в вашем примере. Опять же, вы можете иметь несколько уровней косвенности:
int x = 10;
int *p = &x; // p stores the address of x
int **pp = &p; // pp stores the address of p
После этих заявлений верно следующее:
**pp == *p == x == 10 // all of these expressions have type int
*pp == p == &x // all of these expressions have type int *
pp == &p // all of these expressions have type int **
&pp // this expression has type int ***
Синтаксис объявления C построен на типе выражений . Предположим, у вас есть указатель на int
с именем p
и вы хотите получить доступ к этому целочисленному значению. Вы бы написали выражение вроде
x = *p;
Тип выражения *p
равен int
, поэтому объявление записывается как
int *p;
Если у вас есть массив указателей на int
с именем p
и вы хотите получить доступ к целочисленному значению, указанному i
'-ым элементом, вы должны написать
x = *p[i];
Опять же, тип выражения *p[i]
равен int
, поэтому объявление записывается как
int *p[N];
Множественная косвенность проявляется в двух основных местах:
Вы хотите, чтобы функция записывала параметр типа указателя
Помните, что C передает все аргументы функции по значению, поэтому, если вы хотите, чтобы функция записывала параметр, вы должны передать указатель на этот параметр:
void foo( T *ptr )
{
*ptr = new_value(); // writes a new value to the thing ptr points to
}
void bar( void )
{
T value;
foo ( &value ); // writes a new value to value
}
Заменим T
типом указателя P *
:
void foo( P **ptr )
{
*ptr = new_value(); // writes a new value to the thing ptr points to
}
void bar( void )
{
P *value;
foo ( &value ); // writes a new value to value
}
Как видите, он работает так же. Мы хотим обновить содержимое value
, поэтому мы должны передать указатель на него. Да, value
уже имеет тип указателя, но нам нужен указатель на value
самого , чтобы обновить его.
Помните, что если lvalue-выражение x
имеет тип T
, то выражение &x
имеет тип T *
. Снова замените T
указателем типа P *
, и вы увидите, что выражение &x
имеет тип P **
.
Вы хотите выделить зубчатый массив
Иногда вам нужна двумерная (или трехмерная, или выше) структура в виде массива, но вы не хотите, чтобы строки были одинакового размера. Обычный способ сделать это так:
T **arr = malloc( N * sizeof *arr ); // allocate an array to hold N objects of type T *
if ( arr )
{
for ( size_t i = 0; i < N; i++ )
{
arr[i] = malloc( M * sizeof *arr[i] ); // allocate an array to hold M objects of type T, where M can vary from row to row
}
}
arr
указывает на первое в последовательности указателей на T
.