Куда лепить знак указателя в C++?

29.10.2009
Новиков Максим Глебович

 

В процессе изучения программирования в Линуксе, мне пришлось вплотную заняться языком C++. И тут практически сразу же передо мной снова возник вопрос синтаксиса знака указателя (*). В многочисленных примерах в различных книгах по языку его лепят то к типу, то к имени переменной, то вообще ставят посередине через пробелы. Такое положение дел меня не устраивало, и я решил чётко определиться с этим вопросом. В итоге родилась статья, которую я привожу ниже.

Тип «указатель на…»

Наиболее трудным для понимания в C++ оказываются указатели, создание ссылок и взятие адреса. Это происходит из-за того, что оператор указателя (*) как части типа и создания ссылки (&) употребляется разными программистами и в разных случаях по разному (то вплотную к имени типа, то вплотную к имени переменной, то вообще с пробелами с обеих сторон — и это синтаксически допустимо); оператор взятия адреса (&) пытаются логически связать с оператором создания ссылки (&); указатель как часть типа смешивают с указателем как командой доступа к значению, на которое указывает указатель (разыменовывание); поэтому всё это не укладывается в голове в общую систему. Однако, если чётко определить стиль использования этих операторов, а также различать разные, но похожие по написанию операторы в зависимости от места их употребления, то всё встаёт на свои места.

Итак, оператор указателя в объявлениях следует писать вплотную к типам, потому что он является частью этого типа:

int* a; //Это переменная «а» типа «указатель на int»
int* func(int*, float*); //Объявление функции с параметрами 
                         //типа «указатель на int» и «указатель на float», 
                         //которая возвращает значение типа «указатель на int».

Тут следует заметить, что компилятор, видимо, рассматривает строку «int* a;» не как переменную «a» типа «указатель на int», а как переменную указателя «a» на тип «int», и с этой точки зрения следовало бы писать «int *a». На это указывает результат одновременного объявления двух переменных:

int* a, b; //Это переменная «а» типа «указатель на int»
//и переменная «b» типа «int», //а не две переменные типа «указатель на int», как казалось бы.

Но тогда становятся непонятными типы параметров в объявлении функций, где символ указателя используется без имени переменной. Рождаются какие-то новые сущности, не вписывающиеся в логику типов и только запутывающие программистов. Кроме того, при таком объявлении переменной (int *a), вместо образа некоего типа и образа обычной переменной в голове приходится представлять образ типа и образ указателя на переменную этого типа, что, согласитесь, сложнее. Вдобавок, инициализация указателя «int *a = 0» выглядит, как присвоение нуля значению, на которое указывает указатель, а не самому указателю, тогда как «int* a = 0» отражает фактическое положение дел, а именно присваивание ноля самой переменной «a» (инициализация указателя нулём).

Поэтому я предлагаю не засорять голову, а просто запомнить в виде исключения эту особенность множественного объявления переменных типа «указатель на…», которые следует объявлять так:

int *a, *b; //Это переменные указателей «а» и «b» на тип «int»

Обращение же к значению, на которое указывает указатель, берётся так:

*a; //Значение, на которое указывает указатель «а»

Здесь символ «*» является не частью типа, а командой обращения по адресу, содержащемуся в переменной «a». Поэтому надо различать указатель как часть типа, и указатель, как команду разыменовывания — это две разные вещи.

[Вернуться в начало]

Тип «ссылка на…»

Оператор создания ссылки также пишется вплотную к типу, поскольку он является как-бы частью типа «ссылка на…»:

int& b = a;   //Создание ссылки на переменную «a». 
//Объявляется «b» типа «ссылка на int», //которая становится псевдонимом переменной «a».
int*& b = a;  //Создание ссылки на указатель «a». 
//Объявляется «b» типа «ссылка на указатель на int», //которая становится псевдонимом указателя «a».
func(int&, float&); //Объявление функции с параметрами 
//типа «ссылка на int» и «ссылка на float».
[Вернуться в начало]

Оператор взятия адреса

Существует ещё оператор взятия адреса, который выглядет также (&), но выполняет совсем другую функцию, а именно, функцию, обратную указателю:

&a; //Адрес, по которому располагается значение переменной «a»

Оператор ссылки и оператор взятия адреса хотя и пишутся одинаково, но фактически это разные операторы. Оператор ссылки (int& b = a) всегда является частью типа, тогда как оператор взятия адреса (&a) всегда используется вплотную к переменной, и просто возвращает адрес, по которому её значение расположено в памяти. Несмотря на внешнюю схожесть эти операторы нельзя смешивать логически, и пытаться понять сущность оператора ссылки исходя из знаний об операторе взятия адреса (иначе логичнее было бы создавать ссылку так: «int& b = &a», что не соответствует истине).

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

[Вернуться в начало]

Послесловие

Через несколько дней после написания этой статьи я наткнулся на сайт Алексея Курзенкова, профессионального программиста (кстати, он тоже 1970 года рождения), и его заметку ещё двухлетней давности «Где поставить звёздочку?»:

http://www.sofmos.com/lyosha/Articles/CNotes_Asterisk.html

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

Если я ещё кого-то не убедил своей статьёй, предлагаю к ознакомлению книгу от создателя языка C++ Бьерна Страуструпа «Язык программирования C++»:

http://www.proklondike.com/books/cpp/straustrup_cpp.html

Думаю, более авторитетного автора по данной теме просто не найти! :))) Скачайте её (она в формате pdf) и откройте на странице 53. Глава 2.3.5 Указатели. Думаю, вы будете удивлены.

Кроме того, для общего развития и понимания причин существования в C++ вышеописонных мной исключений вроде множественного объявления указателей и т.п. можно почитать книгу того же автора «Дизайн и эволюция C++», где он описывает процесс создания языка C++:

http://www.proklondike.com/books/cpp/cpp_cpp_straustrup_desing_evolution_cpp.html.

[Вернуться в начало]
[Оставить отзыв в гостевой]
Hosted by uCoz