#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;
вместо.