Есть ли соглашение для объявлений указателей в C?

При объявлении указателей в C есть 3 варианта:

Вариант А:

int* ptr;

Вариант B:

int *ptr;

Вариант C:

int * ptr;
  • В A оператор косвенности был добавлен к типу.
  • В B оператор косвенности был добавлен к переменной.
  • В Си оператор косвенного обращения свободно находится между типом и переменной.

Способ объявления указателя отличается в зависимости от типа документации, которую я читаю. Некоторые авторы предпочитают определенные варианты, другие используют несколько.

  • Правильно ли предположить, что между различными вариантами нет различий в функциональности?
  • Если да, существует ли соглашение, какой вариант следует использовать в C?

Ответ 1

Нет никакой разницы в функциональности между

int* ptr;

и

int *ptr;

который вы используете, зависит от вас, существует несколько противоречивых стилей кодирования.

Ответ 2

Что-то, о чем никто еще не упомянул, заключается в том, что

int *ptr;

ближе соответствует языковой грамматике.

  • int *ptr; - это объявление, состоящее из:
    • объявление-спецификатор int, за которым следует
    • объявление, *ptr.

(Это фактически пропускает несколько шагов, но он получает основную идею.)

Поскольку объявление следует за использованием, это означает, что *ptr имеет тип int. Отсюда следует, что ptr имеет тип int*.

Можно утверждать, что это делает его лучше, чем

int* ptr;

по той же причине, что

x = y+z;

лучше, чем

x=y + z;

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

int* ptr;

и прочитайте его как "ptr имеет тип int*". И много программистов делают именно это и отлично ладят (это, как правило, предпочтительный стиль на С++). Компилятору все равно, как вы это делаете, и у любого, кто читает ваш код, не должно быть проблем с пониманием этого в любом случае.

Но какой бы промежуток вы ни выбрали, вам нужно понять, что означает int *ptr;, так что, когда вы видите

int *ptr, i;

в чужом коде (как вы неизбежно будете), вы сразу поймете, что ptr является указателем, а i является int.

И если вы работаете с другими программистами в проекте, вы должны следить за тем, что существующее соглашение находится в стандартах кодирования, или если его нет, способ, которым код уже написан. Я лично предпочитаю int *ptr; до int* ptr;, но использование смеси обоих стилей намного хуже, чем использование одного из них последовательно.

Ответ 3

Это имеет значение только тогда, когда вы планируете объявлять несколько переменных одного и того же типа в одной строке. Например, если вы хотите использовать несколько указателей int, вам нужно сделать это:

int *a, *b, *c;

Стилистически, но это путает, когда вы только объявляете одну переменную. Многие люди любят видеть тип, за которым следует имя переменной, и тип должен быть указателем на int, а не int, поэтому они предпочитают:

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

В конечном счете, до вас, предпочитаете ли вы одну форму над другой. За 20 лет программирования C профессионально, я видел, что около 50% людей выбирают друг друга.

Ответ 4

T *a;

является предпочтительным стилем C для объявления указателя на T который используется в книге Kernighan & Ritchie о C, а также в ISO/IEC 9899: 2018.

T* a;

является предпочтительным способом C++ для указания указателя на T который используется в книге Stroustrup о C++.

Оба обозначения эквивалентны.

Ответ 5

Оба они означают то же, что говорили другие. Тем не менее, вас ждет ловушка. Рассмотрим этот код:

int* a, b;

Вы можете подумать, что это объявлено указателями на int. Нет, но иначе. На самом деле a есть int*, но b есть int. Это одна из причин, по которой многие программисты С предпочитают помещать * рядом с переменной, а не тип. Когда написано так:

int *a, b;

вы менее склонны вводить в заблуждение относительно того, что a и b.

Сказав все это, многие стандарты кодирования настаивают на том, что вы объявляете не более одной переменной на строку, i, e.

int* a;
int b;

Если вы придерживаетесь одной переменной для правила строки, то определенно нет возможности для путаницы.

Ответ 6

Объявления C основаны на типах выражений, а не на объектах.

Если у вас есть указатель на int с именем pi, и вы хотите получить доступ к целочисленному значению, на которое оно указывает, вы должны разыменовать указатель, как в:

x = *pi;
printf("%d", *pi);
*pi = 1 + 2;

и т.д.. Тип выражения *pi равен int: поэтому декларация должна читать как

int *pi;

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

c = *pc[i];
if (*pc[j] == 'a') {...}

и т.д.. Опять же, тип выражения *pc[i] равен char, поэтому декларация читается как

char *pc[N];

Оба *pi и *pc[N] известны как деклараторы и указывают дополнительную информацию о типе, не указанную спецификатором типа. IOW, массив и указатель pc указываются как часть декларатора, а char - задается спецификатором типа.

Что касается вопроса о том, какой стиль правильный...

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

Обычный ответ на эту критику - "не объявлять более одного пункта за строку". Мой ответ на этот ответ "правильно напишите ваши деклараторы, и у вас не будет никаких проблем".

У многих программистов на C++ существует другая школа мысли, которые предпочитают стиль T* p, и я должен сказать, что есть несколько случаев (ограниченных С++), где он может сделать код более читаемым.

Однако это работает только для простых указателей на T: парадигма быстро разрушается, когда вы начинаете работать с массивами указателей или указателями на массивы или указателями на функции или указателями на массивы указателей на функции и т.д. Я имею в виду, пишу что-то вроде

T* (*(*p)[N])(); // p is a pointer to an array of pointers to functions
                 // returning pointers to T.

просто указывает на путаное мышление. Хотя, если вы действительно действительно чувствуете, что вы должны следовать парадигме T* p, вы всегда можете создать серию typedefs:

ИЗМЕНИТЬ

Повторите попытку:

typedef T* TPtr;                           // pointer to T
typedef TPtr TPtrFunc();                   // function returning pointer to T
typedef TPtrFunc* TPtrFuncPtr;             // pointer to function returning
                                           // pointer to T
typedef TPtrFuncPtr TPtrFuncPtrArray[N];   // N-element array of pointer to function
                                           // returning pointer to T

TPtrFuncPtrArray* p;

За любовь к Богу не делайте этого.

Ответ 7

В C пробелы не имеют значения, кроме случаев, когда необходимо разделить токены. Оба варианта синтаксически правильны.

Вариант 1 связывает оператор указателя с типом, который достаточно логичен. Для меня этого было бы достаточно, если бы не по той причине, почему Вариант 2 имеет смысл.

Вариант 2 согласуется с тем, как структурируются объявления C. В грамматике C оператор указателя принадлежит декларатору (то есть с именем), а не типу. Это имеет значение при объявлении нескольких переменных в одной декларации. Есть также ряд более эзотерических случаев, когда это имеет значение.

Таким образом, для меня вариант 2 более согласуется с C. Но любой вариант является оправданным, и оба варианта являются обычными.

Ответ 8

Вы правы, оба означают точно то же самое для компилятора. Оба оператора создадут переменную типа (int *).

Как правильно: Can of worms! Обычно это тема для обсуждения. Если вы работаете в компании или на OSS, вероятно, лучше всего отнестись к определенному стилю кодирования. Если нет, то я обычно использую стиль LNT (оставить без следа), соответствующий любому стилю, который, по-видимому, использовался в этой части базы кода.

Можно утверждать, что читателю легче понять. Например int* ptr; помещает * ближе к int, который более четко сообщает об этом, мы говорим о типе (int *)...