Объявления в C ориентированы на выражения, что означает, что форма объявления должна соответствовать форме выражения в исполняемом коде.
Например, предположим, что у нас есть указатель на целое число с именем p
,Мы хотим получить доступ к целочисленному значению, указанному p
, поэтому мы разыменовываем указатель, например:
x = *p;
Тип выражения *p
- это int
;поэтому объявление p
принимает форму
int *p;
В этом объявлении int
является спецификатором типа , а *p
является декларатором .Декларатор вводит имя объявляемого объекта (p
) вместе с дополнительной информацией о типе, не предоставляемой спецификатором типа.В этом случае дополнительная информация о типе состоит в том, что p
является типом указателя.Объявление можно прочитать как «p
имеет указатель типа на int
» или «p
является указателем на тип int
».Я предпочитаю использовать вторую форму, другие предпочитают первую.
Это случайность синтаксиса C и C ++, когда вы можете написать это объявление как int *p;
или int* p;
.В обоих случаях он анализируется как int (*p);
- другими словами, *
всегда ассоциируется с именем переменной, а не с указателем типа.
Теперь предположим, что у нас есть массив указателей на int
, и мы хотим получить доступ к значению, на которое указывает i-й элемент массива.Мы добавляем в массив и разыменовываем результат, как показано ниже:
x = *ap[i]; // parsed as *(ap[i]), since subscript has higher precedence
// than dereference.
Опять тип выражения *ap[i]
равен int
, поэтому объявление ap
это
int *ap[N];
, где декларатор *ap[N]
означает, что ap
является массивом указателей на int
.
И просто, чтобы обозначить точку, теперь предположим, что у нас есть указатель на указатель на int
и мы хотим получить доступ к этому значению.Опять же, мы обращаемся к указателю, а затем к получению целого значения:
x = **pp; // *pp deferences pp, then **pp dereferences the result of *pp
Поскольку тип выражения **pp
равен int
, объявление равно
int **pp;
Декларатор **pp
указывает, что pp
является указателем на другой указатель на int
.
Двойная косвенность часто проявляется, когда вы хотите изменить значение указателя, передаваемого в функцию, например:
void openAndInit(FILE **p)
{
*p = fopen("AFile.txt", "r");
// do other stuff
}
int main(void)
{
FILE *f = NULL;
...
openAndInit(&f);
...
}
В этом случае нам нужна функцияобновить значение f
;чтобы сделать это, мы должны передать указатель на f
.Поскольку f
уже является типом указателя (FILE *
), это означает, что мы передаем указатель на FILE *
, следовательно, объявление p
как FILE **p
.Помните, что выражение *p
в openAndInit
относится к тому же объекту, что выражение f
в main
.
И в объявлениях, и в выражениях оба значения []
и ()
имеют более высокий приоритет, чем унарный *
.Например, *ap[i]
интерпретируется как *(ap[i])
;выражение ap[i]
является типом указателя, а *
разыменовывает этот указатель.Таким образом, ap
- это массив указателей .Если вы хотите объявить указатель на массив , вы должны явно сгруппировать *
с именем массива, например так:
int (*pa)[N]; // pa is a pointer to an N-element array of int
и когда вы хотите получить доступ к значениюв массиве необходимо применить pa
перед применением индекса:
x = (*pa)[i];
Аналогично функциям:
int *f(); // f is a function that returns a pointer to int
...
x = *f(); // we must dereference the result of f() to get the int value
int (*f)(); // f is a pointer to a function that returns an int
...
x = (*f)(); // we must dereference f and execute the result to get the int value