Как я могу объявить указатель на структуру в C?

#c #pointers #structure #declaration

Вопрос:

Я узнал, что указатели могут быть объявлены 3 различными способами:

 int* a;
int *b;
int * c;
 

Я предпочитаю:

 int* a;
 

При объявлении указателя на структуру правильно ли писать его следующим образом:

 struct Card {
   int a;
};
struct Card my_card = { 3, 7 };
struct Card* p = amp;my_card; 
(*p).a = 8; 
 

Я в замешательстве, потому что везде, где я нашел, это объявляется следующим:

 struct Card *p = amp;my_card;
 

Заранее спасибо.

Комментарии:

1. Пробелы могут быть опущены, вы можете объявить указатель даже следующим образом: struct Card*p = amp;my_card;

Ответ №1:

Если T есть некоторый спецификатор типа, то указатель на объект этого типа T может быть объявлен любым из следующих способов

 T*p;
T* p;
T *p;
T * p;
 

Например, если T есть int * , то объявление указателя может выглядеть так

 int **p;
int ** p;
int * *p;
int * * p;
 

Таким же образом вы можете объявить указатель на структуру

 struct Card*p = amp;my_card;
struct Card* p = amp;my_card;
struct Card *p = amp;my_card;
struct Card * p = amp;my_card;
 

Обратите внимание, что вы можете написать

 T ( *p );
 

но вы можете не писать

 ( T* ) p;
 

Также существует еще одна тонкость. Если вы напишете, например

 int* p1, p2
 

тогда переменная p1 имеет тип int * , в то время как переменная p2 имеет тип int вместо int * .

Но если вы напишете

 typedef int * T;
 

когда в этом заявлении

 T p1, p2;
 

обе переменные имеют один и тот же тип int * .

Ответ №2:

Не имеет значения, куда вы помещаете пробелы. struct Card* p и struct Card *p одинаково действительны; это просто вопрос стиля, какой из них вы предпочитаете.

Я согласен, что вторая форма более распространена, но самое главное, чтобы вы последовательно использовали одну форму на протяжении всего кода. У вашего босса / учителя / других разработчиков также могут быть стандарты стиля кодирования, которые определяют, какую форму использовать.

Ответ №3:

ИМО int *a более значима, чем int* a . В C *a может быть прочитано как «содержимое a «, поэтому int *a может быть прочитано как «содержимое a является целым числом», точно так int a же читается как « a является целым числом». Как видно из других ответов, пробелы здесь на самом деле не имеют значения — для компилятора они все одинаковы.

Этот способ написания также имеет больше смысла при объявлении нескольких переменных указателя, таких как int *a, *b . Легко ошибиться, написав int* a, b и ожидая, что оба a и b будут указателями int.

Ответ №4:

Что касается компилятора,

 T *a;
T* a;
T*a;
T      *      a;
 

все означают одно и то же — все интерпретируются как T (*a) ( a имеет тип «указатель на T «). Пробелы в данном случае не имеют значения. С вашим struct типом struct Card *p и struct Card* p делайте точно то же самое, и то и другое интерпретируется как struct Card (*p) .

Синтаксически * он всегда привязан к декларатору (подробнее об этом ниже). Это означает, что декларации, такие как

 T* a, b;
 

интерпретируются как

 T (*a), b;
 

и объявляйте только a как указатель на T b это обычный T . Это одна из многих причин, по которым я не советую использовать этот T* p стиль (ниже я приведу больше причин).

В языке C объявления состоят из двух основных частей — последовательности спецификаторов объявлений (спецификаторы типов, struct , union , и enum спецификаторы, спецификаторы классов хранения, квалификаторы типов и т.д.), За которыми следует разделенный запятыми список деклараторов. В таком заявлении, как

 static unsigned long int a[10], *p, f(void);
 

спецификаторами объявления являются static unsigned long int , а деклараторами являются a[10] , *p , и f(void) .

Декларатор вводит имя объявляемой вещи ( a , p , и f ) вместе с информацией о массивности, указателе и функции этой вещи. Тип каждого элемента полностью определяется комбинацией спецификаторов объявления и декларатора.

Структура декларатора соответствует структуре выражения в коде — если у вас есть массив указателей int и вы хотите получить доступ к определенному int значению, вам необходимо индексировать в массив и разыменовать результат с помощью унарного * оператора:

 printf( "%dn", *ap[i] );
 

Выражение *ap[i] имеет тип int , поэтому объявление ap массива является

 int *ap[N];
 

Декларатор *ap[N] соответствует структуре выражения *ap[i] .

В объявлении операторы * and [] и () используются только для указания типа — на самом деле вы не разыменовываете, не индексируете и ничего не вызываете. Однако они подчиняются тем же правилам приоритета, что и в выражениях. Постфиксные операторы имеют более высокий приоритет, чем унарные операторы, поэтому [] и () «привязка» перед * :

 T *a[N];    // parsed as *(a[N]) -- a is an array of pointers
T *f(void); // parsed as *(f(void)) - f is function returning a pointer
 

Чтобы объявить указатель на массив или функцию, необходимо явно сгруппировать * оператор с выражением массива или функции:

 T (*a)[N];    // a is a pointer to an array
T (*f)(void); // f is a pointer to a function
 

А теперь перейдем к редакционной части этого ответа (которую вы можете игнорировать)…

Со временем я стал более воинственно относиться к тому, чтобы препятствовать T* p стилю объявлений указателей. Это предпочтительный стиль среди программистов на C , но чем больше вы об этом думаете, тем меньше в этом смысла, и, по моему опыту, это просто вызывает проблемы.

Его заявленная цель — подчеркнуть «указательность» переменной — является ложной. Вы не можете подчеркнуть «массивность» переменной, объявив ее как

 T[N] a; // syntax error
 

или «функциональность» как

 T(void) f; // syntax error
 

потому что операндами постфикса [] и () операторов являются a и f , нет T . Аналогично, в заявлении, подобном

 T* p; // parsed as T (*p);
 

операнд унарного * оператора есть p , нет T . И * оператор унарный (префиксный), а не постфиксный, T* так что все равно выглядит неправильно. Потому что он унарный и потому что пробелы не имеют значения T* p , будет работать так, как ожидалось, но вы как бы игнорируете правила языка, когда делаете это.

Во всяком случае, это несовместимо с объявлением указателей на массивы или указателей на функции:

 T (*ap)[N];     // ap is a pointer to an N-element array of T
T (*fp)(void);  // fp is a pointer to a function returning T
 

и объявление указателя на массив указателей, таких как

 T* (*ap)[N];    // ap is a pointer to an array of pointers to T
 

это некрасиво и просто указывает на путаницу в мышлении, и, как я уже говорил ранее, вы не можете следовать этому соглашению с объявлениями массивов или функций:

 T[N] a;    // syntax error
T(void) f; // syntax error
 

и если вы им воспользуетесь, то неизбежно облажаетесь и напишете

 T* a, b;
 

когда ты собирался написать

 T *a, *b;
 

Теперь неизбежный ответ на это возражение состоит в том, чтобы поместить отдельные заявления в отдельные строки:

 T* a;
T* b;
 

и да, есть много веских причин для того, чтобы иметь по одной декларации на строку, но работа с плохой практикой не входит в их число. Напишите эти декларации как

 T *a;
T *b;
 

вместо.