Оператор разыменования также может стоять в левой части оператора присваивания.
*pc = 'x'; // OK: переменной char, на которую ссылается
// указатель pc,
// можно присвоить символ 'x'
*pi = 27; // OK: указатель int* ссылается на int, поэтому *pi —
// это int
*pi = *pc; // OK: символ (*pc) можно присвоить переменной
// типа int (*pi)
Обратите внимание: несмотря на то, что значение указателя является целым числом, сам указатель целым числом не является. “На что ссылается
int
?” — некорректный вопрос. Ссылаются не целые числа, а указатели. Тип указателя позволяет выполнять операции над адресами, в то время как тип
int
позволяет выполнять (арифметические и логические) операции над целыми числами. Итак, указатели и целые числа нельзя смешивать.
int i = pi; // ошибка: нельзя присвоить объект типа int*
// объекту типа int
pi = 7; // ошибка: нельзя присвоить объект типа int объекту
// типа int*
Аналогично, указатель на
char
(т.е.
char*
) — это не указатель на
int
(т.е.
int*
). Рассмотрим пример.
pc = pi; // ошибка: нельзя присвоить объект типа int*
// объекту типа char*
pi = pc; // ошибка: нельзя присвоить объект типа char*
// объекту типа int*
Почему нельзя присвоить переменную
pc
переменной
pi
? Один из ответов — символ
char
намного меньше типа
int
.
char ch1 = 'a';
char ch2 = 'b';
char ch3 = 'c';
char ch4 = 'd';
int* pi = &ch3; // ссылается на переменную,
// имеющую размер типа char
// ошибка: нельзя присвоить объект char* объекту
// типа int*
// однако представим себе, что это можно сделать
*pi = 12345; // попытка записи в участок памяти, имеющий размер
// типа char
*pi = 67890;
Как именно компилятор размещает переменные в памяти, зависит от его реализации, но, скорее всего, это выглядит следующим образом.
< image l:href="#"/>
Если бы компилятор пропустил такой код, то мы могли бы записать число
12345
в ячейку памяти, начинающуюся с адреса
&ch3
. Это изменило бы содержание окрестной памяти, т.е. значения переменных
ch2
и
ch4
. В худшем (и самом реальном) случае мы бы перезаписали часть самой переменной
pi
! В этом случае следующее присваивание
*pi=67890
привело бы к размещению числа
67890
в совершенно другой области памяти. Очень хорошо, что такое присваивание запрещено, но таких механизмов защиты на низком уровне программирования очень мало.
В редких ситуациях, когда нам требуется преобразовать переменную типа
int
в указатель или конвертировать один тип показателя в другой, можно использовать оператор
reinterpret_cast
(подробнее об этом — в разделе 17.8).
Итак,
мы очень близки к аппаратному обеспечению. Для программиста это не очень удобно. В нашем распоряжении лишь несколько примитивных операций и почти нет библиотечной поддержки. Однако нам необходимо знать, как реализованы высокоуровневые средства, такие как класс
vector
. Мы должны знать, как написать код на низком уровне, поскольку не всякий код может быть высокоуровневым (см. главу 25). Кроме того, для того чтобы оценить удобство и относительную надежность высокоуровневого программирования, необходимо почувствовать сложность низкоуровневого программирования. Наша цель — всегда работать на самом высоком уровне абстракции, который допускает поставленная задача и сформулированные ограничения. В этой главе, а также в главах 18–19 мы покажем, как вернуться на более комфортабельный уровень абстракции, реализовав класс
vector
.
17.3.1. Оператор sizeof
Итак, сколько памяти требуется для хранения типа
int
? А указателя? Ответы на эти вопросы дает оператор
sizeof
.
cout << "размер типа char" << sizeof(char) << ' '
<< sizeof ('a') << '\n';
cout << "размер типа int" << sizeof(int) << ' '
<< sizeof (2+2) << '\n';
int* p = 0;
cout << "размер типа int*" << sizeof(int*) << ' '
<< sizeof (p) << '\n';
Как видим, можно применить оператор
sizeof
как к имени типа, так и к выражению; для типа оператор
sizeof
возвращает размер объекта данного типа, а для выражения — размер типа его результата. Результатом оператора
sizeof
является положительное целое число, а единицей измерения объема памяти является значение
sizeof(char)
, которое по определению равно
1
. Как правило, тип
char
занимает один байт, поэтому оператор
sizeof
возвращает количество байтов.
ПОПРОБУЙТЕ
Выполните код, приведенный выше, и посмотрите на результаты. Затем расширьте этот пример для определения размера типов
bool
,
double
и некоторых других.
Размер одного и того же типа в разных реализациях языка С++ не обязательно совпадает. В настоящее время выражение
sizeof(int)
в настольных компьютерах и ноутбуках обычно равно четырем. Поскольку в байте содержится 8 бит, это значит, что тип
int
занимает 32 бита. Однако в процессорах встроенных систем тип
int
занимает 16 бит, а в высокопроизводительных архитектурах размер типа
int
обычно равен 64 битам.
Сколько памяти занимает объект класса vector? Попробуем выяснить.
vector<int> v(1000);
cout << "Размер объекта типа vector<int>(1000) = "
<< sizeof (v) << '\n';
Результат может выглядеть так:
Размер объекта типа vector<int>(1000) = 20
Причины этого факта станут очевидными по мере чтения этой и следующей глав (а также раздела 19.2.1), но уже сейчас ясно, что оператор