Каждая переменная, объявленная в программе, имеет адрес – номер ячейки памяти, в которой она расположена. Адрес является неотъемлемой характеристикой переменной. Для хранения адресов областей памяти предназначены указатели.
Указатели применяются при передаче в функцию параметров, которые мы хотим изменить, при работе с массивами, при работе с динамической памятью и в ряде других случаев.
Указатели чаще всего используют при работе с динамической памятью. Доступ к выделенным участкам динамической памяти, называемым динамическими переменными, производится только через указатели.
Время жизни динамических переменных - от точки создания до конца программы или до явного освобождения памяти.
В С++ используется два способа работы с динамической памятью. Первый использует семейство функций malloc и достался в наследство от С, второй использует операции new и delete.
Указатель может указывать на значения базового, перечислимого типа, структуры, объединения, функции, указателя.
Различают два вида указателей - указатели данных и указатели функций, отличающиеся свойствами и набором допустимых операций. Указатель не является самостоятельным типом, он всегда связан с каким-либо другим конкретным типом.
1.1.1. Указатель данных
Указатель данных содержит адрес области памяти, в которой хранятся данные определенного типа.
Синтаксис объявления указателя:
<тип> *<имя> [=<инициализатор>];
где тип может быть любым, кроме ссылки и битового поля, причем тип может быть к этому моменту только объявлен, но еще не определен.
Звездочка относится непосредственно к имени, поэтому для того, чтобы объявить несколько указателей, требуется ставить ее перед именем каждого. Например, в операторе
int* a, b, *c;
описываются два указателя на целое с именами a и c, а также целая переменная b.
Размер указателя зависит от модели памяти. Можно определить указатель на указатель, и т.д.
int *pi;
// Указатель на int
char *ppc;
// Указатель на указатель на char
int* p, s;
// Плохой стиль объявления, s – не указатель!
int *p, s;
// Видно, что s – не указатель
int *p, *s;
// Два указателя
char *names[] = {"John", "Anna"};
// Массив указателей
В последнем объявлении для формирования типа используются два оператора: * и [ ], один из которых стоит перед именем, а другой – после. Использование операторов объявления значительно упростилось бы, будь они все либо префиксами, либо суффиксами. Однако, *, [] и () разрабатывались так, чтобы отражать их смысл в выражениях. Таким образом, * является префиксом, а [] и () – суффиксами. Суффиксные операторы «крепче связаны» с именем, чем префиксные. Следовательно, *names[] означает массив указателей на какие-либо объекты, а для определения типов наподобие «указатель на функцию», необходимо использовать скобки.
Инициализация указателей
Существуют следующие способы инициализации указателя:
· int a = 5; // целая переменная· int* p = &a; // в указатель записывается адрес a· int* p (&a); // то же самое другим способом
· значения другого инициализированного указателя:
· int* r = p;
· имени массива или функции, которые трактуются как адрес:
· int b[10]; // Массив· int* t = b; // Присваивание имени массива· // ...· void f(int a) { /* ... */ } // Определение функции· void (*pf)(int); // Указатель на функцию· pf = f; // Присваивание имени функции
2. Присваивание указателю адреса области памяти в явном виде:
4. Выделение участка динамической памяти и присваивание ее адреса указателю:
· с помощью операции new:
· int* n = new int;· int* m = new int (10);· int* q = new int [10];
· с помощью функции malloc:
· int* u = (int*)malloc(sizeof(int));
Освобождение памяти, выделенной с помощью операции new, должно выполняться с помощью delete, а памяти, выделенной функцией malloc — посредством функции free. При этом переменная-указатель сохраняется и может инициализироваться повторно. Приведенные выше динамические переменные уничтожаются следующим образом:
delete n;delete m;delete [] q;free (u);
Если переменная-указатель выходит из области своего действия, отведенная под нее память освобождается. При этом память из-под самой динамической переменной не освобождается.
Можно описать указатель на тип void и присвоить ему значение указателя любого типа, а также сравнивать его с любыми указателями, но перед выполнением каких-либо действий с областью памяти, на которую он ссылается, требуется явным образом преобразовать его к конкретному типу.