Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Тема 3.2 (ч.1). Команды и данные. Абстракция да...docx
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
624.15 Кб
Скачать

3.2.2.9 Указатели

Указателем называется идентифицированный скаляр6, содержащий адрес некоторого данного.

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

Перед использованием в программе указатели должны быть объявлены согласно правилам:

адрес данного ::= &, идентификатор;

объявление указателей ::= ( тип данного | void ),

*, { * }, идентификатор, [ = адрес данного ]7,

{ «,»,*, { * }, идентификатор, [ = адрес данного ] }, «;»;

Синтаксически символ * в объявлении считается префиксом и отно­сится только к единственному следующему за ним идентификатору.

Примеры объявления указателей:

  1. int *X, *Y; // указатели на переменные X и Y типа int

  2. char *Intel; // указатель на переменную Intel типа char

  3. float *Mem; // указатель на переменную Mem типа float

  4. 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 {