
Тема 4 Указатели и ссылки
Все языки программирования используют указатели. Однако некоторые языки используют их «скрытно», т.е. они недоступны программистам.
На заре программирования программисты управляли данными в памяти компьютера, непосредственно определяя адрес памяти, содержащий данные. Можно легко представить, что непосредственная манипуляция памятью была делом чрезвычайно сложным. Поэтому сообщество разработчиков программного обеспечения приняло решение разработать языки программирования, в которых будут использоваться имена для ячеек памяти (или переменные), а компилятор или интерпретатор будут «заботиться» об отображении имен переменных в их адреса памяти. Фактически, особенность программирования, которую мы рассматриваем сейчас как нечто само собой разумеющееся, забрала из рук программиста возможность «хозяйничать» в памяти (довольно деликатное дело). Но в языке С++ такая возможность программисту легко доступна благодаря указателям.
4.1 Объявление, инициализация и использование указателей
Итак, имя переменной ассоциируется с ее адресом и типом данных. Эта связь позволяет компилятору или интерпретатору, по существу, иметь доступ к памяти компьютера и интерпретировать данные. Кроме того, имя функции связано с адресом, по которому компилятор или интерпретатор располагает загрузочный код функции.
Указатель – это специальная переменная, в которой хранится адрес другой переменной или объекта.
Знание адреса переменной позволяет получать доступ к данным в такой переменной через указатель. Для правильной интерпретации значения переменной указатель должен имеет тип, соответствующий типу этих данных. Иными словами, тип указателя должен совпадать с типом переменной, на которую он указывает.
Прежде чем использовать указатель, его нужно объявить, как и обычную переменную. Общий синтаксис для объявления указателя:
Тип* ИмяУказателя;
Тип *ИмяУказателя; // РЕКОМЕНДУЕТСЯ!
Символ * можно размещать одним из двух способов – либо сразу после типа указателя, либо сразу перед именем указателя.
При объявлении нескольких указателей символ * должен быть перед именем каждого указателя, например:
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; // ОШИБКА! значение = указатель
ЗАМЕЧАНИЕ. Нет никакой необходимости использовать указатель на переменную, если известно имя переменной. Поэтому указатели в основном используются для передачи параметров функциям и для работы с динамической памятью компьютера.