Тема 4 Указатели и ссылки
Все языки программирования используют указатели. Однако некоторые языки используют их «скрытно», т.е. они недоступны программистам.
На заре программирования программисты управляли данными в памяти компьютера, непосредственно определяя адрес памяти, содержащий данные. Можно легко представить, что непосредственная манипуляция памятью была делом чрезвычайно сложным. Поэтому сообщество разработчиков программного обеспечения приняло решение разработать языки программирования, в которых будут использоваться имена для ячеек памяти (или переменные), а компилятор или интерпретатор будут «заботиться» об отображении имен переменных в их адреса памяти. Фактически, особенность программирования, которую мы рассматриваем сейчас как нечто само собой разумеющееся, забрала из рук программиста возможность «хозяйничать» в памяти (довольно деликатное дело). Но в языке С++ такая возможность программисту легко доступна благодаря указателям.
Объявление, инициализация и использование указателей
|
|
Итак, имя переменной ассоциируется с ее адресом и типом данных. Эта связь позволяет компилятору или интерпретатору, по существу, иметь доступ к памяти компьютера и интерпретировать данные. Кроме того, имя функции связано с адресом, по которому компилятор или интерпретатор располагает загрузочный код функции.
Указатель – это специальная переменная, в которой хранится адрес другой переменной или объекта.
Знание адреса переменной позволяет получать доступ к данным в такой переменной через указатель. Для правильной интерпретации значения переменной указатель должен имеет тип, соответствующий типу этих данных. Иными словами, тип указателя должен совпадать с типом переменной, на которую он указывает.
Прежде чем использовать указатель, его нужно объявить, как и обычную переменную. Общий синтаксис для объявления указателя:
Тип * ИмяУказателя;
Тип * ИмяУказателя; // РЕКОМЕНДУЕТСЯ!
Символ * можно размещать одним из двух способов – либо сразу после типа указателя, либо сразу перед именем указателя.
При объявлении нескольких указателей символ * должен быть перед именем каждого указателя, например:
int *pX, *pY; // улучшает читабельность программы
А в этом примере: int* pX, pY; объявлен указатель pX на тип int и обычная целочисленная переменная с именем pY.
В качестве типа указателя можно использовать также тип void*, что обеспечивает доступ к данным любого типа. Чаще всего этот такой тип используется, когда указатель является параметром функции. При этом для того чтобы получить доступ к этим данным, необходимо преобразовать тип указателя к соответствующему типу данных, отличному от void. Кроме того, С++ не поддерживает арифметику с указателями void*, поскольку размер данных не указан. Таким образом, с одной стороны указатели типа void* – это расширение возможностей использования указателей (функции становятся более универсальными), а с другой – это ограничение возможностей использования арифметики указателей.
|
|
ЗАМЕЧАНИЕ. Поскольку указатели хранят адреса ячеек памяти компьютера, нужно обязательно правильно инициализировать указатели, прежде чем использовать их. Необходимо строго соблюдать это правило во избежание серьёзных проблем с компьютером!
Если присвоить указателю нулевой адрес:
pX = 0; // или pX = NULL;
то он никуда конкретно не указывает. Необходимо явно устанавливать указатели в нулевые адреса, прежде чем сравнивать их адреса с нулем. Некоторые функции, такие как оператор new,который выделяет динамическую память, возвращают нулевые адреса, если они завершают свои операции с указателями аварийно.
Указатели могут быть установлены на существующие переменные. Общий синтаксис для объявления указателя и инициализации его адресом переменной:
Тип * ИмяУказателя = & ИмяПеременой;
Рассмотрим следующий пример:
int A = 18;
// 1-й способ: объявление и инициализация указателя
int *pA = &A; // int *pA = A; неправильно!
// 2-й способ: отдельное объявление и отдельная инициализация указателя
int *pA;
pA = &A;
Как только адрес существующей переменной присвоен указателю, можно получить доступ к значению этой переменной. Для доступа к значению переменной через указатель используется операция разыменования (разадресации) указателя:
* ИмяУказателя |
Она дает значение по адресу. Примеры:
int A = 18, B = 36;
int *pA = &A;
printf(“A = %d”, *pA); // будет напечатано 18
*pA = 45;
printf(“A = %d”, A); // будет напечатано 45
Примеры правильного и неправильного использования указателей:
*pA = A; или *pA = 10; // значение = значение
pA = &A; // указатель = адрес (присвоить указателю адрес)
pA = A; или pA = 10; // ОШИБКА! указатель = значение
*pA = &A; // ОШИБКА! значение = указатель
ЗАМЕЧАНИЕ. Нет никакой необходимости использовать указатель на переменную, если известно имя переменной. Поэтому указатели в основном используются для передачи параметров функциям и для работы с динамической памятью компьютера.
Ссылки
Ссылки появились в языке С++ позднее, чтобы упростить работу с указателями. По сути, ссылка – это тоже указатель, только неявный. Ссылка – это псевдоним (второе имя) существующей переменной. Работа со ссылкой похожа на работу с обычной переменной. Общий синтаксис для объявления ссылки:
Тип & ИмяСсылки = ИмяПеременной;
Тип & ИмяСсылки = ИмяПеременной; // РЕКОМЕНДУЕТСЯ!
Знак & в данном случае не означает операцию взятия адреса. Если он используется при объявлении переменной, то это означает объявление переменной-ссылки.
Ссылка должна обязательно инициализироваться при объявлении. Нельзя объявить ссылку, не указав, на какую переменную она ссылается.
Исключением является ссылка, используемая в качестве параметра функции. Параметр-ссылка может быть просто объявлен, а его инициализация произойдет в момент вызова функции.
В следующем примере объявлены переменные A и B типа int, а также ссылка pA на переменную A. Это означает, что у переменной A появилось второе имя pA, и теперь можно обращаться к одной и той же переменной, используя любое из этих имен. Ссылке pA присваивается значение 10, а затем печатается значение переменной A, которое также будет равно 10.
Однако нельзя впоследствии установить ту же ссылку pA на другую переменную B. Это объясняется тем, что ссылка не занимает места в памяти, поэтому она не может изменять свое значение в процессе работы в отличие от обычных переменных и переменных-указателей, которые занимают место в памяти.
|
|
int A, B;
int &pA = A;
A = 10;
printf(“pA = %d”, pA); // будет напечатано pA = 10
pA = 20;
printf(“A = %d”, A); // будет напечатано A = 20
pA = B; // ОШИБКА!
Ссылки значительно упростили работу с указателями. Они не требуют использования операции разадресации (или разыменования) указателя * для того, чтобы получить значение по указанному адресу.
ЗАМЕЧАНИЕ. Разумеется, нет никакой необходимости в использовании нескольких имен для одной и той же переменной. Поэтому основное назначение ссылок – это передача параметров функциям.