В этой заметке мы поговорим о том, что традиционно вносит наибольший сумбур в умы изучающих C++ новичков, об указателях. Но взгляним на них с немного непривычной точки зрения. Рассмотрим странный на первый взгляд вопрос: Какая из следующих записей более правильна и логична:
int* ptr; // вариант 1
int *ptr; // вариант 2
Отличаются они только расположением пробела. Должен сразу подчеркнуть, что с точки зрения компилятора оба эти варианта абсолютно првильны и полностью идентичны, так же как и «int * ptr
» и «int*ptr
». Но мы будем смотреть на них с точки зрения не программы а программиста. А человек, как мы сейчас увидим, может обнаружить в них тонкую смысловую разницу.
Вообще, стоит ли обсуждать подобные вопросы? Я убеждён, что стоит. Более того, оформлению кода я собираюсь посвятить отдельную заметку. Текст программы должен быть легко понятен не только компилятору, но и программисту. Последнее же очень сильно зависит именно от оформления. Что же касается данной темы, то она оказывается даже глубже чем просто вопрос оформления. Поэтому я и решил вынести её в отдельную заметку.
Итак, чем же отличаются записи 1 и 2? Если перевести их на нормальный человеческий язык, то первая могла бы звучать так: «ptr является указателем на переменную типа int.», а вторая: «Переменная, на которую указывает ptr имеет тип int.» Общий смысл этих фраз вроде бы одинаков. Есть указатель ptr и есть некая величина типа int, на котрую он указвает. Разница в акцентах! Первая делает акцент на сам указатель, а вторая на то, на что он указывает. Как мы сейчас увидим, эта незначительная на первый взгляд разница вполне ощутима а иногда может даже приводить к путанице.
Рассмотрим как раз такую ситуацию, где неправильно поставленная звёздочка часто вводит в заблуждение:
int* a, b; // неправильно!
Какой тип имеет переменная b? Гладя на этот код можно подумать, что это указатель на целое число. Но это неправильно! Потому что с точки зрения компилятора звёздочка относится только к переменной a, поэтому она действительно является указателем. b же – просто переменнная типа int. Поэтому, чтобы избежать недоразумений мы должны прилепить звёздочку к имени переменной-указателя:
int *a, b; // (3)
Из этой записи очевидно, что как переменная b, так и та переменная, на которую указывает a, имеют тип int. Именно так интерпретирует эту запись компилятор.
Означает ли это, что вариант 2 приведённый в начале более правильный, чем вариант 1? Подождите, всё не так просто! Вспомним, что объявить указатель, но оставить его неинициализированным – это даже не просто дурной тон, это нечто совершенно вульгарное! Потому, давайте как это принято, инициализируем наш указатель нулём:
int *ptr = 0;
Однако в таком виде эта запись выглядит довольно странно. Она слишком напоминает другую:
*ptr = 0;
Может показаться, что также как и во второй записи, ноль присвается не указателю, а тому, на что он указывает. Конечно же это абсурдно, ведь сам указатель ещё не инициализирован и указывает в никуда. В силу этой абсурдности вряд ли такая запись может ввести в заблуждение, в отличие от предыдущего примера. Тем не менее, звёздочка здесь явно относится к «int» и нельзя не признать, что вариант 1 выглядит намного более логично и естественно:
int* ptr = 0; // (4)
Итак, мы видим, что в разных ситуациях очень похожие по виду записи трактуются компилятором по-разному. В первом примере звёздочка является частью объявления переменной. В то же время, во втором примере звёздочка является частью типа переменной. Как же могло случится, что возникла такая неоднозначность?
Всё дело в том, что конструкция в первом примере, объявление переменных через запятую, досталась C++ в наследство от C. Она возникла ещё в те далёкие времена, когда программисты не воспринимали указатели как самостоятельные типы. Вторая же запись, объявление переменной с одновременной инициализацией – это уже нововведение, появившееся только в C++. И приведённые пример наглядно иллюстрируют, что изменения при переходе от C к C++, от процедурного программирования к объектно-ориентированному, были более глубокими чем просто добавление новых «фич». Объектно-ориентированный подход потребовал более внимательного отношения к типам данных и контролю за ними. Поэтому, там где программист C сказал бы: «переменная p указывает на int», программист C++ должен сказать: «переменная p является указателем на int». Таким образом, с виду почти не отличающеся записи 1 и 2 приведённые в начале символизируют собой новое и старое представления о типах данных!
Но вернёмся с небес на землю. Какой-же вариант записи всё-таки предпочтительней на практике? Как вы уже поняли, я бы рекомендовал использовать вариант 1, как более соотвествующий духу C++ и современным представлениям о типах. Что же касается объявления переменных через запятую, то я бы настоятельно не рекомендовал пользоваться такой конструкцией. И не столько из-за того, что она может вызывать недоразуменя, сколько потому, что она не позволяет сразу же инициализировать переменную. Всегда инициализировать переменные – одно из немногих правил которых стоит придерживаться жёстко, без исключений. Я не могу представить себе ситуацию, где бы оно имело хоть какой-то негативный эффект, зато во многих случаях позволяет выявить ошибку в программе намного быстрее, без многочасового сидения в отладчике. Поэтому всегда объявляйте локальные переменные по-одной сразу же присвивая им начальное значение, или просто ноль. Объвление же локальных переменных через запятую следут рассматривать лишь как средство обеспечения совместимости со старым кодом и избегать его при написании нового.
В принципе, в вопросе о звёздочках нет смысла быть абсолютно категоричным. Хотя запись 1 и является более современной, в большинстве случаев запись 2 не приведёт к недорзумениям. Поэтому, если вы привыкли ко второму варианту, можете продолжать им пользоваться.
И всё же, есть один существенный аргумент в пользу того, чтобы переучиться. Пока вы работаете один, не так важно, каким правилам вы следуете. Главное – чтобы вам самому было удобно. Но всё меняется, если вы работаете в коллективе. Разный стиль оформления кода в этом случае становится серьёзной проблемой. Поэтому, в каждой сколько-нибудь серьёзной фирме обязательно вырабатывается набор правил оформления кода, которым должны следовать все программисты. Начинающим программистам, ещё не участвовавшим в коммерческих проектах этот вопрос вообще обычно кажется несущественным. Но это не так. Из таких мелочей складывается общий стиль программирования, влияющий на эффективность работы всей команды.
Конечно, и в этом случае можно выбрать вариант номер 2, если к нему привыкло большинство. Но как вы уже поняли, я настоятельно рекомендую использовать здесь вариант 1, как более соответствующий объектно-ориентированному стилю программирования.
Я надеюсь, что данная статья помогла вам по-новому взглянуть на такую казалось бы простую и привычную вещь, как знак звёздочки, что в конечном итоге должно способствовать более глубокому пониманию концепций языка C++.
Внимание!!! Если вы хотите перепечатать эту статью или её часть на на своём сайте, в печтном издании, где либо ещё, большая просьба, согласуйте пожалуйста это с автором! Связаться со мной можно по адресу: