Этот стиль подчеркивает, что переменная имеет составной тип. Согласно второму, модификатор типа располагается рядом с типом, но он определяет только одну переменную в операторе:
int* p1; // p1 - указатель на тип int
int* p2; // p2 - указатель на тип int
Этот стиль подчеркивает, что объявление определяет составной тип.
Нет никакого единственно правильного способа определения указателей и ссылок. Важно неукоснительно придерживаться выбранного стиля.
В этой книге используется первый стиль, знак *
(или &
) помещается рядом с именем переменной.
Теоретически нет предела количеству модификаторов типа, применяемых в операторе объявления. Когда модификаторов более одного, они объединяются хоть и логичным, но не всегда очевидным способом. В качестве примера рассмотрим указатель. Указатель — это объект в памяти, и, как у любого объекта, у этого есть адрес. Поэтому можно сохранить адрес указателя в другом указателе.
Каждый уровень указателя отмечается собственным символом *
. Таким образом, для указателя на указатель пишут **
, для указателя на указатель на указатель — ***
и т.д.
int ival = 1024;
int *pi = &ival; // pi указывает на переменную типа int
int **ppi = π // ppi указывает на указатель на переменную типа int
Здесь pi
— указатель на переменную типа int, a ppi
— указатель на указатель на переменную типа. Эти объекты можно было бы представить так:
Подобно тому, как обращение к значению указателя на переменную типа int
возвращает значение типа int
, обращение к значению указателя на указатель возвращает указатель. Для доступа к основной объекту в этом случае необходимо обратиться к значению указателя дважды:
cout << "The value of ival\n"
<< "direct value: " << ival << "\n"
<< "indirect value: " << *pi << "\n"
<< "doubly indirect value: " << **ppi << endl;
Эта программа выводит значение переменной ival
тремя разными способами: сначала непосредственно, затем через указатель pi
на тип int
и наконец обращением к значению указателя ppi
дважды, чтобы добраться до основного значения в переменной ival
.
Ссылка — не объект. Следовательно, не может быть указателя на ссылку. Но поскольку указатель — это объект, вполне можно определить ссылку на указатель.
int i = 42;
int *p; // p - указатель на тип int
int *&r = p; // r - ссылка на указатель p
r = &i; // r ссылается на указатель;
// присвоение &i ссылке r делает p указателем на i
*r = 0; // обращение к значению r дает i, объект, на который
// указывает p; изменяет значение i на 0
Проще всего понять тип r
— прочитать определение справа налево. Ближайший символ к имени переменной (в данном случае &
в &r
) непосредственно влияет на тип переменной. Таким образом, становится ясно, что r
является ссылкой. Остальная часть оператора объявления определяет тип, на который ссылается ссылка r
. Следующий символ, в данном случае *
, указывает, что тип r относится к типу указателя. И наконец, базовый тип объявления указывает, что r
— это ссылка на указатель на переменную типа int
.
Сложное объявление указателя или ссылки может быть проще понять, если читать его справа налево.
Упражнение 2.25. Определите типы и значения каждой из следующих переменных:
(a) int* ip, &r = ip; (b) int i, *ip = 0; (c) int* ip, ip2;
2.4. Спецификатор const
Иногда необходимо определить переменную, значение которой, как известно, не может быть изменено. Например, можно было бы использовать переменную, хранящую размер буфера. Использование переменной облегчит изменение размера буфера, если мы решим, что исходный размер нас не устраивает. С другой стороны, желательно предотвратить непреднамеренное изменение в коде значения этой переменной. Значение этой переменной можно сделать неизменным, используя в ее определении спецификатор const
(qualifier const):