
- •Данных. Составные структуры данных
- •3.2.1 Общие положения
- •3.2.1.1 Алфавит языка
- •3.2.1.2 Формат программы
- •3.2.1.3. Комментарии
- •// Пример однострочного комментария
- •3.2.1.4 Зарезервированные слова
- •3.2.2 Данные
- •3.2.2.1 Идентификаторы данных
- •3.2.2.2 Типы данных
- •3.2.2.3 Постоянные
- •3.2.2.4 Переменные
- •3.2.2.5 Массивы
- •3.2.2.6 Структуры
- •3.2.2.7 Объединения
- •Масив X:
- •Адреса элементу:
- •Масив y:
- •3.2.2.8 Перечисления
- •3.2.2.9 Указатели
- •Int Hour;
- •Int Press;
- •3.2.2.9.1 Динамическое выделения памяти для хранения данных
- •3.2.2.9.2 Динамическое освобождение памяти от хранимых данных
- •3.2.2.9.3 Ссылка
- •3.2.2.10 Множества
- •3.2.3 Выражения
- •3.2.3.1 Арифметические операции
- •Char int float | signed unsigned | short (базовый) long
- •3.2.3.2 Битовые операции
- •3.2.3.2 Логические операции
- •3.2.3.4 Операции отношения
- •3.2.3.5 Операция присваивания
- •3.2.3.6 Специальные операции
- •3.2.3.7 Элементарные функции
- •3.8 Приведение типов данных
- •3.2.3.9 Приоритеты операций
- •3.2.3.10 Адресные выражения
3.2.2.9 Указатели
Указателем называется идентифицированный скаляр6, содержащий адрес некоторого данного.
Начальный этап выполнения программы предусматривает размещение данных в оперативной памяти компьютера; при этом каждому данному ставится в соответствие его индивидуальный адрес. Для эффективной обработки данных со сложной логической структурой (таких, например, как массивы и списки) часто оказывается полезной возможность обработки их элементов с помощью механизма косвенной адресации с применением указателей.
Перед использованием в программе указатели должны быть объявлены согласно правилам:
адрес данного ::= &, идентификатор;
объявление указателей ::= ( тип данного | void ),
*, { * }, идентификатор, [ = адрес данного ]7,
{ «,»,*, { * }, идентификатор, [ = адрес данного ] }, «;»;
Синтаксически символ * в объявлении считается префиксом и относится только к единственному следующему за ним идентификатору.
Примеры объявления указателей:
int *X, *Y; // указатели на переменные X и Y типа int
char *Intel; // указатель на переменную Intel типа char
float *Mem; // указатель на переменную Mem типа float
int *X, Y; // указатель на переменную X типа int и «обычная» переменная Y типа int
Если указатель хранит адрес какого-либо объекта, то говорят, что указатель ссылается (или указывает) на этот объект.
Указатель может содержать адрес фрагмента памяти, содержимое и структура которого неизвестны; в таком случае он называется нетипизированным указателем и объявляется как данное типа void. Указатель типа void может указывать на данное любого типа. Поэтому, для получения доступа к типизированному данному указатель типа void необходимо предварительно привести к типу данного, например, так:
char Let = ‘T’; // объявление переменной Let
void ptrLet = &Let; // объявление и инициализация указателя на переменную Let
*(char *) ptrLet = ’L’; // присваивание переменной Let значения ‘L’
// (здесь (char *) – операция приведения типа к char *)
Приведение нетипизированного указателя к типизированному может быть источником программных ошибок, ведь данные разных типов хранятся в памяти по-разному. Однако результаты таких оераций исполняющей системой не отслеживаются.
Указатель также может указывать на местонахождение в памяти элемента конкретного типа; в таком случае он называется типизированным указателем. Указатели могут содержать адреса объектов любых типов. Указатель имеет базовый тип, совпадающий с типом указываемого данного, что существенно упрощает работу с указателями. Пусть, например, описан массив X двухбайтовых целых данных и указатель ptrX, содержащий адрес начала массива (рисунок 3.2.6).
Рисунок 3.2.6
Тогда указание элемента X3 массива с помощью выражения ptrX+3 приведет к автоматическому вычислению адреса X3 по формуле: 3·sizeof (int)8, причем результат операции sizeof (int) сразу учитывает модель памяти (IA32 или IA64).
Диапазон значений указателя состоит из диапазона адресов доступных программе ячеек памяти компьютера и специального значения - нулевого адреса NULL. Последний не является реальным адресом, а используется только для обозначения ситуации «указатель в данный момент не может использоваться для обращения ни к какой ячейке памяти».
Базовыми операциями над указателями являются:
инициализация - присваивание указателю адреса некоторого данного;
разыменование - обращение к значению данного, на которое ссылается указатель.
Пусть в программе объявлена переменная типа int с идентификатором X, хранящая в ячейке с адресом 2FD70002 оперативной памяти (RAM) значение 5 (рисунок 3.2.7).
Для доступа к переменной X в программе можно объявить указатель int *ptrX и занести в него адрес переменной X с помощью одноместной операции & (читается как «взять адрес»), т.е.: ptrX = &X. Доступ к значению переменной X с использованием указателя ptrX можно получить с помощью одноместной операции разыменования адреса *ptrX (читается как «извлечь данное, адрес которого хранит указатель ptrX»), например, так:
*ptrX = 5; // присваивание значения 5 переменной X.
2FD70002
RAM
· · · · · · · · · · · · · · · · · · · · · · ·
2FD70002
5
ptrX=
X=
Рисунок 3.2.7
В языке С++ имя массива одновременно является и постоянным указателем его начала. Поэтому, для массива A выражения: A и &A[0] эквивалентны друг другу, также как и выражения: A+i и &A[i], *A и A[0], *(A+i) и A[i] 9.
Следует отметить, что в отличие от объявления char A[8]="Leonid" символьного массива, где А является постоянной-указателем, объявление char *B = "Leonid" того же массива представляет собой указатель В на одномерный массив символов и является переменной (!), так что:
A = "Peter"; // ошибка: попытка изменения постоянной
B = "Peter"; // верно: теперь указатель B содержит адрес строки «Peter»
Вышесказанное справедливо и по отношению к многомерным массивам:
char (*Week1) [7][9]; // объявление указателя на двухмерный массив символьного типа
// объявление агрегата указателей на строки (каждая строка – это массив символов)
char *Week2 [ ] = { "Понеділок", "Вівторок", "Середа", "Четвер","П’ятниця", "Субота", "Неділя" };
Между массивом Week1 и агрегатом Week2 есть существенное отличие. Week1 – это массив, строки которого имеют фиксированную длину по 9 символов каждая и располагаются в памяти компактно. Week2 – это агрегат (а не двумерный массив(!)), строки которого имеют переменную длину. Агрегат Week2 нельзя назвать массивом, поскольку его элементы-строки могут не располагаться в памяти компактно (рисунок 3.2.8).
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Рисунок 3.2.8
Такой агрегат по сложившейся теминологии принято называть ступенчатым массивом.
Для исключения путаницы при использовании указателей приведем некоторые примеры их использования и трактовки:
const int Nine = 9; // объявление постоянной
int Number; // объявление переменной
int *const N1 = &Number; // указатель N1 является константой на переменную Number
const int *N2 = &Nine; // указатель N2 содержит адрес постоянной Nine
const int *const N3 = &Nine; // указатель и указываемое им данное – постоянные
const char *S1 = “text”; // указатель на строку-постоянную
char *const S2 = “text”; // постоянный указатель на текстовую строку
const char *const S3 = “text”; // указатель и указываемая им строка – постоянные
const char *Txt1[ ] = {“line1”, “line2”, “line3”}; // указываемые строки - постоянные
char *const Txt2[ ] = {“line1”, “line2”, “line3”}; // постоянные указатели на строки
// указатели и указываемые ими строки - постоянные
const char * const Txt3[ ] = {“line1”, “line2”, “line3”};
Можно также объявить указатель на указатель, например, так:
char S = 'A',
*X = &S; // X - указатель на символ
сhar **T= &X; // указатель T хранит адрес указателя X
Можно объявить указатель на структуру, например, так:
struct Time {