C++ для начинающих - страница 34

стр.


>long *lp, lp2;


В следующем случае fp интерпретируется как объект типа float, а fp2 – указатель на него:


>float fp, *fp2;


Оператор разыменования (*) может отделяться пробелами от имени и даже непосредственно примыкать к ключевому слову типа. Поэтому приведенные определения синтаксически правильны и совершенно эквивалентны:


>string *ps;

>string* ps;


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


>//внимание: ps2 не указатель на строку!

>string* ps, ps2;


Можно предположить, что и ps, и ps2 являются указателями, хотя указатель – только первый из них.

Если значение указателя равно 0, значит, он не содержит никакого адреса объекта.

Пусть задана переменная типа int:


>int ival = 1024;


Ниже приводятся примеры определения и использования указателей на int pi и pi2:


>//pi инициализирован нулевым адресом

>int *pi = 0;


>// pi2 инициализирован адресом ival

>int *pi2 = ival;


>// правильно: pi и pi2 содержат адрес ival

>pi = pi2;


>// pi2 содержит нулевой адрес

>pi2 = 0;


Указателю не может быть присвоена величина, не являющаяся адресом:


>// ошибка: pi не может принимать значение int

>pi = ival


Точно так же нельзя присвоить указателю одного типа значение, являющееся адресом объекта другого типа. Если определены следующие переменные:


>double dval;

>double *ps = dval;


то оба выражения присваивания, приведенные ниже, вызовут ошибку компиляции:


>// ошибки компиляции

>// недопустимое присваивание типов данных: int* == double*

>pi = pd

>pi = dval;


Дело не в том, что переменная pi не может содержать адреса объекта dval – адреса объектов разных типов имеют одну и ту же длину. Такие операции смешения адресов запрещены сознательно, потому что интерпретация объектов компилятором зависит от типа указателя на них.

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


>// правильно: void* может содержать

>// адреса любого типа

>void *pv = pi;

>pv = pd;


Тип объекта, на который указывает void*, неизвестен, и мы не можем манипулировать этим объектом. Все, что мы можем сделать с таким указателем, – присвоить его значение другому указателю или сравнить с какой-либо адресной величиной. (Более подробно мы расскажем об указателе типа void в разделе 4.14.)

Для того чтобы обратиться к объекту, имея его адрес, нужно применить операцию разыменования, или косвенную адресацию, обозначаемую звездочкой (*). Имея следующие определения переменных:


>int ival = 1024;, ival2 = 2048;

>int *pi = ival;


мы можем читать и сохранять значение ival, применяя операцию разыменования к указателю pi:


>// косвенное присваивание переменной ival значения ival2

>*pi = ival2;


>// косвенное использование переменной ival как rvalue и lvalue

>*pi = abs(*pi); // ival = abs(ival);

>*pi = *pi + 1; // ival = ival + 1;


Когда мы применяем операцию взятия адреса () к объекту типа int, то получаем результат типа int*

>int *pi = ival;

Если ту же операцию применить к объекту типа int* (указатель на int), мы получим указатель на указатель на int, т.е. int**. int** – это адрес объекта, который содержит адрес объекта типа int. Разыменовывая ppi, мы получаем объект типа int*, содержащий адрес ival. Чтобы получить сам объект ival, операцию разыменования к ppi необходимо применить дважды.


>int **ppi = pi;

>int *pi2 = *ppi;


>cout "Значение ival\n"

"явное значение: " ival "\n"

"косвенная адресация: " *pi "\n"

"дважды косвенная адресация: " **ppi "\n"


> endl;


Указатели могут быть использованы в арифметических выражениях. Обратите внимание на следующий пример, где два выражения производят совершенно различные действия:


>int i, j, k;

>int *pi = i;

>// i = i + 2

>*pi = *pi + 2;

>// увеличение адреса, содержащегося в pi, на 2

>pi = pi + 2;


К указателю можно прибавлять целое значение, можно также вычитать из него. Прибавление к указателю 1 увеличивает содержащееся в нем значение на размер области памяти, отводимой объекту соответствующего типа. Если тип char занимает 1 байт, int – 4 и double – 8, то прибавление 2 к указателям на char, int и double увеличит их значение соответственно на 2, 8 и 16. Как это можно интерпретировать? Если объекты одного типа расположены в памяти друг за другом, то увеличение указателя на 1 приведет к тому, что он будет указывать на следующий объект. Поэтому арифметические действия с указателями чаще всего применяются при обработке массивов; в любых других случаях они вряд ли оправданы.