C++ Программирование в среде С++ Builder 5

Указатели


Указатель — это переменная, которая содержит адрес другого объекта. Этим объектом может быть некоторая переменная, динамический объект или функция. Говорят, что указатель ссылается на соответствующий объект. Хотя адрес, по существу — 32-битное целое число, определяющее положение объекта в виртуальной памяти программы, указатель является не просто целым числом, а специальным типом данных. Он “помнит”, на какого рода данные ссылается. Объявление указателя выглядит так:

тип_указываемого_объекта *имя_указателя [= значение];

Вот примеры объявлений:

int *pIntVar; // Указатель на целое.

double *pDouble = SdoubleVar; // Инициализация указателя

// на double.

char *arrStr[16]; // Массив указателей на char.

char (*arrStr) [16][16]; // Указатель на матрицу char.

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

Чтобы получить доступ к объекту, на который указатель ссылается. последний разыменовывают, применяя операцию-звездочку. Например. *pDouble будет представлять значение переменной, на которую ссылается

pDouble:

double doubleVar = 3.14159265;



double *pDouble = SdoubleVar;

printf("Значение самого указателя (адрес): %р", pDoubie) ;

printf("Число, на которое он ссылается: %f", *pDouble);

Как мы уже говорили в предыдущих разделах, указатели используются при обработке строк, а также для передачи функциям параметров, зна-

чения которых могут ими изменяться (передача по ссылке). Но главная “прелесть” указателей в том, что они позволяют создавать и обрабатывать динамические структуры данных. В языке С можно выделить память под некоторый объект не только с помощью оператора объявления, но и динамически, во время исполнения программы. Объект создается в свободной области виртуальной памяти функцией та 11 о с (). Вот пример (предполагается, что переменные объявлены так, как выше):

pDouble = malice(sizeof(double)); // Динамическое выделение


// памяти. *pDouble = doubleVar; // Присвоение значения

// динамическому объекту.

printf("Значение динамического объекта: %f", *pDouble) ;

free(pDouble); // Освобождение памяти.

Аргументом malloc () является размер области памяти, которую нужно выделить; для этого можно применить операцию sizeof, которая возвращает размер (в байтах) переменной или типа, указанного в качестве операнда.

Функция malloc () возвращает значение типа void* — “пустой указатель”. Это указатель, который может указывать на данные любого типа. Такой указатель нельзя разыменовывать, поскольку неизвестно, на что он указывает — сколько байтов занимает его объект и как их нужно интерпретировать. В данном случае операция присваивания автоматически приводит значение malloc () к типу double*. Можно было бы написать в явном виде

pDouble = (double*)malloc(sizeof(double));

Если выделение памяти по какой-то причине невозможно, malloc () возвращает NULL, нулевой указатель. На самом деле эта константа определяется в stdlib.h как целое — “длинный нуль”:

#define NULL OL



Хорошо ли вы поняли смысл различия двух последних примеров? В первом из них указателю pDouble присваивается адрес переменной doubleVar. Во втором указателю присваивается адрес динамически созданного объекта типа double;

после этого объекту, на который ссылается pDouble, присваивается значение переменной doubleVar. Создается динамическая копия значения переменной.

Память, выделенную malloc (), следует освободить функцией free () , если динамический объект вам больше не нужен. Возьмите это себе за правило и не полагайтесь на то, что система Windows автоматически уничтожает все динамические объекты программы по ее завершении.

Перед тем, как разыменовывать указатель, его нужно обязательно инициализировать, либо при объявлении, либо путем присвоения ему адреса какого-либо объекта, возможно, динамического — как в последнем примере. Аналогично, если к указателю применяется функция free(), он становится недействительным и не ссылается больше ни на какие осмысленные данные. Чтобы использовать его повторно, необходимо снова присвоить ему адрес некоторого объекта.





Разыменование нулевого указателя также приводит к ошибке. Поэтому при работе с объектами, создаваемыми с помощью malloc (), обычно всегда проверяют, не возвратила ли эта функция нулевое значение.



Указатель на функцию



Можно объявить, инициализировать и использовать указатель на функцию. В вызовах API Windows часто применяют, например, “возвратно-вызываемые функции”. В вызове API в качестве аргумента в этом случае употребляется указатель на соответствующую функцию.

Вот пример, из которого все станет ясно.

/**********************************************

** Некоторая функция:

*/

void ShowString(char *s)

{

printf (s);

}

/***********************************************

** Главная функция:

*/

int main(void) {

void (*pFunc)(char*); // Объявление указателя на функцию.

pFunc = ShowString; // Инициализация указателя адресом

// функции. (*pFunc)("Calling a pointer to function!\n");

return 0;

}



Вы, возможно, обратили внимание, что в примере указателю присваивается значение, представленное просто именем функции без скобок со списком параметров. То есть “значение”, представленное именем функции, имеет тот же тип, что и объявленный здесь указатель. Поэтому и вызвать функцию через указатель можно было бы проще:

pFunc("Calling a pointer to function!\n");

Соотношение между указателями и функциями примерно такое же, как между указателями и массивами, о чем говорится в следующем разделе.


Содержание раздела