Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции СД.doc
Скачиваний:
212
Добавлен:
19.03.2015
Размер:
1.81 Mб
Скачать
    1. 2.9. Указательный тип

Все рассмотренные типы данных имеют одну общую черту. Перед использованием переменная должна быть предварительно описана в разделе переменных. Переменные, определяемые в явных описаниях, называются статическими. Память для таких переменных выделяется при запуске программы (или при входе в структурный блок, в котором описана переменная, например, в подпрограммы) и освобождается при завершении ее работы (или при выходе из блока).

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

Указатель (ссылка) – переменная, значением которой является физический адрес некоторой ячейки памяти. Адрес занимает 4 смежных байта памяти (два слова). Поскольку переменные большинства типов данных занимают несколько смежных байтов, то указатель содержит адрес первой ячейки памяти. Необходимость использования указателей при решении прикладных задач с использованием языков высокого уровня существует очень часто.

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

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

Значением указательного типа (pointer type) является адрес другой переменной или константы. Объект, на который указывает указатель, называют указуемым. Указатели используются для выполнения операций над адресами ячеек (рис. 2.3).

Рис. 2.3. Переменная-указатель и указуемая переменная.

В примере указатель ptr является переменной, расположенной в памяти по адресу 100. Содержимое этой ячейки, значение 200, является фактическим адресом переменной, которая содержит значение 50. К переменной можно получить доступ, просто обращаясь к ней по имени, но также можно использовать и разыменование указателя. При выполнении операции разыменования, получают не содержимое переменной указателя ptr, а содержимое ячейки памяти, адрес которой содержится в ptr, т.е. указуемый объект.

Операции над указателями. Указатели могут участвовать в операциях присваивания, получения адреса и выборки.

Операция присваивания – двухместная, оба операнда которой указатели. Операция присваивания копирует значение одного указателя в другой, в результате оба указателя будут содержать один и тот же адрес памяти.

Операция получения адреса – одноместная, ее операнд может иметь любой тип, результатом является типизированный (в соответствии с типом операнда) указатель, содержащий адрес объекта-операнда.

Операция выборки – одноместная, ее операндом является обязательно типизированный указатель, а результат – данные, выбранные из памяти по адресу, заданному операндом. Тип результата определяется типом указателя-операнда.

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

Результат операций «указатель + целое» и «указатель – целое» имеет тип «указатель». Можно вычесть один указатель из другого (оба указателя-операнда при этом должны иметь одинаковый тип). Результат такого вычитания будет иметь тип целого числа со знаком. Его значение показывает, на сколько байт (или других единиц измерения) один адрес отстоит от другого.

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

Типизированные указатели. При объявлении типизированного указателя всегда должен быть определен тип объекта, адресуемого указателем. При определении типа-указателя используется базовый тип, перед которым ставится признак указателя «^». По соглашению, идентификаторы типа указателя и переменной-указателя начинаются с заглавной буквы P.

Описание типизированного указателя имеет вид:

type

P = ^t;

где P – идентификатор типа указателя; t – идентификатор (не определение) типа элемента данных, на который ссылается указатель.

Типизированный указатель может быть определен в разделе переменных. Например, следующие объявления:

var

ipt: ^Byte;

cpt: ^Char;

означают, что переменная ipt представляет адрес области памяти, в которой хранится целое число, а cpt – адрес области памяти, в которой хранится символ.

Физическая структура адреса не зависит от типа и значения данных, хранящихся по этому адресу. Тем не менее, указатели ipt и cpt имеют разный тип, и поэтому оператор присваивания:

cpt:=ipt;

недопустим.

Для типизированных указателей правильнее говорить не о едином типе данных «указатель», а о целом семействе типов: «указатель на целое», «указатель на символ» и т.д. Указатели могут быть определены и на более сложные, интегрированные структуры данных, и указатели на указатели.

Пусть имеется описание:

type

Tarr = array[1..20] of Real;

var

Pint: ^Integer;

Parr: ^Tarr;

Для такого описания Pint – переменная, указывающая на элемент целого типа (целое число), а Parr – указатель адреса первого элемента массива вещественных чисел. Сами указатели являются статическими переменными и располагаются в сегменте данных, а элементы, на которые ссылаются указатели, занимают место в динамической памяти (рис. 2.4).

Рассмотрим действия, которые можно выполнять с типизированными указателями. Значение переменной указательного типа можно задать процедурами по работе с динамической памятью, адресным оператором @, или операцией присваивания.

Указатели одного и того же типа можно сравнивать с помощью операций «=» и «<>». Переход от указателя к объекту ссылки производится с помощью операции разыменования, которая обозначается знаком ^, стоящим после имени указателя:

Pint^ – объект (целое число), на который указывает Pint;

Parr^ – массив вещественных чисел, на который ссылается переменная Parr.

2

Динамическая память

0-й элемент массива

. . .

2-й элемент массива

1-й элемент массива

Ц

ссылки

елое число

Статическая память

Pint

Parr

Рис. 2.4. Размещение переменной типа Tarr в динамической памяти.

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

Pint^:=Pint^+3;

Parr^[i]:=0;

Переход от объекта к его адресу осуществляется операцией взятия адреса@ или функцией Addr. Если выполнить присваивания:

Pint:=@X;

или

Pint:=Addr(X);

то будет:

Pint^:=X

Таким образом, операция взятия адреса является обратной к операции разыменования.

Для динамического распределения памяти существуют стандартные процедуры. Процедура New(P) где P – указатель, позволяет выделить область памяти такого размера, в котором можно разместить величину базового типа. Указатель принимает значение адреса выделенной области.

Процедура Dispose(P), позволяет освободить область памяти, на которую указывает указатель P, для последующего использования. После выполнения процедуры значение указателя P становится неопределенным.

Процедура GetMem(P, Size) позволяет выделить в динамической памяти область размером Size байт, при этом адрес выделенной области присваивается переменной P.

Процедура FreeMem(P, Size) освобождает занятую область памяти с адресом, задаваемым оператором P и размером Size байт. После выполнения процедуры область памяти становится свободной, а значение указателя оказывается неопределенным.

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

Рассмотрим несколько типичных приемов работы с указателями. Пусть имеется описание:

type

PComplex= ^TComplex; { тип-указатель }

TComplex=record{ определение базового типа }

Re, Im: Real;

end;

AdrInt = ^Integer; { тип-указатель }

var

X: TComplex;

P1,P2,P3,P4: PComplex;

Adr:AdrInt;

тогда возможны следующие операции:

New(P1); { выделение памяти для типа TComplex }

New(Adr); { выделение памяти для типа Integer }

P2:=@X; { определение адреса переменной X }

P3:=P1; { присвоение значения другого указателя }

P4:=nil; { присвоение значения nil – «пустая» ссылка, не указывающая

ни на какой объект, «адресный ноль» }

X:=P1^; { переменной X присваивается значение

элемента, на который указывает P1 }

P3^:=X; { элементу, на который указывает P3,

присваивается значение переменной X }

X:=@Y; { X – адрес параметра Y }

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

Dispose(P1); { освобождение памяти от данных, на которые указывает P1 }

New(Adr);

Нетипизированные указатели. В языке Паскаль существует стандартный тип-указатель Pointer, не связанный с базовым типом. Нетипизированный указатель совместим с любым другим типом-указателем. Переменная типа Pointer содержит адрес объекта неопределенного типа.

Пусть имеются следующие описания:

var

p1: ^Char;

p2: ^Boolean;

pp:Pointer;

Использование указателей типа Pointer позволяет динамически размещать с одного адреса данные различных типов. Напомним, присваивание значения одного указателя другому возможно только между указателями объектов идентичного типа. В примере символьный и булевый тип занимает одинаковый объем памяти (1 байт), однако следующая запись некорректна:

p1:=p2;

Нетипизированный указатель совместим по присваиванию с указателем любого типа, поэтому присваивание между p1 и p2 можно выполнить опосредованно:

pp:=p2;

p1:=pp;

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