Для нетипизированных указателей



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

var

px , py : pointer;

begin

GetMem (px, SizeOf(char));

GetMem (py, SizeOf(integer));

px ^ : ='A';

py ^ : = 7;

FreeMem(px , SizeOf(char));

FreeMem(py , SizeOf(integer));

end.

В этой программе в разделе описания переменных объявлены два не­типизированных указателя. Затем в теле программы под динамические переменные, на которые ссылаются эти указатели, с помощью процедуры GetMem выделена динамическая память. Эта процедура имеет два пара­метра, один из которых представляет собой переменную указательного типа, а второй – целочисленное выражение, определяющее объем выде­ляемой памяти в байтах. Часто для определения второго параметра про­цедуры GetMem используется функция SizeOf, возвращающая длину в байтах внутреннего представления указанного объекта. В нашем случае функция SizeOf помогает определить, сколько байт памяти потребуется для данных типа Char и Integer..

После выделения памяти динамическим переменным можно при­сваивать значения подходящего типа (для которых подходит объем заре­зервированной памяти – это есть в теле программы) и использовать их в разного рода выражениях (операторы с этими выражениями в теле про­граммы заменены тремя точками).

Когда надобность в переменных РХ и PY отпадает, выделенная для них память освобождается с помощью процедуры FreeMem. Эта проце­дура имеет те же параметры, что и процедура GetMem. После этого осво­бодившуюся память можно выделять для других переменных, использующихся в оставшихся операторах.

2.3.3. Действия над указателями и динамическими переменными.После того как указатель объявлен (в разделе описаний программы) и под динамическую переменную, на которую он ссылается, выделена память (с помощью процедуры New или GetMem), над указателем и дина­мической переменной обычно производятся какие-то манипуляции (иначе зачем же их объявлять и выделять память). К динамическим пе­ременным применимы все действия, допустимые для данных этого ти­па. Иными словами, динамической переменной можно манипулировать так же, как и аналогичной статической переменной. Некоторые из этих действий иллюстрирует рис.1.

Рис.1. Действия над динамическими переменными

На рис.1(а) представлена операция присваивания. Здесь трем ди­намическим переменным А, В и С, принадлежащим разным типам, присваиваются некоторые значения.

На рис.1(б) представлена операция сложения. Здесь значения двух динамических переменных Х и Y складываются и присваиваются треть­ей переменной – Z.

На рис.1(в) представлены операции ввода с клавиатуры и вывода на экран. Здесь значение динамической строковой переменной сначала вводится с клавиатуры, а затем выводится на экран.

Для указателей (а не данных, на которые они указыва­ют) допустимо только следующее:

1. Проверка равенства. Например:

if рх = ру then...

где РХ и PY – указатели. Этот оператор проверяет, ссылаются ли указатели на один и тот же адрес памяти. Однако если бы здесь вместо рх=ру присутствовало рх <ру или рх >ру, то была бы зафиксирована ошибка.

2. Значение одного указателя можно присвоить другому указа­телю, если оба указателя ссылаются на динамическую пере­менную одного типа. Например:

рх := ру;

где РХ и PY – указатели.

Это ограничение распространяется только на типизированные указатели. Нетипизированному указателю можно присвоить значение любого указателя. Точно так же любому указателю можно присвоить значение нетипизированного указателя.

3. Значения указателям (представляющие собой адреса памяти) присваиваются процедурой New или GetMem при выделении памяти для динамических переменных. Поэтому присваивать с помощью оператора присваивания указателям какие-либо явно заданные значения нельзя – за исключением NIL. Как мы уже знаем, указатель со значением NIL указывает "в никуда".

После завершения использования в программе динамической переменной не забывайте освободить выделен­ную для нее память. Для этого предназначены процеду­ры Dispose и FreeMem.

Два вида динамических данных

Динамические данные, на которые ссылаются указатели, можно разделить на две категории: данные без внутренних ссылок и данные с внутренними ссылками.

2.4.1. Динамические данные без внутренних ссылок. Эти данные отличаются от статических данных только тем, что в разделе описания переменных объявляются не переменные, а указатели на них. Сами же переменные создаются и ликвидируются в ходе работы программы, когда под них выделяется память (процедурой New или GetMem), или эта память освобождается (процедурой Dispose или FreeMem).

Целесообразно ли использовать такие данные? В чем их преимущества по сравнению с аналогичными статическими данными? Во-первых, как уже отмечалось, для статических данных память выделяется при запуске программы, и эта память остается занята, пока программа не завершит работу. А для динамических данных (с внутренними ссылками и без них) память выделяется в момент начала использования той или иной дина­мической переменной и освобождается сразу же по завершении ее ис­пользования. Иными словами, применяя динамические переменные, можно обойтись меньшим объемом оперативной памяти.

Во-вторых, динамическая память предоставляет возможность рабо­тать с данными структурированных типов гораздо большего объема. Помните, массив, содержащий 15 000 элементов типа Longint, мы смогли обработать, а 20 000 – уже нет?

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

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

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

Таблица1

Использование статических и динамических данных

Статические переменные Динамические переменные
var a:boolean ; b:integer ; x:array[1..10] of integer; j:1..10; begin a:=true; b:=44; for j:=1 to 10 do read(x[j]); end . type v=array[1..10] of integer; var pa:^boolean ; pb : ^integer; px:^v ; j:1..10; begin New(pa); New(pb); New(px); pa^:true ; pb ^:=44; for j:=1 to 10 do read(px ^[j]); Dispose(pa); Dispose(pb); Dispose(px); end .

И еще нюанс. Обратим внимание, что в первой программе в разделе описания переменных массив объявлен так:

х : аrrау[1..10] of integer;

А во второй программе в разделе описания типов объявлен тип V:

v = array[1..10] of integer;

затем в разделе описания переменных объявлен указатель РХ на данные типа V:

рх : ^v;

А почему бы во второй программе не поступить как в первой, объ­явить указатель на массив? Вот так:

рх : ^array[1..10] of integer;

К сожалению, если так сделать, а затем запустить программу, поя­вится сообщение об ошибке, гласящее, что в этой точке программы (т. е. после знака ^) ожидается идентификатор. (ARRAY – это, как известно, не идентификатор, а зарезервированное слово). Иными словами, объявить указатель непосредственно на массив нельзя. Чтобы обойти это препятствие, пришлось сначала объявить пользовательский тип V:

v = array[1..10] of integer;

а затем – указатель на данные типа V:

рх : ^v;

То, о чем здесь шла речь применительно к массивам, относится также к записям и множествам, поскольку RECORD и SET – также зарезервированные слова.

2.4.2. Динамические данные с внутренними ссылками.Помимо простых (т. е. без внутренних ссылок) динамических данных возможно существование динамических структур, в которых элементы ссылаются один на другой. Как это реализуется? Очевидно, что элемент подобной структуры должен представлять собой запись не менее чем с двумя полями. Одно поле такой записи предназначено для информации, а второе – для ссылки на следующий элемент этой же структуры. Вот как упомянутая структура может быть объявлена в разделе описания типов:

type

p=^item;

item=record

data:integer ;

reference:p

end ;

Как эту структуру представить графически, демонстрирует рис.2.

Рис.2. Простейшая структура данных с внутренними ссылками (здесь Item - элемент; Data - данные: Reference - ссылка)

Мы уже знаем, что при описании указателя на запись, одно из полей которого ссылается на этот указатель, первым должно следовать описание указателя (в нашем случае Р). Запись объявленного нами типа Item состо­ит из полей Data и Reference. Первое поле предназначено для данных, а второе – для указателя, ссылающегося на следующую запись типа Item.

На рис.2 изображена цепочка элементов нашей структуры с внут­ренними ссылками. Каждый элемент здесь – это запись, принадлежащая типу Item. Кстати, указатель, содержащийся в поле Reference каждого элемента, ссылается не на одно из полей следующего элемента структуры (как это можно понять по изображению), а на элемент (запись) в целом. Структура, о которой идет речь, называется связным списком.

Нетрудно прийти к выводу, что связный список с точки зрения эко­номии памяти – не самый удачный метод организации данных, по­скольку каждый элемент списка, помимо байтов памяти для информа­ции, нуждается в памяти для указателя, ссылающегося на следующий элемент списка. Однако при большем расходе памяти списки обеспечи­вают и большую гибкость. Действительно, если аналогичные данные ор­ганизовать в виде массива, для того чтобы включить в структуру новый элемент или исключить один из элементов, потребуется преобразовы­вать весь массив. В то же время для того чтобы добавить в список новый элемент, достаточно сделать так, чтобы указатель предыдущего элемен­та ссылался на новый элемент, а указатель нового элемента – на сле­дующий элемент списка. Аналогично, при исключении одного из эле­ментов списка достаточно только изменить значение указателя предыдущего элемента так, чтобы он указывал на элемент, следующий за ис­ключаемым. Как это выглядит на практике, демонстрирует рис.3.

Существует несколько разновидностей связного списка, о котором шла речь выше.

Кольцевой список. В этом списке указатель последнего элемен­та ссылается на первый.

Очередь. Эта разновидность связного списка допускает только добавление нового элемен­та в конец очереди и ис­ключение элемента в начале очереди.

Стек. Для этой разновид­ности связного списка можно только добавлять или исключать элементы с одного его конца.

Рис.3. Включение и исключение элемента списка

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

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

Рис.4. Иерархическое дерево


Дата добавления: 2019-02-26; просмотров: 217; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!