Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
2.4.2. Указатели и спецификатор const
Подобно ссылкам, вполне возможно определять указатели, которые указывают на объект константного или неконстантного типа. Как и ссылку на константу (см. раздел 2.4.1), указатель на константу (pointer to const) невозможно использовать для изменения объекта, на который он указывает. Адрес константного объекта можно хранить только в указателе на константу:
const double pi = 3.14; // pi - константа; ее значение неизменно
double *ptr = π // ошибка: ptr - простой указатель
const double *cptr = π // ok: cptr может указывать на тип
// const double
*cptr = 42; // ошибка: нельзя присвоить *cptr
В разделе 2.3.2 упоминалось о наличии двух исключений из правила, согласно которому типы указателя и объекта, на который он указывает, должны совпадать. Первое исключение — это возможность использования указателя на константу для указания на неконстантный объект:
double dval = 3.14; // dval типа double; ее значение неизменно
cptr = &dval; // ok: но изменить dval при помощи cptr нельзя
Подобно ссылке на константу, указатель на константу ничего не говорит о том, является ли объект, на который он указывает, константой. Определение указателя как указателя на константу влияет только на то, что с его помощью можно сделать. Не забывайте, что нет никакой гарантии того, что объект, на который указывает указатель на константу, не будет изменяться.
Возможно, указатели и ссылки на константы следует рассматривать как указатели или ссылки, "которые полагают, что они указывают или ссылаются на константы".
Константные указателиВ отличие от ссылок, указатели — это объекты. Следовательно, подобно любым другим объектам, вполне может быть указатель, сам являющийся константой. Как и любой другой константный объект, константный указатель следует инициализировать, после чего изменить его значение (т.е. адрес, который он содержит) больше нельзя. Константный указатель объявляют, расположив ключевое слово const после символа *. Это означает, что данный указатель является константой, а не обычным указателем на константу.
int errNumb = 0;
int *const curErr = &errNumb; // curErr всегда будет указывать на errNumb
const double pi = 3.14159;
const double *const pip = π // pip константный указатель на
// константный объект
Как уже упоминалось в разделе 2.3.3, проще всего понять эти объявления, читая их справа налево. В данном случае ближе всего к имени curErr расположен спецификатор const, означая, что сам объект curErr будет константным. Тип этого объекта формирует остальная часть оператора объявления. Следующий символ оператора объявления, *, означает, что curErr — это константный указатель. И наконец, объявление завершает базовый тип, означая, что curErr — это константный указатель на объект типа int. Аналогично pip — это константный указатель на объект типа const double.
Тот факт, что указатель сам является константой, ничто не говорит о том, можем ли мы использовать указатель для изменения основного объекта. Возможность изменения объекта полностью зависит от типа, на который указывает указатель. Например, pip — это константный указатель на константу. Ни значение объекта, на который указывает указатель pip, ни хранящийся в нем адрес не могут быть изменены. С другой стороны, указатель curErr имеет простой, неконстантный тип int. Указатель curErr можно использовать для изменения значения переменной errNumb:
*pip = 2.72; // ошибка: pip - указатель на константу
// если значение объекта, на который указывает указатель curErr
// (т.е. errNumb), отлично от нуля
if (*curErr) {
errorHandler();
*curErr = 0; // обнулить значение объекта, на который
// указывает указатель curErr
}
Упражнения раздела 2.4.2Упражнение 2.27. Какие из следующих инициализаций допустимы? Объясните почему.
(a) int i = -1, &r = 0; (b) int *const p2 = &i2;
(c) const int i = -1, &r = 0; (d) const int *const p3 = &i2;
(e) const int *p1 = &i2; (f) const int &const r2;
(g) const int i2 = i, &r = i;
Упражнение 2.28. Объясните следующие определения. Какие из них недопустимы?
(a) int i, *const cp; (b) int *p1, *const p2;
(c) const int ic, &r = ic; (d) const int *const p3;
(e) const int *p;
Упражнение 2.29. С учетом переменных из предыдущих упражнений, какие из следующих присвоений допустимы? Объясните почему.
(a) i = ic; (b) pi = p3;
(с) pi = ⁣ (d) p3 = ⁣
(e) p2 = pi; (f) ic = *p3;
2.4.3. Спецификатор const верхнего уровня
Как уже упоминалось, указатель — это объект, способный указывать на другой объект. В результате можно сразу сказать, является ли указатель сам константой и являются ли константой объекты, на которые он может указывать. Термин спецификатор const верхнего уровня (top-level const) используется для обозначения того ключевого слова const, которое объявляет константой сам указатель. Когда указатель способен указывать на константный объект, это называется спецификатор const нижнего уровня (low-level const).
В более общем смысле спецификатор const верхнего уровня означает, что объект сам константа. Спецификатор const верхнего уровня может присутствовать в любом типе объекта, будь то один из встроенных арифметических типов, тип класса или ссылочный тип. Спецификатор const нижнего уровня присутствует в базовом типе составных типов, таких как указатели или ссылки. Обратите внимание, что ссылочные типы, в отличие от большинства других типов, способны иметь спецификаторы const как верхнего, так и нижнего уровня, независимо друг от друга.
int i = 0;
int *const pi = &i; // нельзя изменить значение pi;
// const верхнего уровня
const int ci = 42; // нельзя изменить ci; const верхнего уровня
const int *p2 = &ci; // нельзя изменить p2; const нижнего уровня
const int *const p3 = p2; // справа const верхнего уровня, слева нет
const int &r = ci; // const в ссылочных типах всегда нижнего уровня
Различие между спецификаторами const верхнего и нижнего уровней проявляется при копировании объекта. При копировании объекта спецификатор const верхнего уровня игнорируется.
i = ci; // ok: копирование значения ci; спецификатор const верхнего
// уровня в ci игнорируется
p2 = p3; // ok: указываемые типы совпадают; спецификатор const верхнего
// уровня в p3 игнорируется
Копирование объекта не изменяет копируемый объект. Поэтому несущественно, является ли копируемый или копирующий объект константой.
Спецификатор const нижнего уровня, напротив, никогда не игнорируется. При копировании объектов у них обоих должны быть одинаковые спецификаторы const нижнего уровня, или должно быть возможно преобразование между типами этих двух объектов. Как правило, преобразование неконстанты в константу возможно, но не наоборот.
int *p = p3; // ошибка: p3 имеет const нижнего уровня, а p - нет
p2 = p3; // ok: p2 имеет то же const нижнего уровня, что и p3
p2 = &i; // ok: преобразование int* в const int* возможно
int &r = ci; // ошибка: невозможно связать обычную int& с
// объектом const int
const int &r2 = i; // ok: const int& можно связать с обычным int
У указателя p3 есть спецификатор const нижнего и верхнего уровня. При копировании указателя p3 можно проигнорировать его спецификатор const верхнего уровня, но не тот факт, что он указывает на константный тип. Следовательно, нельзя использовать указатель p3 для инициализации указателя p, который указывает на простой (неконстантный) тип int. С другой стороны, вполне можно присвоить указатель p3 указателю p2. У обоих указателей тот же тип (спецификатор const нижнего уровня). Тот факт, что p3 — константный указатель (т.е. у него есть спецификатор const верхнего уровня), не имеет значения.