Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
85
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

86

 

(a) int double = 3.14159;

(b) vector< int > _;

 

 

(c) string namespase;

(d) string catch-22;

 

(e) char 1_or_2 = '1';

(f) float Float = 3.14f;

 

 

 

 

Упражнение 3.7

В чем разница между следующими глобальными и локальными определениями

string global_class; int global_int;

переменных?

int main() {

int local_int; string local_class;

// ...

}

3.3. Указатели

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

Каждый указатель ассоциируется с некоторым типом данных, причем их внутреннее представление не зависит от внутреннего типа: и размер памяти, занимаемый объектом типа указатель, и диапазон значений у них одинаков5. Разница состоит в том, как компилятор воспринимает адресуемый объект. Указатели на разные типы могут иметь одно и то же значение, но область памяти, где размещаются соответствующие типы, может быть различной:

указатель на int, содержащий значение адреса 1000, направлен на область памяти 1000-1003 (в 32-битной системе);

указатель на double, содержащий значение адреса 1000, направлен на область памяти 1000-1007 (в 32-битной системе).

int

*ip1, *ip2;

complex<double>

*cp;

string

*pstring;

vector<int>

*pvec;

Вот несколько примеров:

5 На самом деле для указателей на функции это не совсем так: они отличаются от указателей на данные (см. раздел 7.9).

С++ для начинающих

87

double *dp;

Указатель обозначается звездочкой перед именем. В определении переменных списком звездочка должна стоять перед каждым указателем (см. выше: ip1 и ip2). В примере ниже lp указатель на объект типа long, а lp2 объект типа long:

long *lp, lp2;

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

float fp, *fp2;

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

string *ps;

синтаксически правильны и совершенно эквивалентны: string* ps;

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

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

string* ps, ps2;

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

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

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

int ival = 1024;

//pi инициализирован нулевым адресом int *pi = 0;

//pi2 инициализирован адресом ival int *pi2 = &ival;

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

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

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

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

С++ для начинающих

88

// ошибка: 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:

С++ для начинающих

89

//косвенное присваивание переменной 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, операцию разыменования к

int **ppi = π int *pi2 = *ppi;

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

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

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

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

ppi необходимо применить дважды.

<< 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 приведет к тому, что он будет указывать на следующий объект. Поэтому арифметические действия с указателями чаще всего применяются при обработке массивов; в любых других случаях они вряд ли оправданы.

Вот как выглядит типичный пример использования адресной арифметики при переборе элементов массива с помощью итератора:

int ia[10];

С++ для начинающих

90

int *iter = &ia[0];

int *iter_end = &ia[10];

while (iter != iter_end) { do_something_with_value (*iter); ++iter;

}

Упражнение 3.8

int ival = 1024, ival2 = 2048;

Даны определения переменных:

int *pi1 = &ival, *pi2 = &ival2, **pi3 = 0;

Что происходит при выполнении нижеследующих операций присваивания? Допущены

(a) ival = *pi3;

(e) pi1 = *pi3;

(b) *pi2 = *pi3;

(f) ival = *pi1;

(c) ival = pi2;

(g) pi1 = ival;

ли в данных примерах ошибки?

 

(d) pi2 = *pi1;

(h) pi3 = &pi2;

Упражнение 3.9

 

Работа с указателями один из важнейших аспектов С и С++, однако в ней легко

pi = &ival;

допустить ошибку. Например, код pi = pi + 1024;

почти наверняка приведет к тому, что pi будет указывать на случайную область памяти. Что делает этот оператор присваивания и в каком случае он не приведет к ошибке?

Упражнение 3.10

Данная программа содержит ошибку, связанную с неправильным использованием

int foobar(int *pi) { *pi = 1024;

return *pi;

}

int main() { int *pi2 = 0;

int ival = foobar(pi2); return 0;

указателей:

}

В чем состоит ошибка? Как можно ее исправить?