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

Алгоритмические языки высокого уровня. Курс лекций

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
940 Кб
Скачать

begin {Основная программа}

.......

i := MyFunc(1); {Обычное использование результата функции}

.......

VarFunc := MyFunc;

{Присваивание переменной процедурного типа имени функции

MyFunc}

.......

end.

Присваивание VarFunc := MyFunc(1); будет недопусти- мым, так как слева и справа от знака присваивания используются не- совместимые типы: слева процедурный тип, а справа INTEGER; имя функции со списком фактических параметров MyFunc(1) трактуется Turbo Pascal как обращение к значению функции, в то время как имя функции без списка параметров рассматривается как имя функции. В отличие от стандартного Pascal, в Turbo Pascal раз- решается использовать в передаваемой процедуре (функции) любые типы параметров: параметры-значения, параметры-переменные, па- раметры-константы (в стандартном Pascal только параметры- значения).

4.4. Указатели, структуры данных

Все переменные, объявленные в программе, размещаются в од- ной непрерывной области оперативной памяти, которая называется сегментом данных. Длина сегмента данных определяется архитекту- рой микропроцессоров 80×86 и составляет 65536 байт (это может привести к трудностям при обработке больших массивов данных). С другой стороны, объем памяти ПК (обычно не менее 640 Кбайт) дос- таточен для успешного решения задач с большой размерностью дан- ных. Для этого может служить использование так называемой дина- мической памяти.

Динамическая память это оперативная память ПК, предостав- ляемая программе при ее работе, за вычетом сегмента данных (64 Кбайт), стека (обычно 16 Кбайт) и собственно тела программы. Размер динамической памяти можно варьировать в широких преде- лах. По умолчанию этот размер определяется всей доступней памя- тью ПК и, как правило, составляет не менее 200...300 Кбайт.

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

91

необходимость возникает, например, при разработке систем автома- тизированного проектирования (САПР), систем моделирования, вре- менного запоминания данных при работе с графическими и звуко- выми средствами ПК и т.д. Динамическое размещение данных озна- чает использование динамической памяти непосредственно при ра- боте программы. В отличие от этого статическое размещение осуще- ствляется компилятором Pascal в процессе компиляции программы. При динамическом размещении заранее не известны ни тип, ни ко- личество размещаемых данных (к таким данным нельзя обращаться по именам, как к статическим переменным).

Оперативная память ПК представляет собой совокупность эле- ментарных ячеек для хранения информации байт, каждый из кото- рых имеет собственный номер. Эти номера (ячеек) называются адре- сами (они позволяют обращаться к любому байту/ячейке памяти).

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

В ПК адреса задаются совокупностью двух шестнадцатиразряд- ных слов, которые называются сегментом и смещением. Сегмент это участок памяти, имеющий длину 65536 байт (64 кбайт) и начи- нающийся с физического адреса, кратного 16 (т.е. 0, 16, 32, 48 и т.д.). Смещение указывает, сколько байт от начала сегмента необ- ходимо пропустить, чтобы обратиться к нужному адресу.

Адресное пространство ПК составляет 1 Мбайт (речь идет о так называемой стандартной памяти ПК; на современных компьюте- рах с процессорами 80386 и выше адресное пространство составляет 4 Гбайт, однако в Turbo Pascal нет средств, поддерживающих работу с дополнительной памятью). Для адресации в пределах 1 Мбайта нужно 20 двоичных разрядов, которые получаются из двух шестна- дцатиразрядных слов (сегмента и смещения) следующим образом: содержимое сегмента смещается влево на 4 разряда, освободив- шиеся правые разряды заполняются нулями, результат складывается с содержимым смещения.

92

Фрагмент памяти в 16 байт называется параграфом, поэтому можно сказать, что сегмент адресует память с точностью до парагра- фа, а смещение с точностью до байта. Каждому сегменту соответ- ствует непрерывная и отдельно адресуемая область памяти. Сегмен- ты могут следовать в памяти один за другим без промежутков или с некоторым интервалом, или, наконец, перекрывать друг друга. Таким образом, по своей внутренней структуре любой указатель представ- ляет собой совокупность двух слов (данных типа WORD), трактуе- мых как сегмент и смещение. С помощью указателей можно разме- щать в динамической памяти любой из известных в Pascal типов данных. Лишь некоторые из них (BYTE, CHAR, SHORTINT, BOOLEAN) занимают во внутреннем представлении один байт, ос- тальные несколько смежных байтов. Поэтому на самом деле указа- тель адресует лишь первый байт данных.

4.4.1. Виды указателей

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

var

P1 :^Integer; р2 :^Real;

type

PerconPointer = ^PerconRecord; PerconRecord = record

Name : String;

Job : String;

Next : PerconPointer end;

Замечание: при объявлении типа PerconPointer используется ссылка на тип PerconRecord, который предварительно в про- грамме объявлен не был. В Pascal последовательно проводится в жизнь принцип, в соответствии с которым перед использованием какого-либо идентификатора (переменной) он должен быть описан. Исключение сделано только для указателей, которые могут ссы- латься на еще не объявленный тип данных. Динамическая память дает возможность реализовать широко используемую организацию данных в виде списков. Каждый элемент списка имеет в своем со- ставе указатель на соседний элемент, что обеспечивает возмож-

93

ность просмотра и коррекции списка. Если бы в Pascal не было это- го исключения, реализация списков была бы значительно затруднена.

Указатели, не связанные с конкретными типами данных, называ- ются нетипизированными. Для объявления нетипизированного ука- зателя служит стандартный тип POINTER.

Var

р: pointer;

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

Если, например,

var

p1,p2: ^integer; р3 : ^Rеа1;

рр : pointer;

то присваивание p1 := р2; – допустимо,

в то время как р1 := р3; – запрещено, поскольку p1 и p3 указывают на разные типы данных. Это ограничение не распростра- няется на нетипизированные указатели, поэтому можно написать:

pp := p3; p1 := pp; – это будет допустимым.

Замечание: стоило ли вводить ограничения и тут же давать средства для их обхода? Все дело в том, что любое ограничение с одной стороны вводится для повышения надежности программ, а с другой уменьшает мощность языка программирования, делая его менее пригодным для каких-то применений. В Pascal немногочислен- ные исключения в отношении типов данных придают языку необхо- димую гибкость, но их использование требует от программиста

94

дополнительных усилий и, таким образом, свидетельствует о впол- не осознанном действии.

Для указателей определена константа NIL («ноль»), которая оз- начает, что указатель не указывает никуда:

P:= NIL;

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

4.4.2. Операции над указателями

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

Операции сравнения. Для указателей определены две операции сравнения: '=' и '<>'. Две переменные, описанные как указатели на один и тот же тип данных, считаются совпадающими, если они указывают на одну и ту же область памяти. Для разнотипных указа- телей сравнения невозможны: if p = q then

writeln('yes'); {p: ^byte; q: ^integer} вызовет ошибку уже на этапе компиляции. Однако сравнивать типизирован- ные и нетипизированные указатели можно.

Операции определения адреса. Физический адрес любой пере-

менной можно узнать с помощью стандартной функции addr(<имя_переменной>):<указатель> или унарной опера-

ции @<имя_переменной>. В зависимости от значения директивы компилятора {$T} результатом операции @ будет либо типизиро- ванный указатель, тип которого будет определен в соответствии с типом использованной переменной ({$T+}), либо нетипизиро- ванный указатель pointer ({$T-}). Результат функции addr() совместим с указателями любых типов: p:= addr(x);

{x: real; p: ^byte}.

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

95

«извлечь». Унарная операция ^ называется разыменованием и запи- сывается по следующему шаблону: <имя_указателя>^.

Результатом операции ^ является значение, хранящееся по ука- занному адресу. Тип этого значения будет определяться типом (ти- пизированного) указателя. К нетипизированным указателям опера- цию разыменования применять нельзя.

Использование процедуры addr() для разыменования:

const

a: array[1..3] of char ='ААА'; {код(А)=128 или 01000000} var

p: ^word; begin

p:= addr(a); writeln(p^)

end.

На экран будет выведено 32896, что в двоичной системе счис- ления выглядит как 01000000.01000000 (точкой помечена гра- ница двух байтов). Коды двух первых букв оказались слитыми в зна- чение типа word.

Замечание: операции @ и ^ являются взаимно обратными, т.е. для любой переменной a и для любого типизированного указателя p верны следующие равенства:

@(p^)= p и (@a)^ = a.

 

Таблица 4.6

 

Пример использования указателя

 

 

Код

Описание

Const i:

Создается инициализированная переменная i

integer=1;

и указатель Pi с неизвестным адресом

Var Pi:

 

^integer;

 

Pi:=@i

Pi присваивается адрес переменной i

Pi:=p+^2

Значение переменной, адрес которой нахо-

 

дится в Pi, увеличивается на 2

Pi:=nil

Указателю присваивается нулевое значение

4.4.3. Процедуры и функции для работы с указателями

Статической переменной (статически размещенной) называется описанная явным образом в программе переменная, обращение к ко- торой осуществляется по имени. Место в памяти для размещения статических переменных определяется при компиляции программы.

96

В отличие от таких статических переменных в программах, написан- ных на языке Pascal, могут быть созданы динамические перемен- ные. Основное свойство динамических переменных заключается в том, что их создание и выделение для них памяти осуществляется во время выполнения программы. Размещаются динамические пере- менные в динамической области памяти (heap-области). Динами- ческая переменная не указывается явно в описаниях переменных, и к ней нельзя обратиться по имени. Доступ к таким переменным осуще- ствляется с помощью указателей и ссылок.

Процедура New (var p: Pointer) выделяет место в динамической области памяти для размещения динамической переменной p^ и ее адрес присваивает указателю p.

Процедура Dispose (var p: Pointer) освобождает участок памя- ти, выделенный для размещения динамической переменной проце- дурой New (указатель p становится неопределенным).

Процедура GetMem (var p: Pointer; size: Word) выделяет уча-

сток памяти в heap-области, присваивает адрес его начала указате- лю p (размер участка в байтах задается параметром size).

Процедура FreeMem (var p: Pointer; size: Word) освобождает участок памяти, адрес начала определяется указателем p, размер параметром size (указатель p становится неопределенным).

Процедура Mark (var p: Pointer) записывает в указатель p ад- рес начала участка свободной динамической памяти на момент ее вызова.

Процедура Release (var p: Pointer) освобождает участок дина- мической памяти, начиная с адреса, записанного в указатель p про- цедурой Mark (очищает ту динамическую память, которая была занята после вызова процедуры Mark).

Функция MaxAvail: Longint возвращает длину в байтах самого длинного свободного участка динамической памяти.

Функция MemAvail: Longint возвращает объем свободной ди- намической памяти в байтах.

Функция SizeOf (X): Word возвращает объем в байтах, зани- маемый X (X переменная или тип).

Функция OFS(Х): Word возвращает значение типа WORD, со- держащее смещение адреса указанного объекта (Х выражение лю- бого типа или имя процедуры).

97

Функция SEG(Х): Word возвращает значение типа WORD, со- держащее сегмент адреса указанного объекта (X выражение любого типа или имя процедуры).

Функция PTR(SEG, OFS:word):pointer функция пре-

вращает адрес, предоставленный в виде <сегмент>:<смещение> в указатель (возвращаемое значение совместимо с указателем лю- бого типа).

Рассмотрим пример работы с указателями

Var p1, p2: ^Integer; { p1 и p2 – указатели или переменные ссылочного типа}

p1:=NIL; p2:=NIL;

После выполнения операторов указатели p1 и p2 не ссылаются ни на какой объект.

New(p1); New(p2);

Процедура New(p1) выполняет следующие действия: в памяти ПК выделяется участок для размещения величины целого типа; адрес это- го участка присваивается переменной p1 (процедура New(p2) обес- печит выделение участка памяти, адрес которого будет записан в p2).

После выполнения операторов присваивания p1^:=2; p2^:=4; в выделенные участки памяти будут записаны значения 2

и4 соответственно.

Врезультате выполнения оператора присваивания p1^:=p2^; в участок памяти, на который ссылается указатель p1, будет записа- но значение 4.

После выполнения оператора присваивания p2:=p1; оба указа- теля будут содержать адрес первого участка памяти

98

Переменные p1^, p2^ являются динамическими, так как па- мять для них выделяется в процессе выполнения программы с помо- щью процедуры New.

Динамические переменные могут входить в состав выражений,

например: p1^:=p1^+8; Write('p1^=',p1^:3);

В результате выполнения программы:

Program DemoPointer; Var p1,p2,p3:^Integer; Begin

p1:=NIL; p2:=NIL; p3:=NIL; New(p1); New(p2); New(p3); p1^:=2; p2^:=4; p3^:=p1^+Sqr(p2^);

writeln('p1^=',p1^:3,' p2^=',p2^:3,' p3^=',p3^:3); p1:=p2;

writeln('p1^=',p1^:3,' p2^=',p2^:3) End.

На экран дисплея будут выведены результаты:

p1^= 2 p2^= 4 p3^= 18 p1^= 4 p2^= 4

Пример программы

Описание: использование динамической памяти для размещения крупных массивов данных.

Пусть требуется обеспечить доступ к элементам прямоугольной матрицы 100×200 типа EXTENDED. Для размещения такого массива требуется память 200000 = 100*200*10 байт, что превышает размер сегмента данных (65536 байт), доступный программе для статическо- го размещения данных. Для решения используется адресная арифме- тика над указателями.

program ptr_array; uses crt;

const

sizeofreal=6; N=100; M=200;

type

Realpointer=^real;

var

i,j: integer;

99

Ptrar:array[1..N] of pointer; s: real;

function AddrR(I,j:word):Realpointer; begin

AddrR:=ptr(seg(PtrAr[i]^),ofs(PtrAr[i]^)+ +(j-1)*Sizeofreal)

end;

function GetR(I,j:integer):real; begin

GetR:=AddrR(i,j)^

end;

procedure PutR(I,j:integer;x:real); begin

AddrR(i,j)^:=x;

end; Begin

clrscr;

for i:=1 to N do begin

Getmem(PtrAr[i],M*Sizeofreal); for j:=1 to M do

putR(i,j,random(100));

end;

s:=0;

for i:=1 to N do for j:=1 to M do

begin s:=s+GetR(i,j);

end;

writeln(‘Sred znach = ‘,s/(n*m):12:2);

End.

4.5. Динамические структуры данных

Абстрактные структуры данных предназначены для удобного хранения и доступа к информации. Они предоставляют удобный ин- терфейс для типичных операций с хранимыми объектами, скрывая детали реализации от пользователя. Абстрактные структуры данных иногда делят на две части: интерфейс, набор операций над объекта-

ми, который называют АТД (абстрактный тип данных) и реализа-

цию (пример использования простых абстрактных структур данных оперирование с числами. Языки программирования высокого уровня предоставляют удобный интерфейс для чисел: операции +, *, = / и т.п., но при этом скрывают саму реализацию этих операций, ма- шинные команды).

100

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