Ответ 1
Хороший вопрос, со сложным ответом. Чтобы понять это, вам нужно полностью понять внутреннюю структуру объявлений на С++.
(Обратите внимание, что в этом ответе я полностью опускаю существование атрибутов, чтобы предотвратить чрезмерное применение).
Объявление имеет два компонента: последовательность спецификаторов, за которыми следует разделенный запятыми список инициаторов-деклараторов.
Спецификаторы:
- спецификаторы класса хранения (например,
static
,extern
) - спецификаторы функций (например,
virtual
,inline
) -
friend
,typedef
,constexpr
- спецификаторы типов, которые включают:
- спецификаторы простого типа (например,
int
,short
) - cv-qualifiers (
const
,volatile
) - другие вещи (например,
decltype
)
- спецификаторы простого типа (например,
Вторая часть объявления представляет собой разделяемые запятой init-declarators. Каждый init-declarator состоит из последовательности деклараторов, необязательно сопровождаемых инициализатором.
Какие деклараторы:
- (например,
i
inint i;
) - операторы, подобные указателям (
*
,&
,&&
, синтаксис указателя на элемент) - синтаксис параметра функции (например,
(int, char)
) - синтаксис массива (например,
[2][3]
) - cv-qualifiers, если они следуют за указателем указателя.
Обратите внимание, что структура декларации строгая: сначала спецификаторы, а затем init-declarators (каждый из которых является деклараторами, а затем инициатором).
Это правило: спецификаторы применяются ко всему объявлению, а деклараторы применяются только к одному init-declarator (к одному элементу списка, разделенного запятыми).
Также обратите внимание на то, что cv-квалификатор может использоваться как спецификатор, так и декларатор. В качестве декларатора грамматика ограничивает их использование только при наличии указателей.
Итак, для обработки четырех объявлений, которые вы разместили:
1
int i = 0, *const p = &i;
Часть спецификатора содержит только один спецификатор: int
. Это та часть, к которой будут применяться все деклараторы.
Есть два init-декларатора: i = 0
и * const p = &i
.
Первый имеет один декларатор i
и инициализатор = 0
. Поскольку не существует декларатора модификации типов, тип i
задается спецификаторами int
в этом случае.
Второй init-declarator имеет три объявления: *
, const
и p
. И инициализатор = &i
.
Объявители *
и const
изменяют базовый тип на "постоянный указатель на базовый тип". Базовый тип, заданный спецификаторами, равен int
, типу p
будет "постоянный указатель на int
".
2
int j = 0, const c = 2;
Опять же, один спецификатор: int
и два init-declarators: j = 0
и const c = 2
.
Для второго init-declarator деклараторы const
и c
. Как я уже упоминал, грамматика разрешает только cv-qualifiers как деклараторы, если есть указатель. Это не так, следовательно, ошибка.
3
int *const p1 = nullptr, i1 = 0;
Один спецификатор: int
, два init-declarators: * const p1 = nullptr
и i1 = 0
.
Для первого init-declarator объявления: *
, const
и p1
. Мы уже рассматривали такой init-declarator (второй в случае 1). Он добавляет "постоянный указатель на базовый тип" к определяемому спецификатором базовому типу (который все еще int
).
Для второго init-declarator i1 = 0
это очевидно. Нет модификаций типа, используйте спецификаторы (-и) как есть. Таким образом, i1
становится int
.
4
int const j1 = 0, c1 = 2;
Здесь мы имеем принципиально другую ситуацию из предыдущих трех. У нас есть два спецификатора: int
и const
. А затем два init-declarators, j1 = 0
и c1 = 2
.
Ни один из этих инициаторов инициализации не имеет в них деклараторов, изменяющих тип, поэтому они оба используют тип из спецификаторов, который равен const int
.