3.3. УказателиУказатели и динамическое выделение памяти были вкратце представлены в разделе
2.2. Указатель – это объект, содержащий адрес
другого объекта и позволяющий косвенно манипулировать этим объектом. Обычно
указатели используются для работы с динамически созданными объектами, для построения
связанных структур данных, таких, как связанные списки и иерархические деревья,
и для передачи в функции больших объектов – массивов и объектов классов – в
качестве параметров.
Вот несколько примеров: int *ip1, *ip2; complex<double> *cp; string *pstring; vector<int> *pvec; 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 являются указателями, хотя указатель –
только первый из них. int ival = 1024; Ниже приводятся примеры определения и использования указателей на int pi и pi2: //pi инициализирован нулевым адресом int *pi = 0; Указателю не может быть присвоена величина, не являющаяся адресом: // ошибка: pi не может принимать значение int pi = ival Точно так же нельзя присвоить указателю одного типа значение, являющееся адресом объекта другого типа. Если определены следующие переменные: double dval; double *ps = &dval; то оба выражения присваивания, приведенные ниже, вызовут ошибку компиляции: // ошибки компиляции // недопустимое присваивание типов данных: int* <== double* pi = pd pi = &dval; Дело не в том, что переменная pi не может содержать адреса объекта dval – адреса
объектов разных типов имеют одну и ту же длину. Такие операции смешения адресов
запрещены сознательно, потому что интерпретация объектов компилятором зависит
от типа указателя на них. // правильно: void* может содержать // адреса любого типа void *pv = pi; pv = pd; Тип объекта, на который указывает void*, неизвестен, и мы не можем манипулировать
этим объектом. Все, что мы можем сделать с таким указателем, – присвоить его
значение другому указателю или сравнить с какой-либо адресной величиной. (Более
подробно мы расскажем об указателе типа void в разделе 4.14.) int ival = 1024;, ival2 = 2048; int *pi = &ival; мы можем читать и сохранять значение ival, применяя операцию разыменования к указателю pi: // косвенное присваивание переменной ival значения ival2 *pi = ival2; Когда мы применяем операцию взятия адреса (&) к объекту типа int, то получаем
результат типа int* int **ppi = π int *pi2 = *ppi; Указатели могут быть использованы в арифметических выражениях. Обратите внимание на следующий пример, где два выражения производят совершенно различные действия: int i, j, k; int *pi = &i; // i = i + 2 К указателю можно прибавлять целое значение, можно также вычитать из него.
Прибавление к указателю 1 увеличивает содержащееся в нем значение на размер
области памяти, отводимой объекту соответствующего типа. Если тип char занимает
1 байт, int – 4 и double – 8, то прибавление 2 к указателям на char, int и double
увеличит их значение соответственно на 2, 8 и 16. Как это можно интерпретировать?
Если объекты одного типа расположены в памяти друг за другом, то увеличение
указателя на 1 приведет к тому, что он будет указывать на следующий объект.
Поэтому арифметические действия с указателями чаще всего применяются при обработке
массивов; в любых других случаях они вряд ли оправданы. int ia[10]; int *iter = &ia[0]; int *iter_end = &ia[10]; Упражнение 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; } В чем состоит ошибка? Как можно ее исправить? Упражнение 3.11Ошибки из предыдущих двух упражнений проявляются и приводят к фатальным последствиям из-за отсутствия в С++ проверки правильности значений указателей во время работы программы. Как вы думаете, почему такая проверка не была реализована? Можете ли вы предложить некоторые общие рекомендации для того, чтобы работа с указателями была более безопасной? Назад ВпередСодержание |
2011-11-30 12:02:49 Саша чочо 2012-08-29 21:37:45 Anon Да хрен через плечо дурень -_- Оставить комментарий: |