Почему звездочка перед именем переменной, а не после типа?
Почему большинство программистов на C называют переменные так:
int *myVariable;
а не так:
int* myVariable;
Оба действительны. Мне кажется, что звездочка является частью типа, а не частью имени переменной. Кто-нибудь может объяснить эту логику?
16 ответов
Они ТОЧНО эквивалентны. Однако в
int *myVariable, myVariable2;
Кажется очевидным, что myVariable имеет тип int *, а myVariable2 имеет тип int. В
int* myVariable, myVariable2;
может показаться очевидным, что оба типа int *, но это не так myVariable2
имеет тип int.
Поэтому первый стиль программирования более интуитивен.
Если вы посмотрите на это по-другому, *myVariable
имеет тип int
, что имеет какой-то смысл.
Кое-что еще никто не упомянул здесь, это то, что эта звездочка на самом деле является " оператором разыменования " в 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* varA, varB; // This is misleading
Однако даже лучшие однострочные объявления кажутся мне нелогичными, потому что * является частью типа. Мне нравится делать это вместо этого:
int* varA;
int varB;
Как обычно, чем меньше компактный код, тем он более читабелен.;)
Я собираюсь выйти здесь на конечность и сказать, что на этот вопрос есть прямой ответ, как для объявлений переменных, так и для параметров и типов возврата, то есть звездочка должна идти рядом с именем: 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 в случае указателей, так что ссылки действительно выделяются в этом отношении как нечетные.
Один великий гуру однажды сказал: "Прочтите это по пути компилятора".
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Конечно, это было связано с темой размещения констант, но здесь применяется то же правило.
Компилятор читает это как:
int (*a);
не как:
(int*) a;
Если вы привыкнете размещать звездочку рядом с переменной, это облегчит чтение ваших объявлений. Это также избегает глазных пятен, таких как:
int* a[10];
Люди, которые предпочитают int* x;
пытаются втиснуть свой код в вымышленный мир, где тип находится слева, а идентификатор (имя) - справа.
Я говорю "вымышленный", потому что:
В C и C++ в общем случае объявленный идентификатор окружен информацией о типе.
Это может звучать безумно, но вы знаете, что это правда. Вот некоторые примеры:
int main(int argc, char *argv[])
означает "main
это функция, которая принимаетint
и массив указателей наchar
и возвращаетint
"Другими словами, большая часть информации о типах находится справа. Некоторые люди думают, что объявления функций не учитываются, потому что они как-то" особенные ". Хорошо, давайте попробуем переменную.void (*fn)(int)
означаетfn
это указатель на функцию, которая принимаетint
и ничего не возвращает.int a[10]
объявляет 'а' как массив из 10int
s.pixel bitmap[height][width]
,Понятно, что у меня есть несколько примеров, которые содержат много информации о типах, чтобы понять мою точку зрения. Есть много объявлений, где большинство - если не все - типа слева, например
struct { int x; int y; } center
,
Этот синтаксис декларации вырос из желания K&R иметь декларации, отражающие использование. Чтение простых объявлений интуитивно понятно, а чтение более сложных можно освоить, изучая правило справа налево и право (иногда называют спиральным правилом или просто правилом справа налево).
C достаточно прост, что многие программисты на C воспринимают этот стиль и пишут простые объявления как int *p
,
В C++ синтаксис стал немного более сложным (с классами, ссылками, шаблонами, перечислимыми классами), и, как реакция на эту сложность, вы увидите больше усилий по отделению типа от идентификатора во многих объявлениях. Другими словами, вы можете увидеть больше int* p
декларации стиля, если вы проверяете большую часть кода C++.
В любом языке вы всегда можете иметь тип в левой части объявлений переменных: (1) никогда не объявлять несколько переменных в одном выражении и (2) использовать typedef
s (или объявления псевдонимов, которые, по иронии судьбы, помещают идентификаторы псевдонимов слева от типов). Например:
typedef int array_of_10_ints[10];
array_of_10_ints a;
Это просто вопрос предпочтений.
Когда вы читаете код, во втором случае легче различать переменные и указатели, но это может привести к путанице, когда вы помещаете в одну строку как переменные, так и указатели общего типа (что само по себе часто не рекомендуется руководящими принципами проекта, потому что уменьшается читабельность).
Я предпочитаю объявлять указатели с соответствующими знаками рядом с именем типа, например
int* pMyPointer;
Многие аргументы в этой теме носят субъективный характер, а аргумент о "звезде, связанной с именем переменной" наивен. Вот несколько аргументов, которые не просто мнения:
Забытые указатели типа указателя
Формально "звезда" не принадлежит ни типу, ни имени переменной, она является частью своего грамматического элемента с именем указатель. Формальный синтаксис C (ISO 9899:2018):
(6.7) декларация:
спецификаторы объявления;
Где спецификатор объявления содержит тип (и хранилище), а список инициаторов объявления содержит указатель и имя переменной. Что мы увидим, если мы разберем синтаксис этого списка объявлений дальше:
(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.
В K&R они помещают оператор указателя рядом с именем переменной, а не с типом. Лично я считаю, что эта книга является высшим авторитетом в Библии. Также, исходя из опыта Objective-C, я следую соглашению *name.
Потому что это имеет больше смысла, когда у вас есть такие объявления, как:
int *a, *b;
Когда вы инициализируете и назначаете переменную в одном выражении, например
int *a = xyz;
вы присваиваете значение
xyz
к
a
, не к
*a
. Это делает
int* a = xyz;
более последовательное обозначение.
Для объявления нескольких указателей в одной строке я предпочитаю int* a, * b;
который более интуитивно объявляет "a" как указатель на целое число, и не смешивает стили при аналогичном объявлении "b". Как кто-то сказал, я бы не объявлял два разных типа в одном и том же утверждении.
Я уже ответил на аналогичный вопрос в CP, и, поскольку никто не упомянул об этом, здесь я также должен указать, что C - это язык свободного формата, какой бы стиль вы ни выбрали, он подходит, в то время как анализатор может различать каждый токен. Эта особенность С приводит к особому типу соревнований, называемых С обфускацией.
Я предпочитаю ставить звездочку*
рядом с именем типа, например:
someType* somePtr;
Этот стиль соответствует концепции, согласно которой звездочка является частью спецификатора типа, давая понять, чтоsomePtr
это указатель на типsomeType
. Я считаю его более интуитивным, поскольку он подчеркивает тип указателя.
Однако при разыменовании указателя или получении адреса переменной я предпочитаю размещать звездочку рядом с именем переменной:
int x = 42;
int* ptr = &x;
int value = *ptr;
Эта практика помогает различать объявления указателей и разыменование указателя или получение адреса. Он поясняет, когда вы объявляете указатель и когда используете его для доступа или изменения значения, на которое указывает указатель.
Аналогично, когда я объявляю ссылки, я размещаю&
рядом с именем переменной, например:
int& ref = x;
Рассмотреть возможность
int *x = new int();
Это не означает
int *x;
*x = new int();
Это фактически переводится как
int *x;
x = new int();
Это делает int *x
Обозначения несколько противоречивы.