Ответы:
Они ТОЧНО эквивалентны. Однако в
int *myVariable, myVariable2;
Кажется очевидным, что myVariable имеет тип int * , а myVariable2 имеет тип int . В
int* myVariable, myVariable2;
может показаться очевидным, что оба имеют тип int * , но это неверно, так как myVariable2
имеет тип int .
Поэтому первый стиль программирования более интуитивен.
int* someVar
личных проектов. Это имеет больше смысла.
int[10] x
. Это просто не синтаксис Си. Грамматика явно разбирается как:, int (*x)
а не как (int *) x
, поэтому размещение звездочки слева просто вводит в заблуждение и основано на неправильном понимании синтаксиса объявления Си.
int* myVariable; int myVariable2;
вместо этого.
Если вы посмотрите на это по-другому, *myVariable
это тип int
, который имеет некоторый смысл.
myVariable
может быть NULL, в этом случае *myVariable
вызывает ошибку сегментации, но нет типа NULL.
int x = 5; int *pointer = &x;
потому что он предполагает, что мы устанавливаем для int *pointer
значение, а не pointer
само значение.
Потому что * в этой строке привязывается ближе к переменной, чем к типу:
int* varA, varB; // This is misleading
Как указывает @Lundin ниже, const добавляет еще больше тонкостей для размышлений. Вы можете полностью обойти это, объявив одну переменную на строку, что никогда не бывает двусмысленным:
int* varA;
int varB;
Трудно установить баланс между чистым кодом и кратким кодом - дюжина избыточных строк int a;
тоже не годится. Тем не менее, я по умолчанию одно объявление на строку и беспокоюсь о комбинировании кода позже.
int *const a, b;
. Где * "связать"? Тип a
is int* const
, так как вы можете сказать, что * принадлежит переменной, когда она является частью самого типа?
Что-то еще никто не упомянул здесь, это то, что эта звездочка на самом деле является « оператором разыменования » в C.
*a = 10;
Линия выше не означает , что я хочу , чтобы назначить 10
на a
это означает , что я хочу , чтобы назначить 10
в любое место памяти a
указывает. И я никогда не видел, чтобы кто-нибудь писал
* a = 10;
у тебя есть? Таким образом, оператор разыменования почти всегда пишется без пробела. Это, вероятно, чтобы отличить его от умножения, разбитого на несколько строк:
x = a * b * c * d
* e * f * g;
Это *e
могло бы ввести в заблуждение, не так ли?
Хорошо, теперь, что на самом деле означает следующая строка:
int *a;
Большинство людей скажут:
Это означает, что a
это указатель на int
значение.
Это технически правильно, большинству людей нравится видеть / читать его таким образом, и именно так его определяют современные стандарты C (обратите внимание, что язык C сам по себе предшествует всем стандартам ANSI и ISO). Но это не единственный способ взглянуть на это. Вы также можете прочитать эту строку следующим образом:
Значение разыменования a
имеет тип int
.
Таким образом, на самом деле звездочка в этом объявлении также может рассматриваться как оператор разыменования, что также объясняет его размещение. И этот a
указатель на самом деле не объявлен вообще, это подразумевается тем фактом, что единственное, что вы можете разыменовать, это указатель.
Стандарт C определяет только два значения для *
оператора:
И косвенность - это просто одно значение, нет никакого дополнительного значения для объявления указателя, есть просто косвенность, то есть то, что делает операция разыменования, она выполняет косвенный доступ, так же как и внутри оператора, int *a;
такого как косвенный доступ ( *
означает косвенный доступ) и, следовательно, второе утверждение, приведенное выше, намного ближе к стандарту, чем первое.
int a, *b, (*c)();
то вроде «объявляю следующие объекты как int
: объект a
, объект , на который указывает b
, и объект, возвращенный из функции, на которую указывает c
».
*
in int *a;
не является оператором и не разыменовывается a
(что даже не определено)
*
(это дает лишь значение в суммарном выражении, а не в *
пределах выражения!). Там написано, что "int a;" объявляет указатель, что он делает, никогда не заявлял иначе, но без значения *
, читаемого как * значение разыменованного a
является int , все еще полностью допустимо, поскольку это имеет тот же фактический смысл. Ничто, на самом деле ничто из написанного в 6.7.6.1 не противоречит этому утверждению.
int *a;
это декларация, а не выражение. a
не разыменовывается int *a;
. a
даже не существует в тот момент, *
когда обрабатывается. Как вы думаете int *a = NULL;
, это ошибка, потому что она разыменовывает нулевой указатель?
Я собираюсь выйти на конечности здесь и сказать , что есть прямой ответ на этот вопрос , как для объявления переменных и параметров и возвращаемых типов, которые в том , что звездочка должна идти рядом с названием: int *myVariable;
. Чтобы понять почему, посмотрите, как вы объявляете другие типы символов в C:
int my_function(int arg);
для функции;
float my_array[3]
для массива.
Общий шаблон, называемый объявлением после использования , состоит в том, что тип символа разбивается на части перед именем, а также части вокруг имени, и эти части вокруг имени имитируют синтаксис, который вы использовали бы для получения значение типа слева:
int a_return_value = my_function(729);
float an_element = my_array[2];
и: int copy_of_value = *myVariable;
.
C ++ добавляет гаечный ключ в работу со ссылками, потому что синтаксис в точке, где вы используете ссылки, идентичен синтаксису типов значений, поэтому вы можете утверждать, что C ++ использует другой подход к C. С другой стороны, C ++ сохраняет тот же поведение C в случае указателей, так что ссылки действительно выделяются как странные в этом отношении.
Это просто вопрос предпочтений.
Когда вы читаете код, различать переменные и указатели легче во втором случае, но это может привести к путанице, когда вы помещаете и переменные и указатели общего типа в одну строку (что само по себе часто не рекомендуется руководящими принципами проекта, потому что уменьшается читаемость).
Я предпочитаю объявлять указатели с соответствующими знаками рядом с именем типа, например
int* pMyPointer;
Один великий гуру однажды сказал: «Прочтите это по пути компилятора».
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Конечно, это было связано с темой размещения констант, но здесь применяется то же правило.
Компилятор читает это как:
int (*a);
не как:
(int*) a;
Если вы привыкнете размещать звездочку рядом с переменной, это облегчит чтение ваших объявлений. Это также избегает глазных пятен, таких как:
int* a[10];
-- Редактировать --
Чтобы точно объяснить, что я имею в виду, когда я говорю, что он анализируется как int (*a)
, это означает, что он *
более тесно связан с a
чем int
, во многом так, как это выражается в выражении 4 + 3 * 7
3
более тесно, 7
чем с ним 4
из-за более высокого приоритета *
.
С извинениями за ascii art, краткий обзор AST для разбора int *a
выглядит примерно так:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Как ясно показано, *
связывается более тесно a
с их общим предком Declarator
, в то время как вам нужно пройти весь путь вверх по дереву, Declaration
чтобы найти общего предка, который включает в себя int
.
(int*) a
.
int
в таком случае. Шаг 2. Прочитайте декларацию, включая любые украшения типа. *a
в таком случае. Шаг 3 Прочитайте следующий символ. Если запятая, уничтожьте ее и вернитесь к шагу 2. Если точка с запятой остановится Если что-нибудь еще, сгенерируйте синтаксическую ошибку. ...
int* a, b;
и получить пару указателей. Я хочу подчеркнуть, что *
связывается с переменной и анализируется с ней, а не с типом, чтобы сформировать «базовый тип» объявления. Это также одна из причин того, что typedefs были введены для typedef int *iptr;
iptr a, b;
создания пары указателей. Используя typedef, вы можете привязать *
к int
.
Declaration-Specifier
с "украшениями" в Declarator
для получения окончательного типа для каждой переменной. Однако это не «MOVE» декорации к спецификатор декларации в противном случае int a[10], b;
будет производить совершенно нелепые результаты, он разбирает , int *a, b[10];
как int
*a
,
b[10]
;
. Нет другого способа описать это, что имеет смысл.
int*a
в конце читается компилятором как: « a
имеет тип int*
». Это было то, что я имею в виду с моим оригинальным комментарием.
Потому что это имеет больше смысла, когда у вас есть такие объявления, как:
int *a, *b;
int* a, b;
сделал b
указатель на int
. Но они этого не сделали. И не без причины. Под вашей предложенной системой, какой тип b
в следующей декларации int* a[10], b;
:?
Для объявления нескольких указателей в одной строке я предпочитаю, чтобы int* a, * b;
более интуитивно объявлять «a» как указатель на целое число и не смешивать стили, когда аналогично объявляется «b». Как кто-то сказал, я бы в любом случае не объявлял два разных типа в одном и том же утверждении.
Люди, которые предпочитают int* x;
, пытаются втиснуть свой код в вымышленный мир, где тип находится слева, а идентификатор (имя) - справа.
Я говорю «вымышленный», потому что:
В C и C ++ в общем случае объявленный идентификатор окружен информацией о типе.
Это может звучать безумно, но вы знаете, что это правда. Вот некоторые примеры:
int main(int argc, char *argv[])
означает « main
это функция, которая принимает int
массив и указателей на char
и возвращает int
». Другими словами, большая часть информации о типе находится справа. Некоторые люди думают, что объявления функций не учитываются, потому что они как-то «особенные». Хорошо, давайте попробуем переменную.
void (*fn)(int)
означает fn
, что указатель на функцию, которая принимает int
и ничего не возвращает.
int a[10]
объявляет 'a' как массив из 10 int
с.
pixel bitmap[height][width]
,
Понятно, что у меня есть несколько примеров, которые содержат много информации о типах, чтобы понять мою точку зрения. Есть много объявлений, где большинство - если не все - типа слева, например struct { int x; int y; } center
.
Этот синтаксис декларации вырос из желания K & R иметь декларации, отражающие использование. Чтение простых объявлений интуитивно понятно, а чтение более сложных можно освоить, изучая правило справа налево и право (иногда называют спиральным правилом или просто правилом справа налево).
C достаточно прост, что многие программисты на C воспринимают этот стиль и пишут простые объявления как int *p
.
В C ++ синтаксис стал немного более сложным (с классами, ссылками, шаблонами, перечисляемыми классами), и, как реакция на эту сложность, вы увидите больше усилий по отделению типа от идентификатора во многих объявлениях. Другими словами, вы можете увидеть больше int* p
объявлений -style, если вы проверяете большую часть кода C ++.
В любом языке вы всегда можете иметь тип в левой части объявлений переменных : (1) никогда не объявлять несколько переменных в одном выражении и (2) использовать typedef
s (или объявления псевдонимов, которые, как ни странно, ставят псевдоним идентификаторы слева от типов). Например:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)
указатель, fn
а не возвращаемый тип.
Я уже ответил на аналогичный вопрос в CP, и, поскольку никто не упомянул об этом, здесь я также должен указать, что C - это язык свободного формата , какой бы стиль вы ни выбрали, он подходит, в то время как анализатор может различать каждый токен. Эта особенность С приводит к особому типу соревнований, называемых С обфускацией .
Многие аргументы в этой теме носят субъективный характер, а аргумент о «звезде, связанной с именем переменной» наивен. Вот несколько аргументов, которые не просто мнения:
Забытые указатели типа указателя
Формально «звезда» не принадлежит ни типу, ни имени переменной, она является частью своего грамматического элемента с именем указатель . Формальный синтаксис C (ISO 9899: 2018):
(6.7) декларация:
Декларация спецификаторы Init-описатель-лист опт;
Где спецификатор объявления содержит тип (и хранилище), а список инициаторов объявления содержит указатель и имя переменной. Что мы увидим, если мы разберем синтаксис этого списка объявлений дальше:
(6.7.6) описатель:
указатель неавтоматического прямого описатель
...
(6.7.6) указатель:
*
тип-спецификатор-список неавтоматического
*
типа отборочные-лист неавтоматического указатель
Если декларатор - это полное объявление, прямой декларатор - это идентификатор (имя переменной), а указатель - это звезда, за которой следует необязательный список квалификаторов типов, принадлежащий самому указателю.
То, что делает различные аргументы стиля о том, что «звезда принадлежит переменной» несовместимо, так это то, что они забыли об этих квалификаторах типа указателя. int* const x
, int *const x
Или int*const x
?
Рассмотрим int *const a, b;
, какие бывают типы a
и b
? Не так очевидно, что «звезда принадлежит переменной» больше. Скорее, можно было бы задуматься о том, где он const
принадлежит.
Вы можете определенно аргументировать, что звезда относится к определителю типа указателя, но не намного дальше.
Список спецификаторов типов для указателя может вызвать проблемы у тех, кто использует int *a
стиль. Те, кто использует указатели внутри typedef
(что мы не должны делать, очень плохая практика!) И думают, что «звезда принадлежит имени переменной», как правило, пишут эту очень тонкую ошибку:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
Это компилируется чисто. Теперь вы можете подумать, что код сделан правильно. Не так! Этот код случайно является фальшивой константностью.
Тип на foo
самом деле int*const*
- самый внешний указатель был сделан только для чтения, а не указывал на данные. Таким образом, внутри этой функции мы можем сделать, **foo = n;
и она изменит значение переменной в вызывающей стороне.
Это происходит потому , что в выражении const bad_idea_t *foo
, то *
не принадлежит к имени переменной здесь! В псевдокоде это объявление параметра следует читать как, const (bad_idea_t *) foo
а не как (const bad_idea_t) *foo
. В этом случае звезда относится к скрытому типу указателя - тип является указателем, а указатель с константой записывается как *const
.
Но тогда корень проблемы в приведенном выше примере - это практика скрывать указатели за, typedef
а не *
стиль.
Относительно объявления нескольких переменных в одной строке
Объявление нескольких переменных в одной строке широко признано плохой практикой 1) . CERT-C подытоживает это так:
DCL04-С. Не объявляйте более одной переменной на объявление
Только чтение на английском языке, то здравый смысл соглашается с тем , что декларация должна быть одна декларация.
И не имеет значения, являются ли переменные указателями или нет. Объявление каждой переменной в одной строке делает код более понятным почти в каждом случае.
Так что спор о том, что программист запутался, int* a, b
плохой. Корень проблемы - использование нескольких деклараторов, а не размещение *
. Независимо от стиля, вы должны написать это:
int* a; // or int *a
int b;
Другим здравым, но субъективным аргументом было бы то, что данный int* a
тип не a
вызывает сомнений, int*
и поэтому звезда относится к определителю типа.
Но в основном мой вывод заключается в том, что многие из приведенных здесь аргументов являются лишь субъективными и наивными. Вы не можете в действительности дать веские аргументы для любого стиля - это действительно вопрос субъективных личных предпочтений.
1) CERT-C DCL04-C .
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Как великий пророк Дэн Сакс учит:« Если вы всегда располагаете const
как можно дальше вправо, не меняя смысловой смысл », это полностью прекращается. быть проблемой. Это также делает ваши const
объявления более последовательными для чтения. «Все справа от слова const - это то, что является const, все слева - это его тип». Это будет применяться ко всем константам в декларации. Попробуйте это сint const * * const x;
Рассматривать
int *x = new int();
Это не переводит на
int *x;
*x = new int();
Это на самом деле переводится как
int *x;
x = new int();
Это делает int *x
обозначения несколько непоследовательными.
new
является оператором C ++ Его вопрос помечен как «C», а не «C ++».
typedefs
, но это добавит ненужную сложность, ИМХО.