Почему звездочка перед именем переменной, а не после типа?

Почему большинство программистов на C называют такие переменные:

int *myVariable;

а не так:

int* myVariable;

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

Ответы

Ответ 1

Они ТОЧНО эквивалентны. Однако в

int *myVariable, myVariable2;

Кажется очевидным, что myVariable имеет тип int *, а myVariable2 имеет тип int. В

int* myVariable, myVariable2;

может показаться очевидным, что оба имеют тип int *, но это неверно, поскольку myVariable2 имеет тип int.

Следовательно, первый стиль программирования более интуитивно понятен.

Ответ 2

Если вы посмотрите на это по-другому, *myVariable имеет тип int, что имеет смысл.

Ответ 3

Потому что * в этой строке привязывается ближе к переменной, чем к типу:

int* varA, varB; // This is misleading

Как @Lundin указывает ниже, const добавляет еще больше тонкостей для размышлений. Вы можете полностью обойти это, объявив одну переменную на строку, что никогда не бывает двусмысленным:

int* varA;
int varB;

Трудно установить баланс между чистым кодом и кратким кодом - дюжина избыточных строк int a; тоже не хорошо Тем не менее, я по умолчанию одно объявление на строку и беспокоюсь о комбинировании кода позже.

Ответ 4

Что-то, о чем никто не упоминал до сих пор, состоит в том, что эта звездочка на самом деле является "оператором разыменования" в 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; this является косвенным доступом (* означает косвенный доступ), и, следовательно, второй вышеприведенный оператор намного ближе к стандарту, чем первый.

Ответ 5

Я собираюсь выйти на конечность здесь и сказать, что есть прямой ответ на этот вопрос, как для объявлений переменных, так и для типов параметров и возвратов, которые означают, что звездочка должна идти рядом с именем: int *myVariable;. Чтобы понять, почему, посмотрите, как вы объявляете другие типы символов в C:

int my_function(int arg); для функции;

float my_array[3] для массива.

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

int a_return_value = my_function(729);

float an_element = my_array[2];

и: int copy_of_value = *myVariable;.

С++ запускает гаечный ключ в работах со ссылками, потому что синтаксис в точке, где вы используете ссылки, идентичен синтаксису типов значений, поэтому вы можете утверждать, что С++ использует другой подход к C. С другой стороны, С++ сохраняет то же поведение C в случае указателей, поэтому ссылки действительно стоят как нечетные в этом отношении.

Ответ 6

Это просто вопрос предпочтения.

Когда вы читаете код, различие между переменными и указателями проще во втором случае, но это может привести к путанице, когда вы помещаете обе переменные и указатели общего типа в одну строку (что само часто не поощряется руководящие принципы проекта, поскольку уменьшает читаемость).

Я предпочитаю объявлять указатели со своим соответствующим значком рядом с именем типа, например.

int* pMyPointer;

Ответ 7

В K & R они помещают оператор указателя рядом с именем переменной, а не типом. Лично я считаю, что книга является высшим авторитетом C/библией. Также, исходя из фона Objective-C, я следую за соглашением *.

Ответ 8

Один великий гуру однажды сказал: "Прочтите это по пути компилятора".

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.

Ответ 9

Потому что это имеет смысл, когда у вас есть объявления вроде:

int *a, *b;

Ответ 10

Для объявления нескольких указателей в одной строке я предпочитаю int* a, * b;, который более интуитивно объявляет "a" как указатель на целое число и не смешивает стили, также объявляя "b". Как и кто-то сказал, я бы не объявлял два разных типа в одном и том же заявлении.

Ответ 11

Люди, которые предпочитают 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 s.

  • pixel bitmap[height][width].

  • Понятно, что у меня есть несколько примеров, которые содержат много информации о типах, чтобы понять мою точку зрения. Есть много объявлений, где большинство - если не все - типов слева, например struct { int x; int y; } center struct { int x; int y; } center struct { int x; int y; } center.

Этот синтаксис декларации вырос из желания K & R иметь декларации, отражающие использование. Чтение простых объявлений интуитивно понятно, а чтение более сложных можно освоить, изучая правило справа налево и право (иногда называют спиральным правилом или просто правилом справа налево).

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

В C++ синтаксис стал немного более сложным (с классами, ссылками, шаблонами, перечисляемыми классами), и, как реакция на эту сложность, вы увидите больше усилий по отделению типа от идентификатора во многих объявлениях. Другими словами, вы можете увидеть больше объявлений типа int* p -style, если вы посмотрите большую часть кода C++.

В любом языке вы всегда можете иметь тип в левой части объявлений переменных: (1) никогда не объявлять несколько переменных в одном выражении и (2) использовать typedef (или объявления псевдонимов, которые, как ни странно, помещают псевдоним идентификаторы слева от типов). Например:

typedef int array_of_10_ints[10];
array_of_10_ints a;

Ответ 12

Я уже ответил на аналогичный вопрос в CP, и, поскольку никто не упоминал об этом, также здесь я должен указать, что C - это язык свободного формата, любой стиль, который вы выбираете, анализатор может различать каждый токен. Эта особенность C приводит к особому типу конкурса, названного C obfuscation contest.

Ответ 13

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


Забытые указатели типа указателя

Формально "звезда" не принадлежит ни типу, ни имени переменной, она является частью своего грамматического элемента с именем указатель. Формальный синтаксис C (ISO 9899: 2018):

(6.7) декларация:
спецификаторы объявлений init-декларатор-список opt;

Где спецификатор объявления содержит тип (и хранилище), а список инициаторов объявления содержит указатель и имя переменной. Что мы увидим, если мы разберем синтаксис этого списка объявлений дальше:

(6.7.6) декларатор:
указатель опт прямой декларатор
...
(6.7.6) указатель:
* type-qualifier-list opt
* указатель выбора списка спецификаторов типов

Если декларатор - это полное объявление, прямой декларатор - это идентификатор (имя переменной), а указатель - это звезда, за которой следует необязательный список квалификаторов типов, принадлежащий самому указателю.

То, что делает различные аргументы стиля о том, что "звезда принадлежит переменной" противоречивы, это то, что они забыли об этих квалификаторах типа указателя. int* const x, int *const x или int*const x?

Рассмотрим int *const a, b; Какие бывают типы a и b? Не так очевидно, что "звезда принадлежит переменной" больше. Скорее, можно было бы задуматься о том, к const принадлежит 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.