Специальное применение имеют указатели на тип void. Указатель на тип void может указывать на значения любого типа. Однако для выполнения операций над указателем на void либо над указуемым объектом, необходимо явно привести тип указателя к типу, отличному от указателя на void.
Указатель на объект любого типа можно присвоить переменной типа void*, один void* можно присвоить другому void*, пару void* можно сравнивать на равенство и неравенство, и, наконец, void* можно явно преобразовать в указатель на другой тип. Прочие операции могут оказаться опасными, потому что компилятор не знает, на какого сорта объект ссылается указатель на самом деле. Поэтому другие операции вызывают сообщение об ошибке на этапе компиляции. Чтобы воспользоваться void*, необходимо явно преобразовать его в указатель определённого типа.
void f(int *pi)
{ void *pv = pi;
// Правильно – неявное преобразование типа из int* в void*
*pv;
// Ошибка – нельзя разыменовывать void*
pv++;
// Ошибка – нельзя произвести инкремент void*
int *pi2 = static_cast<int*>(pv);
// Явное преобразование в int*
double *pd1 = pv;
// Ошибка
double *pd2 = pi;
// Ошибка
double *pd3 = static_cast<double*>(pv);
// Небезопасно!
}
Как правило, не безопасно использовать указатель, преобразованный к типу, отличному от типа объекта, на который он указывает.
Основными применениями void * являются передача указателей функциям, которым не позволено делать предположения о типе объектов, а равно возврат объектов «неуточненного» типа из функций. Чтобы воспользоваться таким объектом, необходимо явно преобразовать тип указателя.
Функции, использующие указатели void *, обычно существуют на самых нижних уровнях системы, где происходит работа с аппаратными ресурсами. Наличие void * на более высоких уровнях подозрительно и, скорее всего, является индикатором ошибки на этапе проектирования.
Ноль имеет тип int. Благодаря стандартным преобразованиям, ноль можно использовать в качестве константы любого интегрального типа, типа с плавающей точкой и указателя. Тип нуля определяется по контексту. Ноль, как правило (но не всегда), будет физически представлен в виде последовательности нулевых битов соответствующей длины.
Гарантируется, что нет объектов с нулевым адресом. Следовательно, указатель, равный нулю, можно интерпретировать как указатель, который ни на что не ссылается.
В языке С обычно определялся макрос NULL для представления такого нулевого указателя. Так как в C++ типы проверяются более жестко, использование простого нуля вместо NULL приведёт к меньшим проблемам.
В операциях с указателями участвуют два объекта – сам указатель и объект, на который он ссылается. Помещение ключевого слова const перед объявлением указателя делает константой объект, а не указатель. Для объявления самого указателя в качестве константы, используется оператор объявления * const, а не просто *.
Можно присвоить адрес переменной указателю на константу, потому что это безвредная операция. Нельзя, однако, присвоить адрес константы произвольному указателю, потому что в этом случае можно было бы изменить значение константного объекта.
Указатель может быть константой или переменной, а также указывать на константу или переменную. Рассмотрим примеры:
int i; // целая переменнаяconst int ci = 1; // целая константаint* pi; // указатель на целую переменнуюconst int * pci; // указатель на целую константуint* const cp = &i; // указатель-константа на целую переменную
Величины типа указатель подчиняются общим правилам определения области действия, видимости и времени жизни.
1.1.2. Указатель функции
С функцией можно проделать только две вещи: вызвать её и получить её адрес. Указатель, полученный взятием адреса функции, можно затем использовать для вызова функции.
Указатель функции содержит адрес в сегменте кода, по которому располагается исполняемый код функции.
Указатель функции имеет тип «указатель функции, возвращающей значение заданного типа и имеющей аргументы заданного типа».