Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
6.DOC
Скачиваний:
0
Добавлен:
09.11.2018
Размер:
100.86 Кб
Скачать

6.8. Ссылочные типы

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

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

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

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

Если typnam - имя типа, то ссылочный тип обозначается ^typnam , а для объявления переменных ссылочного типа используется запись

VAR p : ^typnam ,

а сама динамическая переменная обозначается p^.

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

Динамическая переменная порождается в программе стандартной процедурой New(p) . При этом в динамической памяти переменной типа typnam отводится мес-то, а переменной-указателю присваивается ссылочное значение на эту пере-менную (т.е. значение ее адреса).

Освобождение памяти, занимаемой динамической переменной, реали-зуется процедурой Dispose(p) , где p - указатель на эту переменную.

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

PROGRAM Dyn;

CONST n=25;

TYPE rea = ARRAY[1..n] OF real;

int = ARRAY[1..n] OF integer;

VAR p : ^int;

q : ^rea;

a : rea;

k : integer;

BEGIN

FOR k:=1 TO n DO Read(a[k]);

ReadLn;

{ Построение двух массивов в динамической памяти }

New(p);

New(q);

FOR k:=1 TO n DO

BEGIN p^[k]:=Trunc(a[k]);

q^[k]:=a[k]-p^[k];

END;

{ Распечатка результирующих массивов }

FOR k:=1 TO n DO

WriteLn(k:4, p^[k]:7, q^[k]);

{ Освобождение динамической памяти }

Dispose(p);

Dispose(q)

END.

Задачи

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

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

6.8.2. Линейные списки

Пусть необходимо хранить данные о группе студентов. Вполне естественный прием - организовать массив. Но тут возникает ряд проблем.

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

Вторая проблема: каким образом фиксировать факт исключения студента из группы или включения нового.

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

Пример 6.8.2. Демонстрация работы с линейным однонаправленным списком .

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

PROGRAM SpisStud;

CONST n=25;

TYPE spis=^student;

student=RECORD

fam:STRING[20];

rat:integer;

next:spis

END;

VAR p,q,first : spis;

gran,k : integer;

BEGIN { Ввести данные о группе студентов. Построить список.} { Реализована схема включения очередного элемента в } { начало списка (рис.6.5) }

first:=NIL;

FOR k:=1 TO n DO

BEGIN

New(p);

p^.next:=first;

Write('фамилия '); ReadLn(p^.fam);

Write('рейтинг '); ReadLn(p^.rat);

first:=p

END;

студ студ студ

first n n-1 1

NIL

Рис.6.5. Построение линейного однонаправленного списка

{ Удалить из списка всех студентов, чей рейтинг ниже } {заданного (см. рис.6.6) }

first

NIL

p а) до удаления элемента

first

NIL

p

б) после удаления элемента

Рис.6.6. Удаление элемента из списка

Write('Граничный рейтинг ? '); ReadLn(gran);

p:=first;

WHILE p^.next <> NIL DO

IF p^.next^.rat < gran

THEN BEGIN

q:=p^.next;

p^.next:=p^.next^.next;

Dispose(q)

END

ELSE p:=p^.next;

IF first^.rat < gran

THEN BEGIN

q:=first;

first:=first^.next;

Dispose(q)

END;

{ Включить в список новый элемент после того,на который } { показывает текущий указатель (см.рис.6.7) } { Вставим элемент с данными первого после второго }

p:=first^.next;

New(q);

q^:=first^;

q^.next:=p^.next;

p^.next:=q;

first

NIL

p новый q а) до включения элемента

first

NIL

p

новый q б) после включения элемента

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

{Распечатать данные обо всех студентах списка}

p:=first;

WHILE p<>NIL DO

BEGIN

WriteLn(p^.fam, p^.rat:10);

p:=p^.next { продвижение по списку }

END;

END.

Здесь мы столкнулись с рекурсивным описанием типа: тип spis - ссылочный тип на еще не определенный тип student, в то же время тип student содержит поле типа spis.

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

Удаление элемента, следующего за тем, на который указывает текущий указатель p, производится "переключением" ссылки на следующий (рис.6.6). Первый элемент не вписывается в эту схему, поэтому его удаление производится отдельно.

Включение элемента осуществляется изменением ссылок.

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

NIL back back back

· · ·

first NIL

forw forw forw

Рис.6.8. Линейный двунаправленный список

Пример 6.8.3. Демонстрация линейного двунаправленного списка.

PROGRAM ListTwo;

CONST n=25;

TYPE list = ^stud;

stud = RECORD

fam : STRING[20];

rat : integer;

forw,back : list {2 указателя: вперед-назад}

END;

VAR p,first : list;

k : integer;

BEGIN

{ Построение списка }

first:=NIL;

FOR k:=1 TO n DO

BEGIN

New(p);

ReadLn(p^.fam);

ReadLn(p^.rat);

p^.forw:=first;

IF first <> NIL THEN first^.back:=p;

p^.back:=NIL;

first:=p

END;

{ Отпечатать данные о трех последних в списке }

p:=first;

WHILE p^.forw <> NIL DO p:=p^.forw;

FOR k:=1 TO 3 DO

BEGIN

WriteLn(p^.fam, p^.rat:10);

p:=p^.back

END

END.

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

Задачи

6.8.3. Имеется линейный список. Необходимо: 1) определить количество элементов в списке; 2) включить новый элемент в конец списка; 3) включить новый элемент перед тем, на который показывает текущий указатель; 4) удалить первый элемент; 5) удалить последний элемент; 6) определить фамилию студента с самым высоким рейтингом.

6.8.4. Имеется два списка. Слить их в один.

6.8.5. Имеется двунаправленый список. Необходимо: 1) включить новый элемент в начало списка; 2) включить новый элемент в конец списка; 3) удалить из списка студента с заданной фамилией; 4) изменить значение рейтинга студента с заданной фамилией; 5) изменить фамилию студента; 6) определить фамилию студента с самым высоким рейтингом; 7) слить воедино два списка.

6.8.6. Имеется двунаправленый список. Преобразовать его в кольце-вой список, соединив ссылками первый и последний элементы, после чего: 1) определить фамилию студента с самым высоким рейтингом; 2) включить в список еще одного студента.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]