Файл: Основы программирования на языке Pascal ( Ссылочные типы в языке программирования Pascal. Создание ссылочных типов ).pdf
Добавлен: 01.04.2023
Просмотров: 104
Скачиваний: 1
Пример 1.
Type mas = array[1..50] of real;
mas1: ^mas; {mas1 – указатель}
Пример 2.
Var t: ^integer;
s: ^string;
a: mas1;
Для объявления переменных не связанных с типом данных можно использовать указатель без типа (pointer).
Пример 3.
Var p: pointer;
где
pointer – это не типизированный указатель, занимающий в памяти 4 байт (2-байта сегмент, 2байта смещение). [4]
Существует такое понятие, как переменная с указателем. Например: А^ - переменная с указателем, с ней можно обращаться также как с переменной указанного типа.
Пример 4.
t^ := 15;
t^ mod 2;
Память под динамическую величину, которая связана с указателем, выделяется в результате выполнения процедуры New(). Формат обращения к данной процедуре следующий:
New(<переменная-указатель>);
Пример 5.
New(t);
Динамические объекты не должны храниться в памяти компьютера вечно. Существует стандартная функция, уничтожающая динамический объект и освобождающая память компьютера, которую объект занимал. Формат обращения к данной процедуре следующий:
DISPOSE(<указатель>);
Пример 6.
DISPOSE(T)
Чередование обращений к процедурам New() и Dispose() обычно приводит к фрагментации, т.е. образованию ячеистой структуры памяти. При очередном обращении к процедуре New() отыскивается наименьший свободный фрагмент, в котором может поместиться новая переменная. [17]
Для освобождения целого фрагмента «кучи», перед началом выделения динамической памяти текущее значение указателя на границу незанятой динамической памяти запоминается в переменной указателе с помощью процедуры MARK().
Формат обращения к данной процедуре следующий:
MARK(<переменная-указатель>);
Для того чтобы освободить фрагмент «кучи» используется процедура RELEASE().Формат обращения к данной процедуре следующий:
RELEASE (<указатель>);
Пример 7.
Пример использования процедур New(),Mark(),Release() и Dispose().
var
t, t1, t2: ^integer;
begin
New(t1);
Mark(t);
New(t2);
Release(t);
Dispose(t1);
Dispose(t2);
end.
При работе с указателями и динамическими переменными важно помнить:
- Необходимо освобождать выделенные области сразу после того, как потребность в них отпадает, во избежание «засорения» памяти компьютера.
- В связи с использованием процедуры New() возможно возникновение проблемы исчерпания области памяти компьютера, которая отведена для динамических переменных. В случае если при выполнении процедуры New() выяснится, что для размещения новой динамической переменной не хватает памяти, то значение указателя, которое передано в параметре процедуры не изменится. При этом выполнение программы не приостановится и никаких сообщений пользователю выдано не будет. Следовательно, работа с таким указателем может привести к различным непредсказуемым последствиям.
- После выполнения процедуры Dispose() указатель на освобожденную область памяти не обнуляется (значение указателя остается доступным в программе), что означает сохранение возможности доступа к формально уже недоступной области памяти, которая к моменту такого ошибочного доступа уже может быть выделена для других переменных обращением к процедуре New().
- Необходимо учитывать проблему, которая связана с противоречием между стековым принципом размещения статических переменных, а также произвольным характером создания и удаления этих переменных. Существует правило, согласно которому, все локальные переменные подпрограммы перестают существовать после завершения подпрограммы. [17]
Пример 8.
Program primer;
Uses crt;
procedure PrProc;
var
а: integer;
t: ^string;
begin
new(t);
...
end;
begin
clrscr;
writeln(memAvail);
someProc;
writeln(memAvail);
readkey;
end. [4]
Пример показывает, что исчезает локальная переменная t, однако область памяти, которая отведена под эту переменную, остается поскольку освободить ее можно только явно путем использования процедуры Dispose().
В примере используется следующая функция:
- MemAvail: LongInt (без параметров), которая возвращает размер (в байтах) общей свободной области памяти в текущий момент.
Кроме того, существует функция:
- MaxAvail: LongInt (без параметров), которая возвращает размер максимальной свободной области памяти в текущий момент.
Для выделения и освобождения области динамической памяти без учета типа используются следующие процедуры:
- GetMem (<нетипизированный указатель>, <размер памяти в байтах>) – выделение памяти;
- FreeMem (<нетипизированный указатель>, <размер памяти в байтах >) – освобождение памяти.
Данные процедуры имеют два параметра:
- указатель типа;
- размер выделяемой либо освобождаемой памяти в байтах.
Важно помнить, что освобождать необходимо ровно столько байтов, сколько было выделено, и по тому же самому указателю. [6]
Пример 9.
type tRec = record
a: real;
b: real;
end;
tPrim = ^tRec;
tInt=^integer;
Var
p: pointer;
rc: tRec;
q: tInt;
begin
rc.a := 0.3;
rc.b := 0.16;
GetMem(p, SizeOf(tRec));
new(q);
p := q;
tInt(p)^ := q^;
tPrim(p)^ := rc;
end.
Применение нетипизированных указателей позволяет осуществлять неявное преобразование типов «по одному и тому же адресу можно размещать различные типы данных, главное чтобы они занимали одинаковый объем памяти».
Чтобы получить доступ к значению некоторой динамической переменной необходимо выполнить операцию разыменования указателя. [19]
Пример 10.
t^:=3;
Использование операции разыменования допускается для любых ссылочных типов. Однако важно помнить, что разыменование является некорректным в случае, если ссылочная переменная имеет значение nil. В данном случае не существует переменной, на которую ссылается указатель, следовательно, последовательность операторов
t := nil;
t^ := 3;
является недопустимой.
Дальнейшее использование динамических переменных происходит так же, как со статическими переменными соответствующих типов. Динамическим переменным можно присваивать значения, их можно использовать в качестве операндов в выражениях, параметров подпрограмм и т.д.
Пример 11.
t^ := 51;
s^ := 'Read';
for i := 1 to 50 do
pm^[i] := i + 3;
Динамические переменные, как правило, используют в связанных структурах данных. Простейшим способом описания связанных объектов является однонаправленный список (например, очередь). [3]
Выделяют пять основных операций над списками:
- формирование списка;
- просмотр элементов списка;
- поиск элемента в списке;
- удаление элементов звена в списке;
- вставка элементов звена в списке. [9]
Далее приведем примеры использования операций над однонаправленными списками (рис. 3-7). [17]
Рис. 3 – Формирование списка
Рис. 4 – Процедура просмотра элементов списка
Рис. 5 – Функция поиска элементов в списке
Примечание к рисунку:
Функция poisk возвращает значение true, если элемент С принадлежит списку и значение false, если элемент С не принадлежит списку. Кроме того, функция ищет значение AD (адрес ссылки звена элемента С). [17]
Рис. 6 – Процедура удаления звена из списка
Рис. 7 – Процедура вставки элементов звена в список
3. Примеры программ ЯП Pascal с использованием ссылочных типов
Далее приведем примеры программ для работы с ссылочными типами данных.
Пример 12.
Создать одномерный массив случайных чисел, разместить его в динамической памяти, вычислить сумму элементов и удалить массив из памяти.
Текст программы:
Program Primer12;
Uses crt;
Const
t1 = 100;
t2 = 2*t1+1;
Type
Item = Real;
DMas = array[1..$FFF0 div SizeOf(Item)] of Item;
DPrt = ^DMas;
Var
mas: DPrt;
i, n: Word;
sum, f: Real;
{$R-}
Begin
clrscr;
randomize;
writeln('Введите количество элементов массива: ');
readln(n);
GetMem(mas,n*SizeOf(Item));
for i:=1 to n do
begin
f := random(t2);
mas^[i] := t1-f;
end;
writeln;
s:=0;
for i:=1 to N do sum:=sum+mas^[i];
writeln(^G'Результат:',^J^M,^G'Сумма = ',sum);
FreeMem(mas,n*SizeOf(Item));
repeat until KeyPressed
end.
Результат выполнения программы:
Введите количество элементов массива: 200
Результат:
Сумма = -1.8610000000E+03
Пример 13.
Создать двумерный массив случайных чисел, разместить его в динамической памяти, вычислить сумму элементов и удалить массив из памяти.
Текст программы:
Program Primer13;
Uses crt;
Type
Item = Real;
DMas = array[1..$Ff div sizeof(item),1..$ff div sizeof(item)] of Item;
DPrt = ^DMas;
Var
Mas: DPrt;
k, j, l, i, n: Word;
sum: Real;
{$R-}
Begin
clrscr;
randomize;
writeln('Введите количество строк массива: ');
readln(i);
writeln('Введите количество столбцов массива: ');
readln(j);
n := i*j;
GetMem(Mas,n*SizeOf(Item));
for k:=1 to i do
for l:=1 to j do Mas^[k,l] := random(2000);
sum := 0;
for k:=1 to i do
for l:=1 to j do sum := sum+Mas^[k,l];
writeln(^G'Результат:',^J^M,^G'Сумма = ',sum);
FreeMem(Mas,n*SizeOf(Item));
repeat until KeyPressed
end.
Результаты выполнения программы:
Введите количество строк массива: 1000
Введите количество столбцов массива: 1000
Результат:
Сумма = 5.7820716634E+32
Пример 14.
Создать запись, разместить ее в динамической памяти, удалить запись из памяти.
Текст программы:
Program Primer14;
Uses crt;
Type
FRec = Record
FName: string[50];
FAge: Byte;
end;
FPtr = ^FRec;
Var
t : FPtr;
Begin
clrscr;
GetMem(t, SizeOf(FRec)); {Выделение памяти в куче}
write('Введите имя: '); Readln(t^.FName);
write('Введите возраст: '); Readln(t^.FAge);
writeln;
writeln('Память для записи распределена в куче');
writeln;
writeln('Имя: ',t^.FName);
writeln('Возраст: ',t^.FAge);
writeln;
FreeMem(t, SizeOf(FriendRec)); {Освобождение памяти}
writeln('Память освобождена');
repeat until KeyPressed;
End.
Результат выполнения программы:
Введите имя: Степанов Олег Игоревич
Введите возраст: 24
Память для записи распределена в куче
Имя: Степанов Олег Игоревич
Возраст: 24
Память освобождена
Пример 15.
Дан файл целых положительных чисел, который состоит из нескольких последовательностей чисел. Каждая из последовательностей заканчивается числом -1. Необходимо вывести в выходной файл данные последовательности чисел, однако числа внутри каждой последовательности должны быть расположены в обратном порядке.
Пояснение:
147 225 2168 -1 (входной файл);
741 522 8612 -1 (выходной файл)
Длина данных последовательностей заранее неизвестна, а значит, переменные в которые будут считываться числа должны иметь динамическую структуру.
Текст программы:
Program Primer15;
Uses crt;
Type
Ptr = ^DSet;
DSet = record
RData: integer;
RPtr: Pointer
end;
Var
t1, t2: Ptr;
i: integer;
In, Out: file of integer;
Begin
clrscr;
reset(In);
rewrite(Out);
while not Eof(In) do
begin
t1 := nil;
read(In, i);
while I <> -1 do
begin
new(t2);
t2^.RData := I;
t2^.RPtr := t1;
t1 := t2;
read(In, i);
end;
t2 := t1;
while t2 <> nil do
begin
write(Out, t2^.TData);
t2 := t2^.Ptr;
end;
write(Out, '-1');
end;
readln;
End.
Результат выполнения программы:
147 225 2168 -1
741 522 8612 -1
4. Циклические программы
Наиболее часто в практике программирования встречаются циклические программы. В циклических программах какой-либо алгоритм повторяется многократно, при этом один из параметров изменяется. Например, описанная в п. 3.3 программа Рrim 4 является классическим примером циклического алгоритма. Операторов цикла в Паскале три: for, repeat, while.
4.1. Оператор for
Оператор состоит из заголовка, в котором определяется порядок изменения переменной параметра цикла и тела цикла, являющегося многократно повторяющимся алгоритмом. Общий вид оператора:
for – параметр цикла: = начальное значение to, конечное значение do {заголовок}; оператор; {тело цикла}. Этот оператор применяется, если начальное значение < конечного значения;
for – параметр цикла:=начальное значение downto, конечное значение do; оператор; применяется, если начальное значение > конечного значения.
Пример: найти сумму квадратов целых чисел от 8 до 1.24.
Program Prim12; Var i,s:integer; BEGIN s:=0; for i:= 8 to 124 do s:=s+sqr(i); writeln('s=',s); readln;
END.
Работа программы. В разделе Var выделяется ячейка памяти с именем i и s для хранения величин. Поскольку в S мы должны накапливать сумму, то вначале занесем в S ноль. Затем заголовок цикла присваивает i=8. далее выполняется тело цикла: извлекается содержимое ячейки S (а там у нас 0) и к этому содержимому прибавляется sgr(i), т.е. i2=82. Результат присваивается ячейке S, т.е. в S теперь 82.
Проверяется, не стал ли параметр цикла больше конечного значения параметра 128. Это не произошло, поэтому i присваивается следующее значение равное 9 и вновь выполняется тело цикла, т.е. S:=82+92. Можно сказать так: S присвоить тому S, которое было раньше, + следующее i2. Так как цикл еще не закончился, то i станет равным 10, а S присвоится тому S, которое было раньше, т.е. 82+92, и к нему прибавится еще текущее i2, т.е. 102. Этот процесс повторяется до тех пор, пока параметр цикла не станет равным 124. Тогда в последний раз 1242 прибавляется к накапливаемой сумме.
Итак: выполнение цикла значения i значения S
1 8 82
2 9 82+92
3 10 82+92+102
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
116 117 82+92+102+…+1232