Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

infp-14-m

.pdf
Скачиваний:
8
Добавлен:
05.06.2015
Размер:
284.16 Кб
Скачать

«Информатика» для студентов групп Ф2-ХХ

Методические указания

Лабораторная работа 14

Комбинированный тип (запись). Указатели и динамические структуры данных.

Описание типа данных запись

Описание типа

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

Для использования записей, вначале необходимо описать тип записи:

type

ИмяТипа = record ИмяПоля1 : ТипПоля1; ИмяПоля2 : ТипПоля2;

...

ИмяПоляN : ТипПоляN; end;

var

ИмяПеременной : ИмяТипа;

Например, опишем личную карточку успеваемости студента:

type

 

 

TStudentCard = record

 

SurName

: string[20]; {фамилия}

Name

: string[20]; {имя}

BirthYear

: integer; {год рождения}

HomeAddress

: string; {домашний адрес}

GroupCode

: string[7]; {шифр группы}

MathAnal

: byte;

{оценка по Мат.анализу}

LinAlg

: byte;

{оценка по Лин.алгебре}

Phys

: byte;

{оценка по Физике}

Inform

: byte;

{оценка по Информатике}

end;

 

 

var

st1, st2 : TStudentCard;

Поля-записи

В приведенном примере поля, несущие одинаковую смысловую нагрузку (MathAnal, LinAlg, Phys, Inform), целесообразно объединить в отдельную структуру данных типа запись:

1

type

TStudentCard = record

SurName

: string[20]; {фамилия}

Name

: string[20]; {имя}

BirthYear

: integer; {год рождения}

HomeAddress

: string; {домашний адрес}

GroupCode

: string[7]; {шифр группы}

Marks : record {оценки}

MathAnal

: byte; {по Мат.анализу}

LinAlg

: byte; {по Лин.алгебре}

Phys

: byte; {по Физике}

Inform

: byte; {по Информатике}

end;

 

end;

А лучше описать следующим образом:

type

TMarks = record {тип оценки}

MathAnal

: byte; {по

Мат.анализу}

LinAlg

: byte; {по

Лин.алгебре}

Phys

: byte; {по

Физике}

Inform

: byte; {по Информатике}

end;

 

 

TStudentCard = record

 

SurName

: string[20]; {фамилия}

Name

: string[20]; {имя}

BirthYear

: integer; {год рождения}

HomeAddress : string;

{домашний адрес}

GroupCode

: string[7]; {шифр группы}

Marks

: TMarks;

{оценки}

end;

 

 

Работа с полями

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

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

var

stud1, stud2: TStudentCard;

Тогда будут корректными следующие операторы и обращения к полям записей:

begin stud1.Name:=’Сергей’; stud1.BirthYear:=1990; stud1.Marks.Inform:=5; stud2.Marks:=stud1.Marks; stud2:=stud1;

...

end.

2

Массив записей

Работа с массивами записей

Для последнего варианта описания карточки студента объявим тип-массив и переменные-массивы:

type

TGroup = array[1..25] of TStudentCard; var

group1, group2: TGroup;

Тогда будут корректными следующие операторы и обращения к полям записей:

begin

group1[1].SurName:= ’Иванов’; group1[1].Name:= ’Алексей’; group1[1].BirthYear:=1988; group1[1].Marks.MathAnal:=5; group1[1].Marks.Inform:=4;

...

group2[2].Marks:=group1[1].Marks;

...

group2[5]:=group1[3];

...

group1:=group2;

...

end.

Оператор присоединения with

Для упрощения доступа к полям записей и придания программе большей наглядности используется оператор присоединения with. Синтаксис оператора присоединения:

with запись do оператор;

with запись do begin {составной оператор}

оператор; оператор;

...

end;

Например, в предыдущем фрагменте программы можно сократить текст:

with group1[1] do begin SurName:= ’Иванов’; Name:= ’Алексей’; BirthYear:=1988; Marks.MathAnal:=5; Marks.Inform:=4;

end;

3

Распределение памяти и указатели

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

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

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

кучей (heap). Работать с переменными в куче можно только через указатели.

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

Указатели – это особый тип данных (pointer) для хранения адресов ячеек памяти, в которых находятся другие переменные. Адресом переменной является адрес первого байта ячейки памяти, которая под нее отводится. Для данных структурных типов (массивов и записей) их адресом считается адрес первого байта первого элемента.

Для хранения каждого указателя выделяется 4 байта памяти.

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

Примеры объявления указателей:

var

{указатели на переменные целого типа}

p1,p2: ^integer;

p3: ^string;

{указатель на строку}

p: pointer;

{нетипизированный указатель}

Тип pointer совместим со всеми типами указателей.

Создание и удаление динамических переменных

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

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

 

Выделение памяти.

new(p);

 

Выделяется столько байтов памяти, сколько требуется

 

для хранения переменной заданного типа. Указателю p

 

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

dispose(p);

Освобождение памяти.

 

Освобождается выделенная память, на которую

 

указывает p.

 

4

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

 

Выделение памяти.

getmem(p,size);

 

Выделяется столько байтов памяти, сколько указано в

 

параметре size. Указателю p присваивается адрес

 

первого байта выделенной области.

freemem(p,size);

Освобождение памяти.

 

Освобождается size байтов, начиная с адреса p.

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

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

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

пустой (нулевой) адрес с именем nil.

Состояние указателя

Переменная-указатель может находиться в одном из трех состояний:

содержать адрес переменной, память под которую уже выделена;

содержать пустой адрес nil;

находиться в неопределенном состоянии.

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

nil, а также после освобождения области памяти, на которую он указывает.

Операции с указателями

Примеры допустимых операций:

(Дляописанныхвышеуказателей)

p1:=p2;

p:=p3; p1:=p; p2:=nil; p:=nil; var x,y:integer;

... px:^integer; px:=@x;

...

y:=px^;{y=x} p1^:=5; p2^:=2; x:=p1^+3*p2^+4;{x=15}

var b:boolean;

...

b:=p1<>p2;

if p1=nil then ...

Присваивание ( □:=)

Указателю допускается присвоить значения только того же типа или типа pointer, а также значение nil.

Получение адреса ( @)

Результат унарной операции @ – указатель типа pointer, который можно присвоить любому указателю.

Разыменование ( ^ )

Результат унарной операции ^ – доступ к переменной,

на которую ссылается указатель. Нетипизированные указатели разыменовывать нельзя.

Отношение ( =, <>)

Проверка равенства = и неравенства <> адресов. Результат логического типа (true, false).

Действия с указателями

Разберем пример простейших действий с указателями:

5

type

pint=^integer; {описание типа указателя}

var

{описание указателей}

p1,p2: pint;

begin

{выделение

1-й ячейки памяти по указателю p1}

new(p1);

new(p2);

{выделение

2-й ячейки памяти по указателю p2}

p1^:=10;

{занесение

данных в 1-ю ячейку}

p2^:=20;

{занесение данных во 2-ю ячейку}

p1^:=p2^;

{копирование данных из 2-й ячейки в 1}

dispose(p1);

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

p1:=p2;

{копирование адреса 2-й ячейки в указатель p1}

 

{Теперь оба указателя ссылаются на 2-ю ячейку.}

p1^:=p1^+p2^+5; {изменение данных во 2-й ячейке: 20+20+5=45 }

p2:=nil;

{обнуление указателя p2 (nil - пустой адрес)}

dispose(p1);

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

p1:=nil;

{обнуление указателя p1 (nil - пустой адрес)}

...

 

 

end.

 

 

Выделение и освобождение памяти

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

... Недопустимы ситуации, когда ссылки на выделенную память теряются: new(p1); {выделение памяти}

p1:=p2; {переопределение указателя}

...

здесь переопределен p1 – единственный указатель, который ссылался на память, выделенную командой new(p1). Ссылка на эту память утеряна.

Еще один пример утери ссылок из-за переопределения указателя: for i:=1 to 100 do new(p);

На каждой новой итерации данного цикла указатель p переопределяется – теряется адрес ячейки, выделенной на предыдущей итерации. При достаточно большом количестве итераций подобный цикл может заполнить всю кучу.

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

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

Поэтому сразу после команды dispose(p) принято обнулять (или

переопределять) указатель p:

...

dispose(p); {освобождение памяти}

p:=nil; {обнуление указателя}

...

Это считается хорошим стилем программирования.

6

Связанные динамические данные

Основные определения

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

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

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

Очередь (queue) – частный случай линейного односвязного списка, организованного по принципу “first in, first out” (FIFO, «первым пришел, первым ушел»). Для очереди разрешено только два действия:

добавление элемента в конец (хвост) очереди,

удаление элемента из начала (головы) очереди.

Стек (stack) – частный случай линейного односвязного списка, организованного по принципу “last in, first out” (LIFO, «последним пришел,

первым ушел»). Для стека разрешено добавлять и удалять элементы только с одного конца списка, который называется вершиной (головой) стека.

Организация связей

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

динамическое выделение и освобождение памяти под элементы в любой момент работы программы;

установление связей между любыми двумя элементами с помощью

указателей.

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

В простейшем случае элемент динамической структуры данных должен состоять из двух полей: информационного (inf) и указательного (link).

Схематичное изображение такой структуры данных:

inf

inf

link

link

inf

link

inf

nil

Соответствующее ей объявление в разделе описаний:

type

= ^TElem;

{TPtr

- тип указателя на элемент}

TPtr

TElem = record

{TElem - тип элемента (запись)

}

inf

: integer;

{

inf

- информационное поле

}

link : TPtr;

{

link - указательное поле

}

end;

 

 

 

 

 

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

7

BegQ:=p;
EndQ:=p;

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

перед описанием типа самого элемента.

Очередь

Указатели очереди

Для создания очереди (queue) и работы с ней необходимо иметь как минимум два указателя:

на начало очереди (назовем его BegQ, от begin of queue),

на конец очереди (назовем EndQ end of queue).

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

ситуациях для удобства работы с очередью.

Создание очереди

1. Исходное состояние.

BegQ

 

EndQ

 

p

BegQ:=nil;

nil

 

nil

 

?

EndQ:=nil;

?

2.Выделение памяти под первый элемент очереди.

BegQ

 

EndQ

p

 

 

 

 

 

new(p);

nil

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

inf

?

 

 

 

 

link

 

 

 

 

 

 

 

?

 

3.

Занесение данных в первый элемент очереди.

BegQ

 

EndQ

p

p^.inf:=3;

nil

 

 

nil

 

 

 

 

 

 

p^.link:=nil;

 

 

 

 

 

 

 

 

 

 

 

inf

3

 

 

 

 

 

link

nil

 

4.

Установка указателей BegQ и EndQ на созданный первый элемент.

BegQ

 

EndQ

p

 

 

 

 

 

 

 

 

inf 3 link nil

8

Добавление элемента очереди

1. Исходное состояние.

 

p

BegQ

 

 

 

EndQ

 

 

 

 

 

 

 

?

 

 

 

 

 

?

2

 

4

 

3

 

 

 

 

nil

 

 

2. Выделение памяти под новый элемент и занесение в него данных.

BegQ

 

EndQ

 

p

new(p);

 

 

 

 

 

 

 

 

 

 

p^.inf:=5;

 

 

 

 

 

p^.link:=nil;

2

4

3

5

 

 

nil

nil

3. Установка связи между последним элементом очереди и новым, а также

перемещение указателя EndQ на новый элемент.

 

 

BegQ

 

EndQ

 

p

EndQ^.link:=p;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

EndQ:=p;

2

4

3

5

 

 

 

nil

Удаление элемента очереди

1. Исходное состояние.

p

BegQ

EndQ

?

?

2

4

3

5

 

 

 

 

nil

 

2. Извлечение информации из удаляемого элемента в переменную val и

установка на него вспомогательного указателя p.

EndQ

p

BegQ

 

 

 

 

 

 

val:=BegQ^.inf;

val

 

 

p:=BegQ;

4

3

5

2

2

 

 

 

nil

9

3. Перестановка указателя BegQ на следующий элемент, используя значение поля link удаляемого элемента. Освобождение памяти удаляемого элемента p.

p

BegQ

EndQ

BegQ:=p^.link;

dispose(p);

val

2

4

3

5

2

 

 

 

nil

Стек

Указатели стека

Для работы со стеком (stack) необходимо иметь один указатель на вершину стека (назовем его Top). Также потребуется один дополнительный

указатель (p), который используется для выделения и освобождения памяти элементов стека.

Создание стека

1. Исходное состояние.

Top

p

Top:=nil;

nil ?

?

2.Выделение памяти под первый элемент стека.

Top p

 

 

 

new(p);

nil

 

 

 

 

inf ?

link

?

3. Занесение данных в первый элемент стека.

Top

p

p^.inf:=5;

nil

 

 

 

 

p^.link:=nil;

inf 5 link nil

10

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