- •Глава 6
- •6.1. Понятие структурного типа
- •6.2. Регулярные типы (массивы)
- •6.3. Строковый тип
- •6.4. Комбинированные типы (записи)
- •6.4.1. Определение типа "запись"
- •6.4.2. Записи с вариантами
- •6.4.3. Оператор присоединения
- •6.5. Множественные типы
- •6.6. Файловые типы
- •6.7. Текстовые файлы
- •6.7.1. Определение текстового файла
- •6.7.2. Ввод числовых данных из текстовых файлов
- •6.7.3. Вывод числовых данных в текстовый файл
- •6.8. Ссылочные типы
- •6.8.1. Динамические переменные
- •6.8.2. Линейные списки
- •6.9. Типизированные константы
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) включить в список еще одного студента.