Выбрать главу

Последовательность операций, отображенных на рис. 6.1, реализуется в следующей программе.

#include <iostream>

using namespace std;

int main()

{

 int balance;

 int *balptr;

 int value;

 balance = 3200;

 balptr = &balance;

 value = *balptr;

 cout << "Баланс равен:" << value <<'\n';

 return 0;

}

При выполнении этой программы получаем такие результаты:

Баланс равен: 3200

К сожалению, знак умножения (*) и оператор со значением "по адресу" обозначаются одинаковыми символами "звездочка", что иногда сбивает с толку новичков в языке C++. Эти операции никак не связаны одна с другой. Имейте в виду, что операторы "*" и "&" имеют более высокий приоритет, чем любой из арифметических операторов, за исключением унарного минуса, приоритет которого такой же, как у операторов, применяемых для работы с указателями.

Операции, выполняемые с помощью указателей, часто называют операциями непрямого доступа, поскольку мы косвенно получаем доступ к переменной посредством некоторой другой переменной.

Операция непрямого доступаэто процесс использования указателя для доступа к некоторому объекту.

О важности базового типа указателя

На примере предыдущей программы была показана возможность присвоения переменной value значения переменной balance посредством операции непрямого доступа, т.е. с использованием указателя. Возможно, при этом у вас промелькнул вопрос: "Как С++-компилятор узнает, сколько необходимо скопировать байтов в переменную value из области памяти, адресуемой указателем balptr?". Сформулируем тот же вопрос в более общем виде: как С++-компилятор передает надлежащее количество байтов при выполнении операции присваивания с использованием указателя? Ответ звучит так. Тип данных, адресуемый указателем, определяется базовым типом указателя. В данном случае, поскольку balptr представляет собой указатель на целочисленный тип, С++-компилятор скопирует в переменную value из области памяти, адресуемой указателем balptr, четыре байт информации (что справедливо для 32-разрядной среды), но если бы мы имели дело с double-указателем, то в аналогичной ситуации скопировалось бы восемь байт.

Переменные-указатели должны всегда указывать на соответствующий тип данных. Например, при объявлении указателя типа int компилятор "предполагает", что все значения, на которые ссылается этот указатель, имеют тип int. С++-компилятор попросту не позволит выполнить операцию присваивания с участием указателей (с обеих сторон от оператора присваивания), если типы этих указателей несовместимы (по сути не одинаковы).

Например, следующий фрагмент кода некорректен.

int *р;

double f;

// ...

р = &f; // ОШИБКА!

Некорректность этого фрагмента состоит в недопустимости присваивания double-указателя int-указателю. Выражение &f генерирует указатель на double-значение, а р — указатель на целочисленный тип int. Эти два типа несовместимы, поэтому компилятор отметит эту инструкцию как ошибочную и не скомпилирует программу.

Несмотря на то что, как было заявлено выше, при присваивании два указателя должны быть совместимы по типу, это серьезное ограничение можно преодолеть (правда, на свой страх и риск) с помощью операции приведения типов. Например, следующий фрагмент кода теперь формально корректен.

int *р;

double f;

// ...

р = (int *) &f; // Теперь формально все ОК!

Операция приведения к типу (int *) вызовет преобразование double- к int-указателю. Все же использование операции приведения в таких целях несколько сомнительно, поскольку именно базовый тип указателя определяет, как компилятор будет обращаться с данными, на которые он ссылается. В данном случае, несмотря на то, что p (после выполнения последней инструкции) в действительности указывает на значение с плавающей точкой, компилятор по-прежнему "считает", что он указывает на целочисленное значение (поскольку р по определению — int-указатель).