Презентацию к данной работе Вы можете скачать здесь.
В PC-совместимых компьютерах память условно разделена на сегменты. Компилятор формирует сегменты кода, данных и стека, а остальная доступная программе память называется динамической (хипом, кучей ). Ее можно использовать во время выполнения программы.
В программах, приведенных ранее, для хранения данных использовались простые переменные, массивы или записи. По их описаниям в разделе var компилятор определяет, сколько места в памяти необходимо для хранения каждой величины. Такие переменные можно назвать статическими. Распределением памяти под них занимается компилятор, а обращение к этим переменным выполняется по имени. Динамические переменные создаются в хипе во время выполнения программы. Обращение к ним осуществляется через указатели.
С помощью динамических переменных можно обрабатывать данные, объем которых до начала выполнения программы не известен. Память под такие данные выделяется порциями, или блоками, которые связываются друг с другом. Такой способ хранения данных называется динамическими структурами, поскольку их размеры изменяются в процессе выполнения программы.
Имя переменной служит для обращения к области памяти, которую занимает ее значение. Программист может определить собственные переменные для хранения адресов областей памяти. Такие переменные называются указателями. В указателе можно хранить адрес данных или программного кода (например, адрес точки
Указатели в Паскале можно разделить на два вида: стандартные и определяемые программистом. Величины стандартного типа pointer предназначены для хранения адресов данных произвольного типа, например:
var p : pointer;
Программист может определить указатель на данные или подпрограмму конкретного типа. Как и для других нестандартных типов, это делается в разделе type:
type pword = ^word; { читается как 'указатель на word' }
...
var pw : pword;
Здесь определяется тип pword как указатель на величины типа word. В переменной pw можно хранить только адреса величин указанного типа. Такие указатели называются типизированными. Можно описать указатель на любой тип данных, кроме файловых.
Аналогично другим типам, тип указателя на данные можно описать и непосредственно при описании переменной, например:
var pw : ^word;
Для указателей определены только операции присваивания и проверки на равенство и неравенство. В Паскале, в отличие от других языков, запрещаются любые арифметические операции с указателями, их ввод-вывод и сравнение на больше-меньше. Рассмотрим правила присваивания указателей.
nil, которая означает, что указатель не ссылается на какую-либо конкретную ячейку памяти.pointer совместимы с указателями любого типа.Операция @ и функция addr позволяют получить адрес переменной, например:
var x : word; { переменная }
pw : ^word; { указатель на величины типа word }
...
pw := @w; { или pw := addr(w); }
Для обращения к значению переменной, адрес которой хранится в указателе, примеряется операция разадресации ( разыменования ), обозначаемая с помощью символа ^ справа от имени указателя, например:
pw^ := 2; inc(pw^); writeln(pw^);
В первом операторе в ячейку памяти, адрес которой хранится в переменной pw, заносится число 2. При выполнении оператора вывода на экране появится число 3.
С величинами, адрес которых хранится в указателе, можно выполнять любые действия, допустимые для значений этого типа. Для простоты восприятия можно считать имя указателя со следующей за ним "крышкой" просто именем переменной, на которую он указывает, хотя на самом деле это, конечно, не так.
Указатели можно сравнивать на равенство и неравенство, например:
if p1 = p2 then ... if p <> nil then ...
Динамические переменные создаются в хипе во время выполнения программы с помощью подпрограмм new или getmem.
new (var p : тип_указателя) выделяет в динамической памяти участок размера, достаточного для размещения переменной того типа, на который ссылается указатель p, и заносит в него адрес начала этого участка.Функция new(тип_указателя) : pointer выделяет в динамической памяти участок размера, достаточного для размещения переменной базового типа для заданного типа указателя, и возвращает адрес начала этого участка.
Если в new использовать указатель стандартного типа pointer, память фактически не выделяется, а при попытке работать с ней выдается ошибка. Поэтому процедура и функция new обычно применяются для
getmem (var p : pointer; size : word) выделяет в динамической памяти участок размером в size байт и присваивает адрес его начала указателю p. Эту процедуру можно применять и для указателей типа pointer, поскольку количество выделяемой памяти задается в явном виде.Если выделить требуемый объем памяти не удалось, программа аварийно завершается.
Рассмотрим пример работы с динамическими переменными. Определим в разделе описания переменных главной программы три указателя p1, p2 и p3.
type rec = record
d : word;
s : string;
end;
pword = ^word;
var p1, p2 : pword;
p3 : ^rec;
Это — обычные статические переменные, компилятор выделяет под них в сегменте данных по четыре байта и обнуляет их (рис 5.1).
(рис 5.1) Размещение указателей в памяти
В разделе исполняемых операторов программы запишем операторы:
new(p1); p2 := new(pword); new(p3);
В результате выполнения процедуры new(p1) в хипе выделяется объем памяти, достаточный для размещения переменной типа word, и адрес начала этого участка памяти записывается в переменную p1. Второй оператор выполняет аналогичные действия, но используется функция new. При вызове процедуры new с параметром p3 в динамической памяти будет выделено количество байтов, достаточное для размещения записи типа rec.
Доступ к выделенным областям осуществляется с помощью операции разадресации:
p1^ := 2; p2^ := 4; p3^.d := p1^; p3^.s := 'Вася';
В этих операторах в выделенную память заносятся значения (рис 5.2).
(рис 5.2) Выделение и заполнение динамической памяти
inc(p1^); p2^ := p1^ + p3^.d; with p3^ do writeln (d, s);
(рис 5.3) Мусор
Для освобождения динамической памяти используются процедуры Dispose и Freemem, причем если память выделялась с помощью new, следует применять Dispose, в противном случае — Freemem.
Dispose (var p : pointer) освобождает участок памяти,
выделенный для размещения динамической переменной процедурой или функцией New,
и значение указателя p становится неопределенным.Freemem (var p : pointer; size : word) освобождает участок памяти размером size, начиная с адреса, находящегося в p. Значение указателя становится неопределенным.При завершении программы используемая ею динамическая память освобождается автоматически, поэтому явным образом освобождать ненужную память необходимо только в том случае, если она может потребоваться при дальнейшем выполнении программы.
Если до начала работы с данными невозможно определить, сколько памяти потребуется для их хранения, память следует распределять во время выполнения программы по мере необходимости отдельными блоками. Блоки связываются друг с другом с помощью указателей. Такой способ организации данных называется динамической структурой данных, поскольку она размещается в динамической памяти и ее размер изменяется во время выполнения программы.
Из динамических структур в программах чаще всего используются линейные списки, стеки, очереди и бинарные деревья. Они различаются способами связи отдельных элементов и допустимыми операциями. Динамическая структура, в отличие от массива или записи, может занимать несмежные участки оперативной памяти.
Динамические структуры широко применяют и для более эффективной работы с данными, размер которых известен, особенно для решения задач сортировки, поскольку упорядочивание динамических структур не требует перестановки элементов, а сводится к изменению указателей на эти элементы. Например, если в процессе выполнения программы требуется многократно упорядочивать большой массив данных, имеет смысл организовать его в виде линейного списка. При решении задач поиска элемента в тех случаях, когда важна скорость, данные лучше всего представить в виде бинарного дерева.
Элемент динамической структуры состоит из двух частей: информационной, ради хранения которой и создается структура, и указателей, обеспечивающих связь элементов друг с другом. Элемент описывается в виде записи, например:
type
pnode = ^node;
node = record
d : word; { информационная }
s : string; { часть }
p : pnode; { указатель на следующий элемент }
end;
pnode на запись node определен раньше, чем сама запись. Это не противоречит принципу "использование только после описания", поскольку для описания переменной типа pnode информации вполне достаточно.Рассмотрим принципы работы с основными динамическими структурами.
Стек является простейшей динамической структурой. Добавление элементов в стек и выборка из него выполняются из одного конца, называемого вершиной стека. Другие операции со стеком не определены. При выборке элемент исключается из стека.
Говорят, что стек реализует принцип обслуживания LIFO (last in — first out, последним пришел — первым обслужен). Стеки широко применяются в системном программном обеспечении, компиляторах, в различных рекурсивных алгоритмах.
Для работы со стеком используются две статические переменные: указатель на вершину стека и вспомогательный указатель.
var top, p : pnode;
Тип указателей должен соответствовать типу элементов стека.
В приведена программа, которая формирует стек из пяти целых чисел и их текстового представления и выводит его на экран. Функция занесения в стек по традиции называется push, а функция выборки — pop.
program stack;
const n = 5;
type pnode = ^node;
node = record { элемент стека }
d : word;
s : string;
p : pnode;
end;
var top : pnode; { указатель на вершину стека }
i : word;
s : string;
const text : array [1 .. n] of string = ('one', 'two', 'three', 'four', 'five');
{ ------------------------------ занесение в стек --------------------------- }
function push(top : pnode; d : word; const s : string) : pnode;
var p : pnode;
begin
new(p);
p^.d := d; p^.s := s; p^.p := top;
push := p;
end;
{ ------------------------------ выборка из стека --------------------------- }
function pop(top : pnode; var d : word; var s : string) : pnode;
var p : pnode;
begin
d := top^.d; s := top^.s;
pop := top^.p;
dispose(top);
end;
{ ------------------------------- главная программа ----------------------------- }
begin
top := nil;
for i := 1 to n do top := push(top, i, text[i]); { занесение в стек: }
while top <> nil do begin { выборка из стека: }
top := pop(top, i, s); writeln(i:2, s);
end;
end.
Очередь — это динамическая структура данных, добавление элементов в которую выполняется в один конец, а выборка — из другого конца. Другие операции с очередью не определены. При выборке элемент исключается из очереди. Говорят, что очередь реализует принцип обслуживания FIFO (first in — first out, первым пришел — первым обслужен). В программировании очереди применяются очень широко — например, при моделировании,
Для работы с очередью используются указатели на ее начало и конец, а также вспомогательный указатель:
var beg, fin, p : pnode;
Тип указателей должен соответствовать типу элементов, из которых состоит очередь.
В приведена программа, которая формирует очередь из пяти целых чисел и их текстового представления и выводит еe на экран. Для разнообразия операции с очередью оформлены в виде процедур. Процедура начального формирования называется first, помещения в конец очереди — add, а выборки — get.
program queue;
const n = 5;
type pnode = ^node;
node = record { элемент очереди }
d : word;
s : string;
p : pnode;
end;
var beg, fin : pnode; { указатели на начало и конец очереди }
i : word;
s : string;
const text : array [1 .. n] of string = ('one', 'two', 'three', 'four', 'five');
{ ------------------ начальное формирование очереди --------------------------- }
procedure first(var beg, fin : pnode; d : word; const s : string);
begin
new(beg);
beg^.d := d; beg^.s := s; beg^.p := nil;
fin := beg;
end;
{ --------------------- добавление элемента в конец --------------------------- }
procedure add(var fin : pnode; d : word; const s : string);
var p : pnode;
begin
new(p);
p^.d := d; p^.s := s; p^.p := nil;
fin^.p := p;
fin := p;
end;
{ ---------------------- выборка элемента из начала --------------------------- }
procedure get(var beg : pnode; var d : word; var s : string);
var p : pnode;
begin
d := beg^.d; s := beg^.s;
p := beg; beg := beg^.p;
dispose(p);
end;
{ ------------------------------- главная программа --------------------------- }
begin
{ занесение в очередь: }
first(beg, fin, 1, text[1]);
for i := 2 to 5 do add(fin, i, text[i]);
{ выборка из очереди: }
while beg <> nil do begin
get(beg, i, s);
writeln(i:2, s);
end;
end.
В линейном списке каждый элемент связан со следующим и, возможно, с предыдущим. В первом случае список называется односвязным, во втором — двусвязным. Также применяются термины "однонаправленный" и "двунаправленный". Если последний элемент связать указателем с первым, получится кольцевой список.
Каждый элемент списка содержит ключ, идентифицирующий этот элемент. Ключ обычно бывает либо целым числом, либо строкой и является частью поля данных. В качестве ключа в процессе работы со списком могут выступать разные части поля данных. Например, если создается линейный список из записей, содержащих фамилию, год рождения, стаж работы и пол, любая часть записи может выступать в качестве ключа. При упорядочивании списка по алфавиту ключом будет фамилия, а при поиске, например, ветеранов труда — стаж. Ключи разных элементов списка могут совпадать.
Над списками можно выполнять следующие операции:
Стек и очередь представляют собой частные случаи линейного списка с ограниченным набором допустимых операций.
При чтении элемент списка не удаляется, в отличие от элемента, выбираемого из стека и очереди. Для работы со списком в программе требуется определить указатель на его начало. Чтобы упростить добавление новых элементов в конец списка, можно также завести указатель на конец списка.
Рассмотрим программу (), которая формирует
program linked_list;
const n = 5;
type pnode = ^node;
node = record { элемент списка }
d : word;
s : string;
p : pnode;
end;
var beg : pnode; { указатель на начало списка }
i, key : word;
s : string;
option : word;
const text: array [1 .. n] of string = ('one', 'two', 'three', 'four', 'five');
{ -------------- добавление элемента в конец списка --------------------------- }
procedure add(var beg : pnode; d : word; const s : string);
var p : pnode; { указатель на создаваемый элемент }
t : pnode; { указатель для просмотра списка }
begin
new(p); { создание элемента }
p^.d := d; p^.s := s; { заполнение элемента }
p^.p := nil;
if beg = nil then beg := p { список был пуст }
else begin { список не пуст }
t := beg;
while t^.p <> nil do { проход по списку до конца }
t := t^.p;
t^.p := p; { привязка нового элемента к последнему }
end
end;
{ ------------------------- поиск элемента по ключу --------------------------- }
function find(beg : pnode; key : word; var p, pp : pnode) : boolean;
begin
p := beg;
while p <> nil do begin { 1 }
if p^.d = key then begin { 2 }
find := true; exit end;
pp := p; { 3 }
p := p^.p; { 4 }
end;
find := false;
end;
{ -------------------------------- вставка элемента --------------------------- }
procedure insert(beg : pnode; key, d : word; const s : string);
var p : pnode; { указатель на создаваемый элемент }
pkey : pnode; { указатель на искомый элемент }
pp : pnode; { указатель на предыдущий элемент }
begin
if not find(beg, key, pkey, pp) then begin
writeln(' вставка не выполнена'); exit; end;
new(p); { 1 }
p^.d := d; p^.s := s; { 2 }
p^.p := pkey^.p; { 3 }
pkey^.p := p; { 4 }
end;
{ ------------------------------- удаление элемента --------------------------- }
procedure del(var beg : pnode; key : word);
var p : pnode; { указатель на удаляемый элемент }
pp : pnode; { указатель на предыдущий элемент }
begin
if not find(beg, key, p, pp) then begin
writeln(' удаление не выполнено'); exit; end;
if p = beg then beg := beg^.p { удаление первого элемента }
else pp^.p := p^.p;
dispose(p);
end;
{ ------------------------------------ вывод списка --------------------------- }
procedure print(beg : pnode);
var p : pnode; { указатель для просмотра списка }
begin
p := beg;
while p <> nil do begin { цикл по списку }
writeln(p^.d:3, p^.s); { вывод элемента }
p := p^.p { переход к следующему элементу списка }
end;
end;
{ ------------------------------- главная программа --------------------------- }
begin
for i := 1 to 5 do add(beg, i, text[i]);
while true do begin
writeln('1 - вставка, 2 - удаление, 3 - вывод, 4 - выход');
readln(option);
case option of
1: begin { вставка }
writeln('Ключ для вставки?');
readln(key);
writeln('Вставляемый элемент?');
readln(i); readln(s);
insert(beg, key, i, s);
end;
2: begin { удаление }
writeln('Ключ для удаления?');
readln(key);
del(beg, key);
end;
3: begin { вывод }
writeln('Вывод списка:');
print(beg);
end;
4: exit; { выход }
end
writeln;
end
end.
Функция поиска элемента find возвращает true, если искомый элемент найден, и false в противном случае. Поскольку одного факта отыскания элемента недостаточно, функция также возвращает через список параметров два указателя: на найденный элемент p и на предшествующий ему pp. Последний требуется при удалении элемента из списка, поскольку при этом необходимо связывать предыдущий и последующий по отношению к удаляемому элементы.
Сначала указатель p устанавливается на начало списка, и организуется цикл просмотра списка (оператор 1). Если поле данных очередного элемента совпало с заданным ключом (оператор 2), формируется признак успешного поиска и функция завершается. В противном случае перед переносом указателя на следующий элемент списка (он хранится в поле p текущего элемента, оператор 4) его значение запоминается в переменной pp (оператор 3) для того, чтобы при следующем проходе цикла в ней находился указатель на предыдущий элемент.
Если элемента с заданным ключом в списке нет, цикл завершится естественным образом, поскольку nil в поле p указателя на следующий элемент.
Вставка элемента выполняется после элемента с заданным ключом (процедура insert ). Если с помощью функции find место вставки определить не удалось, выводится сообщение и процедура завершается (в этом случае можно было использовать и другой алгоритм — добавлять элемент к концу списка, это определяется конкретным предназначением программы). Если элемент найден, указатель на него заносится в переменную pkey.
Под новый элемент выделяется место в динамической памяти (оператор 1), и информационные поля элемента заполняются переданными в процедуру значениями (оператор 2). Новый элемент ).
(рис 5.4) Вставка элемента в список
Удаление элемента выполняет процедура del. В ней две ветви, поскольку при удалении элемента из начала списка изменяется указатель на начало, а при удалении второго и последующих элементов требуется связывать предыдущий и последующий по отношению к удаляемому элементы.
Бинарное дерево — это динамическая структура данных, состоящая из узлов, каждый из которых содержит кроме данных не более двух ссылок на различные бинарные деревья. На каждый узел имеется ровно одна ссылка. Начальный узел называется корнем дерева.
Пример бинарного дерева приведен на рис 5.5 (корень обычно изображается сверху). Узел, не имеющий поддеревьев, называется листом. Исходящие узлы называются предками, входящие — потомками. Высота дерева определяется количеством уровней, на которых располагаются его узлы.
(рис 5.5) Пример бинарного дерева
Если дерево организовано таким образом, что для каждого узла все ключи
его
Дерево является
procedure print_tree( дерево );
begin
print_tree( левое_поддерево )
посещение корня
print_tree( правое_поддерево )
end;
Эта процедура позволяет получить последовательность ключей, отсортированную по возрастанию. Результат обхода дерева, изображенного на рис 5.5:
1, 6, 8, 10, 20, 21, 25, 30
Если в функции обхода первое обращение идет к правому поддереву, результат обхода будет другим:
30, 25, 21, 20, 10, 8, 6, 1
Таким образом, деревья поиска можно применять для сортировки значений. При обходе дерева узлы не удаляются.
Для бинарных деревьев определены операции:
Для простоты будем рассматривать дерево, каждый элемент которого содержит только целочисленный ключ и два указателя.
type pnode = ^node;
node = record
data : word; { ключ }
left : pnode; { указатель на левое поддерево }
right : pnode { указатель на правое поддерево }
end;
Доступ к дереву в программе осуществляется через указатель на его корень:
var root : pnode;
Рассмотрим сначала ).
function find(root : pnode; key : word; var p, parent : pnode) : boolean;
begin
p := root; { поиск начинается от корня }
while p <> nil do begin
if key = p^.data then { узел с таким ключом есть }
begin find := true; exit end;
parent := p; { запомнить указатель перед спуском }
if key < p^.data
then p := p^.left { спуститься влево }
else p := p^.right; { спуститься вправо }
end;
find := false;
end;
Функция возвращает булевский признак успешности поиска. Ей передаются указатель на корень дерева, в котором выполняется поиск ( root ), и искомый ключ ( key ). Выходными параметрами функции являются указатели на найденный элемент ( p ) и его предка ( parent ).
Удаление элемента — более сложная задача, поскольку при этом необходимо сохранить свойства дерева поиска. Эта задача подробно описана в учебнике .
В приведен пример программы работы с бинарным деревом.
program bintree;
uses crt;
type pnode = ^node;
node = record
data : word; { ключ }
left : pnode; { указатель на левое поддерево }
right : pnode { указатель на правое поддерево }
end;
var root : pnode;
key : word;
option : word;
{ ------------------------------------ вывод дерева --------------------------- }
procedure print_tree(p : pnode; level : integer);
var i : integer;
begin
if p = nil then exit;
with p^ do begin
print_tree(right, level + 1);
for i := 1 to level do write(' ');
writeln(data);
print_tree(left, level + 1);
end
end;
{ ------------------------------- поиск по дереву – см. рис. 5.5----------- }
function find(root : pnode; key : word; var p, parent : pnode) : boolean;
{ ---------------------------- включение в дерево ----------------------------- }
procedure insert(var root : pnode; key : word);
var p, parent : pnode;
begin
if find(root, key, p, parent) then begin
writeln(' такой элемент уже есть'); exit; end;
new(p); { создание нового элемента }
p^.data := key;
p^.left := nil;
p^.right := nil;
if root = nil then root := p { первый элемент }
else { присоединение нового элемента к дереву}
if key < parent^.data
then parent^.left := p
else parent^.right := p;
end;
{ ------------------------------ удаление из дерева - см. учебник --------- }
procedure del(var root : pnode; key : word);
{ ------------------------------- главная программа --------------------------- }
begin
root := nil;
while true do begin
writeln('1 - вставка, 2 - удаление, 3 - вывод, 4 - выход');
readln(option);
case option of
1: begin { вставка }
writeln('Введите ключ для вставки: '); readln(key);
insert(root, key);
end;
2: begin { удаление }
writeln('Введите ключ для удаления: '); readln(key);
del(root, key);
end;
3: begin { вывод }
clrscr;
if root = nil then writeln ('дерево пустое')
else print_tree(root, 0);
end;
4: exit; { выход }
end;
writeln;
end
end.
Рассмотрим функцию обхода дерева print_tree.
Вторым параметром в нее передается целая переменная, определяющая, на каком
уровне находится узел. Корень находится на уровне 0. Дерево печатается по
горизонтали так, что корень находится слева. Для дерева, изображенного на
рис 5.5, вывод выглядит так:
30
25
21
20
10
8
6
1
Презентацию к данной работе Вы можете скачать здесь.
В PC-совместимых компьютерах память условно разделена на сегменты. Компилятор формирует сегменты кода, данных и стека, а остальная доступная программе память называется динамической (хипом, кучей ). Ее можно использовать во время выполнения программы.
В программах, приведенных ранее, для хранения данных использовались простые переменные, массивы или записи. По их описаниям в разделе var компилятор определяет, сколько места в памяти необходимо для хранения каждой величины. Такие переменные можно назвать статическими. Распределением памяти под них занимается компилятор, а обращение к этим переменным выполняется по имени. Динамические переменные создаются в хипе во время выполнения программы. Обращение к ним осуществляется через указатели.
С помощью динамических переменных можно обрабатывать данные, объем которых до начала выполнения программы не известен. Память под такие данные выделяется порциями, или блоками, которые связываются друг с другом. Такой способ хранения данных называется динамическими структурами, поскольку их размеры изменяются в процессе выполнения программы.
Имя переменной служит для обращения к области памяти, которую занимает ее значение. Программист может определить собственные переменные для хранения адресов областей памяти. Такие переменные называются указателями. В указателе можно хранить адрес данных или программного кода (например, адрес точки
Указатели в Паскале можно разделить на два вида: стандартные и определяемые программистом. Величины стандартного типа pointer предназначены для хранения адресов данных произвольного типа, например:
var p : pointer;
Программист может определить указатель на данные или подпрограмму конкретного типа. Как и для других нестандартных типов, это делается в разделе type:
type pword = ^word; { читается как 'указатель на word' }
...
var pw : pword;
Здесь определяется тип pword как указатель на величины типа word. В переменной pw можно хранить только адреса величин указанного типа. Такие указатели называются типизированными. Можно описать указатель на любой тип данных, кроме файловых.
Аналогично другим типам, тип указателя на данные можно описать и непосредственно при описании переменной, например:
var pw : ^word;
Для указателей определены только операции присваивания и проверки на равенство и неравенство. В Паскале, в отличие от других языков, запрещаются любые арифметические операции с указателями, их ввод-вывод и сравнение на больше-меньше. Рассмотрим правила присваивания указателей.
nil, которая означает, что указатель не ссылается на какую-либо конкретную ячейку памяти.pointer совместимы с указателями любого типа.Операция @ и функция addr позволяют получить адрес переменной, например:
var x : word; { переменная }
pw : ^word; { указатель на величины типа word }
...
pw := @w; { или pw := addr(w); }
Для обращения к значению переменной, адрес которой хранится в указателе, примеряется операция разадресации ( разыменования ), обозначаемая с помощью символа ^ справа от имени указателя, например:
pw^ := 2; inc(pw^); writeln(pw^);
В первом операторе в ячейку памяти, адрес которой хранится в переменной pw, заносится число 2. При выполнении оператора вывода на экране появится число 3.
С величинами, адрес которых хранится в указателе, можно выполнять любые действия, допустимые для значений этого типа. Для простоты восприятия можно считать имя указателя со следующей за ним "крышкой" просто именем переменной, на которую он указывает, хотя на самом деле это, конечно, не так.
Указатели можно сравнивать на равенство и неравенство, например:
if p1 = p2 then ... if p <> nil then ...
Динамические переменные создаются в хипе во время выполнения программы с помощью подпрограмм new или getmem.
new (var p : тип_указателя) выделяет в динамической памяти участок размера, достаточного для размещения переменной того типа, на который ссылается указатель p, и заносит в него адрес начала этого участка.Функция new(тип_указателя) : pointer выделяет в динамической памяти участок размера, достаточного для размещения переменной базового типа для заданного типа указателя, и возвращает адрес начала этого участка.
Если в new использовать указатель стандартного типа pointer, память фактически не выделяется, а при попытке работать с ней выдается ошибка. Поэтому процедура и функция new обычно применяются для
getmem (var p : pointer; size : word) выделяет в динамической памяти участок размером в size байт и присваивает адрес его начала указателю p. Эту процедуру можно применять и для указателей типа pointer, поскольку количество выделяемой памяти задается в явном виде.Если выделить требуемый объем памяти не удалось, программа аварийно завершается.
Рассмотрим пример работы с динамическими переменными. Определим в разделе описания переменных главной программы три указателя p1, p2 и p3.
type rec = record
d : word;
s : string;
end;
pword = ^word;
var p1, p2 : pword;
p3 : ^rec;
Это — обычные статические переменные, компилятор выделяет под них в сегменте данных по четыре байта и обнуляет их (рис 5.1).
(рис 5.1) Размещение указателей в памяти
В разделе исполняемых операторов программы запишем операторы:
new(p1); p2 := new(pword); new(p3);
В результате выполнения процедуры new(p1) в хипе выделяется объем памяти, достаточный для размещения переменной типа word, и адрес начала этого участка памяти записывается в переменную p1. Второй оператор выполняет аналогичные действия, но используется функция new. При вызове процедуры new с параметром p3 в динамической памяти будет выделено количество байтов, достаточное для размещения записи типа rec.
Доступ к выделенным областям осуществляется с помощью операции разадресации:
p1^ := 2; p2^ := 4; p3^.d := p1^; p3^.s := 'Вася';
В этих операторах в выделенную память заносятся значения (рис 5.2).
(рис 5.2) Выделение и заполнение динамической памяти
inc(p1^); p2^ := p1^ + p3^.d; with p3^ do writeln (d, s);
(рис 5.3) Мусор
Для освобождения динамической памяти используются процедуры Dispose и Freemem, причем если память выделялась с помощью new, следует применять Dispose, в противном случае — Freemem.
Dispose (var p : pointer) освобождает участок памяти,
выделенный для размещения динамической переменной процедурой или функцией New,
и значение указателя p становится неопределенным.Freemem (var p : pointer; size : word) освобождает участок памяти размером size, начиная с адреса, находящегося в p. Значение указателя становится неопределенным.При завершении программы используемая ею динамическая память освобождается автоматически, поэтому явным образом освобождать ненужную память необходимо только в том случае, если она может потребоваться при дальнейшем выполнении программы.
Если до начала работы с данными невозможно определить, сколько памяти потребуется для их хранения, память следует распределять во время выполнения программы по мере необходимости отдельными блоками. Блоки связываются друг с другом с помощью указателей. Такой способ организации данных называется динамической структурой данных, поскольку она размещается в динамической памяти и ее размер изменяется во время выполнения программы.
Из динамических структур в программах чаще всего используются линейные списки, стеки, очереди и бинарные деревья. Они различаются способами связи отдельных элементов и допустимыми операциями. Динамическая структура, в отличие от массива или записи, может занимать несмежные участки оперативной памяти.
Динамические структуры широко применяют и для более эффективной работы с данными, размер которых известен, особенно для решения задач сортировки, поскольку упорядочивание динамических структур не требует перестановки элементов, а сводится к изменению указателей на эти элементы. Например, если в процессе выполнения программы требуется многократно упорядочивать большой массив данных, имеет смысл организовать его в виде линейного списка. При решении задач поиска элемента в тех случаях, когда важна скорость, данные лучше всего представить в виде бинарного дерева.
Элемент динамической структуры состоит из двух частей: информационной, ради хранения которой и создается структура, и указателей, обеспечивающих связь элементов друг с другом. Элемент описывается в виде записи, например:
type
pnode = ^node;
node = record
d : word; { информационная }
s : string; { часть }
p : pnode; { указатель на следующий элемент }
end;
pnode на запись node определен раньше, чем сама запись. Это не противоречит принципу "использование только после описания", поскольку для описания переменной типа pnode информации вполне достаточно.Рассмотрим принципы работы с основными динамическими структурами.
Стек является простейшей динамической структурой. Добавление элементов в стек и выборка из него выполняются из одного конца, называемого вершиной стека. Другие операции со стеком не определены. При выборке элемент исключается из стека.
Говорят, что стек реализует принцип обслуживания LIFO (last in — first out, последним пришел — первым обслужен). Стеки широко применяются в системном программном обеспечении, компиляторах, в различных рекурсивных алгоритмах.
Для работы со стеком используются две статические переменные: указатель на вершину стека и вспомогательный указатель.
var top, p : pnode;
Тип указателей должен соответствовать типу элементов стека.
В приведена программа, которая формирует стек из пяти целых чисел и их текстового представления и выводит его на экран. Функция занесения в стек по традиции называется push, а функция выборки — pop.
program stack;
const n = 5;
type pnode = ^node;
node = record { элемент стека }
d : word;
s : string;
p : pnode;
end;
var top : pnode; { указатель на вершину стека }
i : word;
s : string;
const text : array [1 .. n] of string = ('one', 'two', 'three', 'four', 'five');
{ ------------------------------ занесение в стек --------------------------- }
function push(top : pnode; d : word; const s : string) : pnode;
var p : pnode;
begin
new(p);
p^.d := d; p^.s := s; p^.p := top;
push := p;
end;
{ ------------------------------ выборка из стека --------------------------- }
function pop(top : pnode; var d : word; var s : string) : pnode;
var p : pnode;
begin
d := top^.d; s := top^.s;
pop := top^.p;
dispose(top);
end;
{ ------------------------------- главная программа ----------------------------- }
begin
top := nil;
for i := 1 to n do top := push(top, i, text[i]); { занесение в стек: }
while top <> nil do begin { выборка из стека: }
top := pop(top, i, s); writeln(i:2, s);
end;
end.
Очередь — это динамическая структура данных, добавление элементов в которую выполняется в один конец, а выборка — из другого конца. Другие операции с очередью не определены. При выборке элемент исключается из очереди. Говорят, что очередь реализует принцип обслуживания FIFO (first in — first out, первым пришел — первым обслужен). В программировании очереди применяются очень широко — например, при моделировании,
Для работы с очередью используются указатели на ее начало и конец, а также вспомогательный указатель:
var beg, fin, p : pnode;
Тип указателей должен соответствовать типу элементов, из которых состоит очередь.
В приведена программа, которая формирует очередь из пяти целых чисел и их текстового представления и выводит еe на экран. Для разнообразия операции с очередью оформлены в виде процедур. Процедура начального формирования называется first, помещения в конец очереди — add, а выборки — get.
program queue;
const n = 5;
type pnode = ^node;
node = record { элемент очереди }
d : word;
s : string;
p : pnode;
end;
var beg, fin : pnode; { указатели на начало и конец очереди }
i : word;
s : string;
const text : array [1 .. n] of string = ('one', 'two', 'three', 'four', 'five');
{ ------------------ начальное формирование очереди --------------------------- }
procedure first(var beg, fin : pnode; d : word; const s : string);
begin
new(beg);
beg^.d := d; beg^.s := s; beg^.p := nil;
fin := beg;
end;
{ --------------------- добавление элемента в конец --------------------------- }
procedure add(var fin : pnode; d : word; const s : string);
var p : pnode;
begin
new(p);
p^.d := d; p^.s := s; p^.p := nil;
fin^.p := p;
fin := p;
end;
{ ---------------------- выборка элемента из начала --------------------------- }
procedure get(var beg : pnode; var d : word; var s : string);
var p : pnode;
begin
d := beg^.d; s := beg^.s;
p := beg; beg := beg^.p;
dispose(p);
end;
{ ------------------------------- главная программа --------------------------- }
begin
{ занесение в очередь: }
first(beg, fin, 1, text[1]);
for i := 2 to 5 do add(fin, i, text[i]);
{ выборка из очереди: }
while beg <> nil do begin
get(beg, i, s);
writeln(i:2, s);
end;
end.
В линейном списке каждый элемент связан со следующим и, возможно, с предыдущим. В первом случае список называется односвязным, во втором — двусвязным. Также применяются термины "однонаправленный" и "двунаправленный". Если последний элемент связать указателем с первым, получится кольцевой список.
Каждый элемент списка содержит ключ, идентифицирующий этот элемент. Ключ обычно бывает либо целым числом, либо строкой и является частью поля данных. В качестве ключа в процессе работы со списком могут выступать разные части поля данных. Например, если создается линейный список из записей, содержащих фамилию, год рождения, стаж работы и пол, любая часть записи может выступать в качестве ключа. При упорядочивании списка по алфавиту ключом будет фамилия, а при поиске, например, ветеранов труда — стаж. Ключи разных элементов списка могут совпадать.
Над списками можно выполнять следующие операции:
Стек и очередь представляют собой частные случаи линейного списка с ограниченным набором допустимых операций.
При чтении элемент списка не удаляется, в отличие от элемента, выбираемого из стека и очереди. Для работы со списком в программе требуется определить указатель на его начало. Чтобы упростить добавление новых элементов в конец списка, можно также завести указатель на конец списка.
Рассмотрим программу (), которая формирует
program linked_list;
const n = 5;
type pnode = ^node;
node = record { элемент списка }
d : word;
s : string;
p : pnode;
end;
var beg : pnode; { указатель на начало списка }
i, key : word;
s : string;
option : word;
const text: array [1 .. n] of string = ('one', 'two', 'three', 'four', 'five');
{ -------------- добавление элемента в конец списка --------------------------- }
procedure add(var beg : pnode; d : word; const s : string);
var p : pnode; { указатель на создаваемый элемент }
t : pnode; { указатель для просмотра списка }
begin
new(p); { создание элемента }
p^.d := d; p^.s := s; { заполнение элемента }
p^.p := nil;
if beg = nil then beg := p { список был пуст }
else begin { список не пуст }
t := beg;
while t^.p <> nil do { проход по списку до конца }
t := t^.p;
t^.p := p; { привязка нового элемента к последнему }
end
end;
{ ------------------------- поиск элемента по ключу --------------------------- }
function find(beg : pnode; key : word; var p, pp : pnode) : boolean;
begin
p := beg;
while p <> nil do begin { 1 }
if p^.d = key then begin { 2 }
find := true; exit end;
pp := p; { 3 }
p := p^.p; { 4 }
end;
find := false;
end;
{ -------------------------------- вставка элемента --------------------------- }
procedure insert(beg : pnode; key, d : word; const s : string);
var p : pnode; { указатель на создаваемый элемент }
pkey : pnode; { указатель на искомый элемент }
pp : pnode; { указатель на предыдущий элемент }
begin
if not find(beg, key, pkey, pp) then begin
writeln(' вставка не выполнена'); exit; end;
new(p); { 1 }
p^.d := d; p^.s := s; { 2 }
p^.p := pkey^.p; { 3 }
pkey^.p := p; { 4 }
end;
{ ------------------------------- удаление элемента --------------------------- }
procedure del(var beg : pnode; key : word);
var p : pnode; { указатель на удаляемый элемент }
pp : pnode; { указатель на предыдущий элемент }
begin
if not find(beg, key, p, pp) then begin
writeln(' удаление не выполнено'); exit; end;
if p = beg then beg := beg^.p { удаление первого элемента }
else pp^.p := p^.p;
dispose(p);
end;
{ ------------------------------------ вывод списка --------------------------- }
procedure print(beg : pnode);
var p : pnode; { указатель для просмотра списка }
begin
p := beg;
while p <> nil do begin { цикл по списку }
writeln(p^.d:3, p^.s); { вывод элемента }
p := p^.p { переход к следующему элементу списка }
end;
end;
{ ------------------------------- главная программа --------------------------- }
begin
for i := 1 to 5 do add(beg, i, text[i]);
while true do begin
writeln('1 - вставка, 2 - удаление, 3 - вывод, 4 - выход');
readln(option);
case option of
1: begin { вставка }
writeln('Ключ для вставки?');
readln(key);
writeln('Вставляемый элемент?');
readln(i); readln(s);
insert(beg, key, i, s);
end;
2: begin { удаление }
writeln('Ключ для удаления?');
readln(key);
del(beg, key);
end;
3: begin { вывод }
writeln('Вывод списка:');
print(beg);
end;
4: exit; { выход }
end
writeln;
end
end.
Функция поиска элемента find возвращает true, если искомый элемент найден, и false в противном случае. Поскольку одного факта отыскания элемента недостаточно, функция также возвращает через список параметров два указателя: на найденный элемент p и на предшествующий ему pp. Последний требуется при удалении элемента из списка, поскольку при этом необходимо связывать предыдущий и последующий по отношению к удаляемому элементы.
Сначала указатель p устанавливается на начало списка, и организуется цикл просмотра списка (оператор 1). Если поле данных очередного элемента совпало с заданным ключом (оператор 2), формируется признак успешного поиска и функция завершается. В противном случае перед переносом указателя на следующий элемент списка (он хранится в поле p текущего элемента, оператор 4) его значение запоминается в переменной pp (оператор 3) для того, чтобы при следующем проходе цикла в ней находился указатель на предыдущий элемент.
Если элемента с заданным ключом в списке нет, цикл завершится естественным образом, поскольку nil в поле p указателя на следующий элемент.
Вставка элемента выполняется после элемента с заданным ключом (процедура insert ). Если с помощью функции find место вставки определить не удалось, выводится сообщение и процедура завершается (в этом случае можно было использовать и другой алгоритм — добавлять элемент к концу списка, это определяется конкретным предназначением программы). Если элемент найден, указатель на него заносится в переменную pkey.
Под новый элемент выделяется место в динамической памяти (оператор 1), и информационные поля элемента заполняются переданными в процедуру значениями (оператор 2). Новый элемент ).
(рис 5.4) Вставка элемента в список
Удаление элемента выполняет процедура del. В ней две ветви, поскольку при удалении элемента из начала списка изменяется указатель на начало, а при удалении второго и последующих элементов требуется связывать предыдущий и последующий по отношению к удаляемому элементы.
Бинарное дерево — это динамическая структура данных, состоящая из узлов, каждый из которых содержит кроме данных не более двух ссылок на различные бинарные деревья. На каждый узел имеется ровно одна ссылка. Начальный узел называется корнем дерева.
Пример бинарного дерева приведен на рис 5.5 (корень обычно изображается сверху). Узел, не имеющий поддеревьев, называется листом. Исходящие узлы называются предками, входящие — потомками. Высота дерева определяется количеством уровней, на которых располагаются его узлы.
(рис 5.5) Пример бинарного дерева
Если дерево организовано таким образом, что для каждого узла все ключи
его
Дерево является
procedure print_tree( дерево );
begin
print_tree( левое_поддерево )
посещение корня
print_tree( правое_поддерево )
end;
Эта процедура позволяет получить последовательность ключей, отсортированную по возрастанию. Результат обхода дерева, изображенного на рис 5.5:
1, 6, 8, 10, 20, 21, 25, 30
Если в функции обхода первое обращение идет к правому поддереву, результат обхода будет другим:
30, 25, 21, 20, 10, 8, 6, 1
Таким образом, деревья поиска можно применять для сортировки значений. При обходе дерева узлы не удаляются.
Для бинарных деревьев определены операции:
Для простоты будем рассматривать дерево, каждый элемент которого содержит только целочисленный ключ и два указателя.
type pnode = ^node;
node = record
data : word; { ключ }
left : pnode; { указатель на левое поддерево }
right : pnode { указатель на правое поддерево }
end;
Доступ к дереву в программе осуществляется через указатель на его корень:
var root : pnode;
Рассмотрим сначала ).
function find(root : pnode; key : word; var p, parent : pnode) : boolean;
begin
p := root; { поиск начинается от корня }
while p <> nil do begin
if key = p^.data then { узел с таким ключом есть }
begin find := true; exit end;
parent := p; { запомнить указатель перед спуском }
if key < p^.data
then p := p^.left { спуститься влево }
else p := p^.right; { спуститься вправо }
end;
find := false;
end;
Функция возвращает булевский признак успешности поиска. Ей передаются указатель на корень дерева, в котором выполняется поиск ( root ), и искомый ключ ( key ). Выходными параметрами функции являются указатели на найденный элемент ( p ) и его предка ( parent ).
Удаление элемента — более сложная задача, поскольку при этом необходимо сохранить свойства дерева поиска. Эта задача подробно описана в учебнике .
В приведен пример программы работы с бинарным деревом.
program bintree;
uses crt;
type pnode = ^node;
node = record
data : word; { ключ }
left : pnode; { указатель на левое поддерево }
right : pnode { указатель на правое поддерево }
end;
var root : pnode;
key : word;
option : word;
{ ------------------------------------ вывод дерева --------------------------- }
procedure print_tree(p : pnode; level : integer);
var i : integer;
begin
if p = nil then exit;
with p^ do begin
print_tree(right, level + 1);
for i := 1 to level do write(' ');
writeln(data);
print_tree(left, level + 1);
end
end;
{ ------------------------------- поиск по дереву – см. рис. 5.5----------- }
function find(root : pnode; key : word; var p, parent : pnode) : boolean;
{ ---------------------------- включение в дерево ----------------------------- }
procedure insert(var root : pnode; key : word);
var p, parent : pnode;
begin
if find(root, key, p, parent) then begin
writeln(' такой элемент уже есть'); exit; end;
new(p); { создание нового элемента }
p^.data := key;
p^.left := nil;
p^.right := nil;
if root = nil then root := p { первый элемент }
else { присоединение нового элемента к дереву}
if key < parent^.data
then parent^.left := p
else parent^.right := p;
end;
{ ------------------------------ удаление из дерева - см. учебник --------- }
procedure del(var root : pnode; key : word);
{ ------------------------------- главная программа --------------------------- }
begin
root := nil;
while true do begin
writeln('1 - вставка, 2 - удаление, 3 - вывод, 4 - выход');
readln(option);
case option of
1: begin { вставка }
writeln('Введите ключ для вставки: '); readln(key);
insert(root, key);
end;
2: begin { удаление }
writeln('Введите ключ для удаления: '); readln(key);
del(root, key);
end;
3: begin { вывод }
clrscr;
if root = nil then writeln ('дерево пустое')
else print_tree(root, 0);
end;
4: exit; { выход }
end;
writeln;
end
end.
Рассмотрим функцию обхода дерева print_tree.
Вторым параметром в нее передается целая переменная, определяющая, на каком
уровне находится узел. Корень находится на уровне 0. Дерево печатается по
горизонтали так, что корень находится слева. Для дерева, изображенного на
рис 5.5, вывод выглядит так:
30
25
21
20
10
8
6
1
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.