Учебное пособие Для студентов технических специальностей всех форм обучения


Глава 7. Динамические структуры данных



бет16/24
Дата10.03.2016
өлшемі2.27 Mb.
#49466
түріУчебное пособие
1   ...   12   13   14   15   16   17   18   19   ...   24

Глава 7. Динамические структуры данных

§7.1. Динамические переменные


Все рассмотренные ранее структуры данных являются статическими. Память под переменные выделяется на этапе трансляции, ее объем и распределение не может изменяться во время решения задачи в зависимости от вводимых условий. Исключение составляют файлы. Однако файл не рассматривается как единое целое. Его обработка – это, по сути дела, обработка отдельных компонентов. Здесь постоянно может требоваться «подкачка» компонент в буферную область оперативной памяти. Работа с компонентами файла – очень медленная операция с точки зрения процессора.

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

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

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

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

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



  1. В IBM-совместимых компьютерах используется сегментная организация памяти. Для 16-разрядных ОС, которой является и MS-DOS, максимальный размер сегмента данных не может превышать 64 Кбайт. Поэтому, если общее количество описанных статических переменных выходит за эту границу, возникает сообщение об ошибке «Выход за пределы памяти». Динамические переменные могут находиться в разных сегментах, поэтому их размер ограничен только размером оперативной памяти. Начиная с Delphi 2.0 (предназначенной для работы в Windows 95 OCR2, – 32-разрядной операционной системы, и выше) это ограничение снимается. Но здесь широко используются объекты, которые и являются динамическими.

  2. Если в программе используются буфера памяти для временного хранения данных заранее неизвестного размера, обычно хранящихся в файлах. Это относится, например, к созданию текстовых редакторов.

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

§7.2. Указатели


Тип указателя сам по себе не является динамической структурой данных, часто его называют ссылочным типом. Это обычное четырехбайтное число, содержащее адрес, с которого начинается динамическая переменная. Адрес в архитектуре процессоров фирмы Intel – два целых беззнаковых числа, определяющие номер сегмента памяти и смещение внутри этого сегмента. Указатели используются для установления отношений или связей между динамическими структурами данных. Эти связи могут быть весьма сложными.

Обозначим динамическую структуру, называемую узлом, как:



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

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

Пример общего случая использования динамической структуры: ввод данных, располагаемых в порядке возрастания: 50, 40, 10, 20, 30.











Описание указателей:

Туре <имя_указателя>=^<базовый_тип>

Например:

Var z:^real;

Type poin=^T;

T=Record

str:string;

pnt:poin;

end;


Var p,q:Poin;

Здесь p и q – указатели на переменную типа Т (запись), чтобы обратиться к самой переменной, записывается p^, q^ (см. далее).



Замечание 1. Правило языка Паскаль: имя любого типа сначала должно быть определено, и лишь затем использовано. Единственное исключение – при определении ссылочного типа можно использовать имя типа, который определяется далее в тексте программы.

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

Для того чтобы присвоить переменной ссылочного типа определенное значение, необходимо воспользоваться операцией взятия адреса (указателя), – символа амперсанд «@» и переменной базового типа, например:

z:=@x;

Ссылочные типы можно образовывать от любых других типов, поэтому допустимо определение вида «указатель на указатель». Например, фрагмент программы:



Type p1=^integer;

Var pp1:^p1;

i:integer;

Begin


p1:=@i;

pp1:=@p1;

приводит к следующим связям:

Над значениями ссылочных типов допускаются только две операции сравнения на равенство и неравенство. Они проверяют, ссылаются ли два указателя на одно и то же место в памяти, или нет, например:

Sign:=P1=P2;

If P1<>nil then ...


Работа с динамическими переменными


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

p1:=@i;


Первый вариант очевиден:

i:=i+2;


Но, хотя на i ссылается указатель, переменная описана как статическая.

Для реализации косвенного доступа к переменной через указатель используется разыменование. То есть, чтобы по указателю получить доступ к переменной, необходимо после указателя поставить знак «^». Так, запись р1^ означает: «переменная, на которую ссылается р1». Поэтому операторы

i:=i+2 и p1^:=p1^+2

полностью эквивалентны. Разыменование имеет тип, совпадающий с базовым типом, в частности p1^ является переменной целого типа (по описанию выше).

Разыменование допустимо для любых ссылочных типов, например, возможны следующие конструкции:

j:=pp1^^;

f:=h.l^.g^.p;

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


Основные действия над динамическими переменными — это их создание и уничтожение. Первое реализуется стандартной процедурой

New (<указатель>)

При выполнении этой процедуры происходят следующие действия:



  1. В динамической области памяти отводится место для переменной по размеру базового типа указателя.

  2. Указателю присваивается адрес этой переменной.

Пример.

Var I,J: ^integer; { определяем два указателя на целую переменную }

NEW (J); { выделяем память для целой переменной и присваиваем ее адрес указателю }

I:=J; { I и J указывают на одну и ту же переменную (J:=I —запрещено) }

J^:=5; { запись числа в выделенную память }

I^:=6; { стало и J^=6 }

I:=NIL; { I ни на что не указывает }

J:=NIL; { доступа к переменной больше нет, она не нужна }

При использовании динамических переменных происходит постоянный процесс изменения указателей. При этом некоторые переменные могут оказаться ненужными, ссылки на них уничтожаются. Но они занимают память, которую ни для каких других целей использовать нельзя, то есть образуют «мусор». Таким образом может возникнуть ситуация, что памяти более чем достаточно, но для размещения новой переменной в динамической области памяти, называемой «кучей», нет места. В этом случае значение указателя из параметра не изменится, но никаких сообщений выдано не будет, что может привести к непредсказуемым последствиям. Для повышения надежности программы следует проверять текущее состояние «кучи» перед каждым созданием новой переменной. Это можно сделать с помощью стандартной функции MaxAvail без параметров, возвращающей максимальный размер непрерывного участка свободной памяти, пригодного для размещения переменной. Например, для 4-байтовой переменной типа longint:

Var p:longint;

Begin

...


If MaxAvail >=4 then

New(p)


else

Writeln (‘Переполнение памяти’);

В общем случае для структурированных типов при определении размера выделяемой памяти можно использовать функцию SizeOf (определение размера переменной):

If MaxAvail >= SizeOf (TypeDat) then ...

Для освобождения памяти, выделенной под динамическую переменную, используется процедура, обратная по действию процедуре New:

Dispose (<указатель>);

Работа с динамическими переменными требует большой аккуратности, иначе «засоре­ние» памяти ненужными переменными может привести к быстрому ее исчерпанию. Трудно обнаруживаемые ошибки могут возникать при использовании динамических переменных в подпрограммах: при выходе из них локальные ссылки на вновь созданные переменные теряются. Поэтому нужно придерживаться правила: при выходе из подпрограммы или освобождается память от вновь созданных в ней переменных, или ссылки сохраняются через параметры.


Пример двухсвязанного циклического списка


Определим функции при работе со списком с пояснениями, представленными на рис. 7.1:

0 - просмотр влево (подпрограмма PRL);

1 - просмотр вправо (подпрограмма PRR);

2 - удаление (подпрограмма DEL);

3 - вставка (подпрограмма BCT);

4 - конец работы.

При проходе первого элемента переводится строка.



Рис. 7.1. Операции с узлами двухсвязанного циклического списка

Program SW_Spisok2;

{ Демонстрационный пример использования динамических переменных: двухсвязанный циклический список }

Uses crt;

Type P=^dat;

dat=Record

INF:char; { любой нужный тип элемента списка }

L,R:P;

end;


Var Point, { указатель на начало кольца }

lef,rig,n:P;

ch:char;

Procedure PRL; { процедура сдвига влево }

Begin

rig:=lef;



lef:=rig^.l; { сдвиг указателей }

Write(lef^.inf);{ вывод информационного поля }

If lef=Point then

Writeln; { начало кольца }

end;

Procedure PRR; { процедура сдвига вправо }



Begin

lef:=rig;

rig:=lef^.r;

Write(lef^.inf);

If lef=Point then

Writeln;


end;

Procedure DEL; { процедура удаления }

Begin

If lef^.l=lef^.r then{ не менее 2х элементов }



Writeln('Осталось два элемента')

else


If lef=Point then

Writeln('Начало списка')

else

Begin


rig^.l:=lef^.l; { изменение указателей }

lef^.l^.R:=lef^.r;

Dispose(lef); { возвращение памяти }

lef:=Rig^.l;

end;

end;


Procedure BCT; { процедура вставки }

Begin


ch:=ReadKey; Write(ch);

NEW(n);


n^.inf:=ch;

n^.l:=lef; { связи из нового узла }

n^.r:=rig;

rig^.l:=n; { указание на новый узел }

lef^.r:=n;

rig:=n;


writeln;

end;


Begin { непосредственно сама программа }

ch:=ReadKey; Write(ch);

NEW(rig);

rig^.inf:=ch; { первый правый узел }

ch:=ReadKey; Write(ch);

NEW(lef);

lef^.inf:=ch; { первый левый узел }

rig^.l:=lef;

rig^.r:=lef;

lef^.l:=rig;

lef^.r:=rig;

Point:=lef; { начало кольца }

Repeat

Ch:=ReadKey;



Case ch of { анализируем введенную команду }

'0': PRL;

'1': PRR;

'2': DEL;

'3': BCT;

'4': Writeln('Конец')

else

Writeln('Неверный ввод')



end;

Until ch='4'; { пока не встретим команду «конец» }

end.

Указатели без типа


Турбо Паскаль содержит стандартный ссылочный тип, который позволяет не конкретизировать базового типа и считается совместимым со всеми ссылочными типами.

Var <имя_указателя> : pointer;

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

Для создания и удаления переменных с такими указателями соответственно используются процедуры:



GetMem (<указатель>,<размер>);

FreeMem (<указатель>,<размер>);

Размер участка памяти указывается в байтах, обычно с применением функции SizeOf:



GetMem (Ptr,SizeOf(R));

Для работы с нетипизированными указателями используются дополнительные функции, здесь не рассматриваемые.


Контрольные вопросы


  1. Что означает термин «статические переменные»?

  2. Как производится обращение к статическим переменным?

  3. Что означает термин «динамические переменные»?

  4. Как производится обращение к динамическим переменным?

  5. В каких случаях используются динамические переменные?

  6. Что такое «указатели»?

  7. Для каких целей используются указатели?

  8. Что такое «пустой указатель», и как он обозначается?

  9. Для чего используется операция взятия адреса?

  10. Что такое «разыменование»?

  11. При каком значении указателя его нельзя разыменовывать?

  12. Как выглядит процедура создания динамической переменной?

  13. Что происходит при выполнении процедуры создания динамической переменной?

  14. Что можно определить с помощью функции MaxAvail?

  15. Как выглядит процедура уничтожения динамической переменной?

  16. Что происходит при выполнении процедуры уничтожения динамической переменной?

  17. С помощью какой функции можно определить максимальный размер непрерывного участка свободной памяти?

  18. Какой тип совместим со всеми ссылочными типами?





Достарыңызбен бөлісу:
1   ...   12   13   14   15   16   17   18   19   ...   24




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет