Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курсовые / Язык программирования Сpp 25.09.11.doc
Скачиваний:
110
Добавлен:
10.05.2015
Размер:
10.13 Mб
Скачать

Преобразование типа указателя

Указатель можно преобразовать к другому типу. Эти преобразования бывают двух видов: с использованием указателя типа void*и без его использования.

В языке С допускается присваивание указателя типа void*указателю любого другого типа (и наоборот) без явного преобразования типа указателя. Тип указателяvoid*используется, если тип объекта неизвестен. Например, использование типаvoid*в качестве параметра функции позволяет передавать в функцию указатель на объект любого типа, при этом сообщение об ошибке не генерируется. Также он полезен для ссылки на произвольный участок памяти, независимо от размещенных там объектов. Например, функция размещенияmalloc()(рассматривается далее) возвращает значение типаvoid*, что позволяет использовать ее для размещения в памяти объектов любого типа.

В отличие от void*, преобразования всех остальных типов указателей должны быть всегда явными (т.е. должна быть указана операция приведения типов). Однако следует учитывать, что преобразование одного типа указателя к другому может вызвать непредсказуемое поведение программы. Например, в следующей программе делается попытка присвоить значениехпеременнойупосредством указателяр. При компиляции программы сообщение об ошибке не генерируется, однако результат работы программы неверен.

#include <iostream.h>

#include <windows.h>

int main(void)

{

SetConsoleOutputCP(1251);

double x = 100.1, y;

int *p;

/* В следующем операторе указателю на целое p

(присваивается значение, ссылающееся на double. */

p = (int *) &x;

/* Следующий оператор работает не так, как ожидается. */

y = *p; /* попытка присвоить y значение x через p */

/* Следующий оператор не выведет число 100.1. */

cout<<" Значение x равно: "<<y<<" Это не так!";

return 0;

}

Обратите внимание на то, что операция приведения типов применяется в операторе присваивания адреса переменной х(он имеет типdouble*) указателюp, тип которогоint*. Преобразование типа выполнено корректно, однако программа работает не так, как ожидается (по крайней мере, в большинстве оболочек). Для разъяснения проблемы напомним, что переменнаяintзанимает в памяти 4 байта, аdouble— 8 байтов. Указательpобъявлен как указатель на целую переменную (т.е. типаint), поэтому оператор присваивания

y = *р;

передаст переменной yтолько 4 байта информации, а не 8 байтов, необходимых дляdouble. Несмотря на то, чтоpссылается на объектdouble, оператор присваивания выполнит действие с объектом типаint, потому чтоpобъявлен как указатель наint. Поэтому такое использование указателяpнеправильное.

Приведенный пример подтверждает то, что операции с указателями выполняются в зависимости от базового типа указателей. Синтаксически допускается ссылка на объект с типом, отличным от типа указателя, однако при этом указатель будет "думать", что он ссылается на объект своего типа. Таким образом, операции с указателями управляются типом указателя, а не типом объекта, на который он ссылается.

Указатель void

В C++ существует специальный тип указателя, который называется указателем на неопределённый тип. Для определения такого указателя вместо имени типа используется ключевое слово void в сочетании с описателем, перед которым располагается символ *.

void *UndefPoint;

С одной стороны, объявленная подобным образом переменная также является объектом определённого типа - типа указатель на объект неопределённого типа. Имя UndefPointдействительно ссылается на объект размером в 32 бита со структурой, которая позволяет сохранять адреса.

Но, с другой стороны, для объекта типа указатель на объект неопределённого типа отсутствует информация о размерах и внутренней структуре адресуемого участка памяти. Из-за этого не могут быть определены какие-либо операции для преобразования значений.

Поэтому переменной UndefPoint невозможно присвоить никаких значений без явного преобразования этих значений к определённому типу указателя.

UndefPoint = 0xb8000000; // Такое присвоение недопустимо.

Подобный запрет является вынужденной мерой предосторожности. Если разрешить такое присвоение, то неизвестно, как поступать в случае, когда потребуется изменить значение переменной UndefPoint, например, с помощью операции инкрементации.

UndefPoint++; // Для типа void * нет такой операции…

Эта операция (как и любая другая для типа указатель на объект неопределённого типа) не определена. И для того, чтобы не разбираться со всеми операциями по отдельности, лучше пресечь подобные недоразумения "в корне", то есть на стадии присвоения значения.

Объектам типа указатель на объект неопределённого типа в качестве значений разрешается присваивать значения лишь в сочетании с операцией явного преобразования типа. В этом случае указатель на объект неопределённого типа становится обычным указателем на объект какого-либо конкретного типа. Со всеми вытекающими отсюда последствиями. Но и тогда надо постоянно напоминать транслятору о том типе данных, который в данный момент представляется указателем на объект неопределённого типа:

int mmm = 10;

pUndefPointer = (int *)&mmm;

//pUndefPointer выступает в роли указателя на объект типа int.

(*(int *)pUndefPointer)++;

Для указателя на объект неопределённого типа не существует способа непосредственной перенастройки указателя на следующий объект с помощью операции инкрементации. В операторе, реализующем операции инкрементации и декрементации, только с помощью операций явного преобразования типа можно сообщить транслятору величину, на которую требуется изменить первоначальное значение указателя.

pUndefPointer++; // Это неверно, инкрементация не определена…

(int *)pUndefPointer++; // И так тоже ничего не получается…

((int *)pUndefPointer)++; // А так хорошо… Сколько скобок!

++(int *)pUndefPointer; // И вот так тоже хорошо…

С помощью операции разыменования и с дополнительной операцией явного преобразования типа изменили значение переменной mmm.

pUndefPointer = (int *)pUndefPointer + sizeof(int);

//Теперь перенастроили указатель на следующий объект типа int.

pUndefPointer = (int *)pUndefPointer + 1;

И получаем тот же самый результат.

Специфика указателя на объект неопределённого типа позволяет выполнять достаточно нетривиальные преобразования:

(*(char *)pUndefPointer)++;

А как изменится значение переменной mmmв этом случае?

pUndefPointer = (char *)pUndefPointer + 1;

Указатель перенастроился на объект типа char. То есть просто сдвинулся на 1байт.

Работа с указателями на объекты определённого типа не требует такого педантичного напоминания о типе объектов, на которые настроен указатель. Транслятор об этом не забывает.

int * pInt; int mmm = 10;

pInt = &mmm; // Настроили указатель.

pInt++; // Перешли к очередному объекту.

*pInt++; // Изменили значение объекта, идущего следом за

// переменной mmm.

Напомним, что происходит в ходе выполнения этого оператора.

  • после выполнения операции разыменования вычисляется значение (адрес объекта mmm),

  • это значение становится значением выражения,

  • после чего это значение увеличивается на величину, кратную размеру того типа данного, для которого был объявлен указатель.

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

int mmm = 10;

char ccc = 'X';

float fff = 123.45;

pInt = &mmm;

pNullInt = (int *)&ccc;

pNullInt = (int *)&fff; // Здесь будет выдано предупреждение об

// опасном преобразовании.

Это обстоятельство имеет определённые последствия, которые связаны с тем, что все преобразования над значениями указателей будут производиться без учёта особенностей структуры тех объектов, на которые указатель в самом начале был настроен.

При этом ответственность за результаты подобных преобразований возлагается на программиста.