Блн, пока писал вопрос, походу во всём разобрался. :) Вы всё равно посмотрите пожалуйста: там мелкие вопросы всётаки остались, и я что-то неправильно понять всёравно мог. Плюс ко всему, для меня, до этого момента (надеюсь), этот раздел C++ был китайской грамотой; так что надеюсь я не один такой дурак и информация будет полезной. Ну и жалко просто.
Если баян — простите.
---
Господа хорошие, помогите познать технику разбора C++ объявлений объектов и псевдонимов. Есть где-нибудь описание этого искусства?
Страуструп кое-что прояснил в этом деле, но про технику ничего не сказал, отговорился описанием порождений грамматики. У Липпмана вообще только примеры.
Я вроде как понимаю это, но всё мне кажется что понимаю я так, как представляли слона двое слепых в известной притче.
Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один :) , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.
const int n;
n — "пустой" суффикс, объект "n"
теперь читаю слева направо
const — константный
int — типа int
Итого: константный объект n, типа int
Проверил: АГА!
таким образом, это равносильно
int const n;
Проверил: АГА!
int * ap[];
ap[] — суффикс [], массив имя "ap"
опять слева направо
int — типа int
* — указатель
Итого: массив указателей на int
Проверил: АГА!
const int * pc
pc — "пустой" суффикс, объект "pc"
слева направо
const — константный
int — типа int
* — указатель
Итого: константный указатель на int
Проверил: НЕА! Это указатель на константный int
Читаю Страуструпа, он просто говорит, что обявление это: "спецификатор, базовый тип, объявляющая часть", — а потом расписывает что есть что в объявляющей части: * — указатель *const — константный указатель (т.е. указатель, и адрес на который он указывает, изменить нельзя).
...
Значит надо разбить объявление на конструкции: "[<спецификатор>] <тип>", "*[<спецификатор>]", "&", "имя[<суффикс>]" а потом сначала смотреть, что говорит суффикс или его отсутствие, а затем слева направо читать префиксные конструкции.
таким образом:
int * ap[];
ap[] — суффикс [], массив имя "ap"
"int", "*", "ap[]" (это в первую очередь)
"ap[]" — массив, звать "ap"
"int" — тип int, без выкрутасов
* — указатель
Итого: массив указателей на int
Проверил: АГА!
const int an[];
Массив константных int.
Проверил:
const int an[] = {0,1,2};
an[1] = n; // Ошибка, константу нельзя менять.const int an1[]; // Ошибка, не инициализированная константа.
АГА!
int & *const prn;
Константный указатель на ссылку на int. -- белеберда какая-то, но...
Проверил: АГА! Компилятор сказал: "error: cannot declare pointer to `int&'", -- т.е. та белеберда которую я и думал. :)
СТОП! Как же быть с
int const n;
, у Страуструпа сказано, что спецификатор может быть только перед базовым типом! Или Страуструп что-то не договорил, и "[<спецификатор>]<тип>" <==> "<тип>[<спецификатор>]"? Или это компилятор (MinGW 5.1.4) просто разрешает?
Ну ладно, теперь со скобками: я так понимаю что, как и в выражениях, префиксы внутри скобок рассматриваются раньше всего остального, т.е. суффиксов.
Например если я хочу объявить ссылку на массив, то без скобок не обойтись. Т.к. сдесь
int an[] = {0,1,2};
int &ran[] = an;
ran разберается так:
"int", "&", "ran[]" (первым)
"ran[]" — массив, звать "ran"
"&" — ссылка
"int" — тип int
Итого: массив ссылок на int.
Проверил: АГА!
Со скобками:
int an[] = {0,1,2};
int (&ran)[] = an;
ошибка!!!
???
Ай, я массив-то сделал int[3], компилятор ж по инициализатору обо всём дотумкал. Поправил:
int an[] = {0,1,2};
int (&ran)[3] = an;
работает, а разбирается так:
"int", "&ran" (первым), "[]"
"&ran" — ссылка, звать "ran"
"[3]" — массив о трёх элементах
"int" — тип int
Итого: ссылка на массив из трёх int.
Теперь попробую извратиться
int n = 99;
int (const*) cpn = &n;
, м.б. "(const*)" будет понято как "*const", но компилятор что-то страшное выдал. Значит скобки должны в себе содержать некую корректную "объявляющую часть".
Про запятые (т.е. конструкции типа 'int k, l, * pn') я понимаю что после запятой надо мысленно вставить базовый тип с его спецификаторами из первого объявления, чтоб получить то что подразумевается в том что после запятой.
int n, * pn;
// равносильноint n; int * pn;
const int cn, & rcn = cn;
// равносильноconst int cn; const int & rcn = cn;
Проверил: АГА! Всё сходится.
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re: Техника разбора C++ объявлений или покажите мне слона.
Здравствуйте, ZAMUNDA, Вы писали:
ZAM>Господа хорошие, помогите познать технику разбора C++ объявлений объектов и псевдонимов. Есть где-нибудь описание этого искусства?
Насчет const: для компилятора X const и const X — одно и то же. Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &».
До последнего не верил в пирамиду Лебедева.
Re[2]: Техника разбора C++ объявлений или покажите мне слона
RO>Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &».
А какие у тебя в данном примере есть еще варианты расположения const, чтобы тип был одним и тем же???
А так — у меня глаза болят от int const Я левша, может поэтому Люблю const Type, а не Type const.
Of course, the code must be complete enough to compile and link.
Re[3]: Техника разбора C++ объявлений или покажите мне слона
Здравствуйте, Lorenzo_LAMAS, Вы писали:
RO>>Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &». L_L>А какие у тебя в данном примере есть еще варианты расположения const, чтобы тип был одним и тем же???
Впервые столкнулся с этим несколько лет назад, когда некто, привыкший обозначать константные ссылки как const X &, для случая X = int* написал const int * &, что внесло расхождение между тем, что он хотел, и тем, что он сказал. Но компилятор это проглотил, потому что это был MSVC.
L_L>А так — у меня глаза болят от int const :) Я левша, может поэтому :) Люблю const Type, а не Type const.
Дело привычки. Мне неприятно видеть const X, например. Заодно запись «X const& x» собирает детали реализации в одну кучу, по краям остается главное: x имеет тип X.
До последнего не верил в пирамиду Лебедева.
Re[4]: Техника разбора C++ объявлений или покажите мне слона
Здравствуйте, Roman Odaisky, Вы писали:
RO>Впервые столкнулся с этим несколько лет назад, когда некто, привыкший обозначать константные ссылки как const X &, для случая X = int* написал const int * &, что внесло расхождение между тем, что он хотел, и тем, что он сказал.
Также часто встречается путаница с typedef'ами, имеющая схожую природу. Новички иногда считают, что typedef'ы разруливаются компилятором лексически (т. е. буквальной подстановкой), а не синтаксически. Если же привыкнуть писать квалификатор const справа, то легче раскрывать typedef'ы «в уме».
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Техника разбора C++ объявлений или покажите мне слона
Поглядел, стрелки это круто! А как tab'ы вставить? :)
Ты кстати в самом красивом примере не вписал объявления типов, что запутывает (я у себя поэтому int использовал, что б понятно было); и разбирал по словам сразу, а не по "единицам объявления" ("[<спецификатор>] <тип>", "*[<спецификатор>]", "*", ...) или я непонял опять что-то.
Я б это так описал
typedef int ( & ( C :: * ( * const * X ) ( void () ) throw() ) ( char, int () ) const ) [];
// ↑ тип X -- это
// ←
// ↑ указатель на
// ↑ ↑ неизменяемое значение типа указатель на
// →
// ↑ функция
// ↑ параметр функции void()
// ↑ спецификатор функции не бросающей исключений
// ←
// ↑ возвращаемое значение функции, типа указатель на
// ↑
// ↑ член класса C
// →
// ↑ функция
// ↑ параметр функции типа char
// ↑ параметр функции типа int(),
// ↑ спецификатор функции разрешённой для вызова в неизменяющемся объкте
// ←
// ↑ возвращаемое значение функции, типа ссылка на
// →
// ↑
// ←
// ↑ ↑ массив типа int.
И ещё вопрос про "(*obj_name)( void () )" — это читается как "(*obj_name)( void smth())" я правильно понимаю, но ведь переменную типа void создать нельзя (т.е. проининциализировать). Или, что я у Страуструпа прочитал, это на самом деле объявление функции без параметров, но у тебя это место не так описано.
RO>>Я всегда пишу X const, так понятнее, к чему именно относится const в случаях вроде «X * const* &». L_L>А какие у тебя в данном примере есть еще варианты расположения const, чтобы тип был одним и тем же???
ГЫ. "Акела промахнулся". :)
L_L>А так — у меня глаза болят от int const :) Я левша, может поэтому :) Люблю const Type, а не Type const.
Я лично, согласен с RO (Roman Odaisky), "<тип> <спецификатор>" лучше, потомучто аналогично синтаксису "* <спецификатор>" и благодаря этому объявление проще читать, например:
obj_type/*+++*/const * */*+++*/const obj_name;
. А лево/право для меня всегда было пофиг, я в децтве пользовался обеими руками, потом в школе правой писать научился. :)
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re[2]: Техника разбора C++ объявлений или покажите мне слона
Я всегда > пишу X const, так понятнее, к чему именно относится const в случаях > вроде «X * const* &».
А я пишу
const X
и никогда не пишу ничего вроде «X * const* &»
Posted via RSDN NNTP Server 2.1 beta
Re: Техника разбора C++ объявлений или покажите мне слона.
От:
Аноним
Дата:
15.03.09 10:40
Оценка:
Здравствуйте, ZAMUNDA, Вы писали:
ZAM>Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.
Читать декларации в C/С++ надо читать справа налево. Так гораздо проще. Посмотрим на твоих примерах.
ZAM>
const int n;
n — "пустой" суффикс, объект "n" ZAM>теперь читаю слева направо
Читаем справа налево.
Объявляется объект с именем n, имеющий тип инт, константный.
ZAM>
int * ap[];
ap[] — суффикс [], массив имя "ap" ZAM>опять слева направо
Опять справа налево.
Объявление массива (скобочки) с именем ap, указателей на int.
ZAM>
const int * pc
pc — "пустой" суффикс, объект "pc"
Указатель на константный (неизменяемый) int (нельзя изменять то, на что указывает pc).
ZAM>Читаю Страуструпа, он просто говорит, что обявление это: "спецификатор, базовый тип, объявляющая часть", — а потом расписывает что есть что в объявляющей части: ZAM>* — указатель ZAM>*const — константный указатель (т.е. указатель, и адрес на который он указывает, изменить нельзя).
Что значит "нельзя изменить адрес"? Адрес он просто есть. Нельзя изменить значение указателя (которое и содержит адрес).
Re[2]: Техника разбора C++ объявлений или покажите мне слона
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, ZAMUNDA, Вы писали:
ZAM>>Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.
А>Читать декларации в C/С++ надо читать справа налево. Так гораздо проще. Посмотрим на твоих примерах.
К сожалению, такой способ мало пригоден на сложных рекурсивных деклараторах. Тут уже придется либо метод "вправо-влево" (или как там его), или просто разобраться с грамматикой.
Of course, the code must be complete enough to compile and link.
Re[3]: Техника разбора C++ объявлений или покажите мне слона
Здравствуйте, Lorenzo_LAMAS, Вы писали:
ZAM>>>Вот например, объявление переменной (как я понимаю): сначала надо смотреть на суффикс, он самый главный: <пустой> — объект обыкновенный один :) , [] — массив, (T) — функция, где T -> объявления_параметров, потом слева направо читать спецификаторы, имя типа и префиксы.
А>>Читать декларации в C/С++ надо читать справа налево. Так гораздо проще. Посмотрим на твоих примерах.
Согласен, и в таком случае, прошу переопределить моё ощущение от грамматики C++ декларакций из "китайская грамота" в "арабский манускрипт". :-)
L_L>К сожалению, такой способ мало пригоден на сложных рекурсивных деклараторах. Тут уже придется либо метод "вправо-влево" (или как там его), или просто разобраться с грамматикой.
Вот собсно "разобраться с грамматикой" и небыло особо трудно, трудность там только то, что круглые скобки являются и частью лексемы "параметры функции", и группирующими символами (хотя "группа лексем" это тож лексема). Если я, конечно, правильно понимаю что такое лексема. Ну и ещё к чему относить const и т.п., пожалуй.
Первая проблема была в том, что читать надо справа-налево и про это ни у Липпмана, ни у Страуструпа не написано. У Roman_Odaisky написано но как-то мозговыворачивательно, к тому моменту когда он об этом говорит твой мозг уже перегрелся информацией про поиски идентификатора, открывающие/закрывающие скобки и куда/когда повернуть.
Вторая проблема была в наличии псевдонимов для всего и функционального стиля инициализации переменной. Классический пример: "xxx yyy(zzz);"
typedef int xxx;
typedef char * zzz;
xxx yyy(zzz); // функция yyy, принимающая char *, возвращающая int;
typedef int xxx;
const int zzz = 777;
xxx yyy(zzz); // yyy типа int, инициализируемая 777;
Так что выискивание НЕ имени типа в объявлении, чтоб считать его идентификатором и от него начать читать декларацию не помогает. Надо сначала разобрать скобки, не забывая про "параметры функции", а потом в самой вложеной группе справа искать идентификатор, которого может и не быть. А ещё проще сначала посмотреть декларации всех имён, встречающихся в декларации, а потом уже приступать к разбору.
Таким образом, система получается не налево-направо, а вниз-налево. Т.е. вниз по дереву вложенности и налево по одному уровню вложенности.
char ( & f () ) [N];
Сначала скобки:
char ( & f () ) [N];
------------------- 0
---------- 1
-- параметры функции и распознать это -- самое сложное
А теперь по группам вниз (т.е. начиная с самой вложеной), и каждую налево (справа-налево)
1 — группа_0
& f ()
^^^^ функция, имя "f",
^ возвращаемый параметр функции, ссылка на
0 — группа_1 (внешняя)
char ( & f () ) [N]
char {группа_1} [N]
^^^^^^^^^^ (сначала "шаг вниз") возвращаемый параметр функции, ссылка на.
^^^ массив, размера N,
^^^^ тип char
И всё вместе: "функция, имя "f", возвращаемый параметр функции, ссылка на массив, размера N, тип char", -- вроде по русски
Короче, если так посмотреть, то в своих предыдущих сообщениях я так до правды не дошёл. Надеюсь хоть тут смог, поправьте если косяк.
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re[4]: Техника разбора C++ объявлений или покажите мне слона
Ой много буков! Ой не асилил! Если для простоты взять объявления С, то сначала ты, двигаясь слева-направо, ищещь нужный тебе идентификатор, он будет _первым_ идентификатором, который не может входить в declarator-specifiers. А дальше можно уже и технику вправо-влево использовать. Для разбора типов параметров может оказаться чуть сложнее, т.к. там может не быть имени, а только тип (и будет то, что вроде как называется абстрактный объявитель в терминологии С). Но тоже не сложно.
Of course, the code must be complete enough to compile and link.
Re[5]: Техника разбора C++ объявлений или покажите мне слона
Здравствуйте, Lorenzo_LAMAS, Вы писали:
L_L>Ой много буков! Ой не асилил!
Сорь, весна понимаешь — вдохновение попёрла.
Ну просто три сложности:
1. Читать надо справа-налево.
2. (smth) — может быть и скобками и параметрами функции.
3. Любое, НЕ ключевое слово может быть и именем типа, и именем объекта, и объявляемым (т.е. ещё не быть ващще)
L_L>Если для простоты взять объявления С, то сначала ты, двигаясь слева-направо, ищещь нужный тебе идентификатор, он будет _первым_ идентификатором, который не может входить в declarator-specifiers.
Ну уж нинаю как в C, а в C++ фигушки. #2 сложность это не даёт, т.к. первыми тебе могут встретиться параметры функции, а не видя начала (слева) объявления их иногда не отличить от объявляемого идентификатора. Пример надо?
Поэтому я сначала справа-налево разбираю скобки, а дальше каждую скобку налево, начиная с самой вложеной.
L_L>Для разбора типов параметров может оказаться чуть сложнее, т.к. там может не быть имени, а только тип (и будет то, что вроде как называется абстрактный объявитель в терминологии С). Но тоже не сложно.
Ага, и поэтому я сначала смотрю на скобки.
шикарно всё описал, и я чесна ляпнул ему оценку. Просто его способ для меня слишком трудный. Я выработал свой и, собсно, его и описал — ну на случай если я не один дурак такой. Вопрос если и был, то только один: "Я правильно всё понимаю?".
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Здравствуйте, ZAMUNDA, Вы писали:
ZAM>Я вроде как понимаю это, но всё мне кажется что понимаю я так, как представляли слона двое слепых в известной притче.
Есть такое правило для С оно восходит вроде как к K&R.
Правило такое: "каждый идентификатор декларируется так же, как и используется".
Например.
int i; // i -- такое выражение будет иметь тип intint ** i; // **i -- будет иметь тип int, *i, тип "указатель на int", ну и т.д.int i[5]; // i[число] будет иметь тип int, ну и так далее
Соответсвенно, расстановка скобок тоже укладывается в эту всю штуку, так как "приоритеты" те же.
Например
int (*f)( int ); // выражение (*f)( число ) будет иметь тип int, ну и далее раскручиваем
Ну а в С++ появилось несколько неукладывающихся в это правило конструкций. Это ссылки, спецификации исключений и CV-квалификаторы методов.
Зато в рамках этой концепции очень просто понять на что действуют CV-квалификаторы. Они всегда действуют на выражениее СПРАВА от них. То есть
int **const** i; // выражение **i будет представлять из себя сонстантное lvalue
Как-то так. Те, кто изучает С++ при моём участии, обычно рады узнать такое мнемоническое правило... Может и кому ещё пригодится...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском