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

Предоплата всего

Подписываем
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Предоплата всего
Подписываем
Тип pointer
Встроенный тип pointer обозначает указатель, который в отличие от понятия ссылки в динамических структурах, не связан конструкцией Record с ни с каким определенным типом данных. Кардинальное число типа Pointer соответвует 4 байтам и включает значение nil. При этом указатели, если они не являются полем в рекурсивных структурах данных, размещаются там же, где и статические переменные, т. е. в сегменте данных.
Переменные типа Pointer описываются так же, как ссылки с указанием символа перед указанием типа той переменной, которая должна распределяться в куче, например:
type
DynType=array [1..$FFF0] of Char;
var
BufferPtr : ^ DynType; {массив критического для сегмента размера
теперь будет размещаться в Heap-области}
StrPtr :^ String;
. . .
Перед использованием переменных типа pointer их, естественно, нужно инициализировать, т. е. выделить память для распределения значения соответствующего типа данных (полная аналогия с формированием новой компоненты динамических структур данных). Для этих целей используется уже известная процедура new с единственным параметром в виде переменной- указателя:
. . .
New(BufferPtr);
New(StrPtr);
. . .
В соответствии с ранее описанными для ссылки правилами, обращение к таким переменным должно иметь вид:
. . .
BufferPtr^[3] :=a;
StrPtr ^:=Иванов;
. . .
В таком контексте динамические переменные ничем не отличаются от обычных переменных соответствующих типов: к ним применимы все допустимые над типом операции.
Процедура Dispose. Кроме процедуры New для работы с динамическими переменными в Borland Pascal зарезервирована процедура Dispose с тем же параметром. Она предназначена для освобождения памяти в Heap, выделенной процедурой New. Так после вызовов:
. . .
Dispose(BufferPtr);
Dispose(StrPtr);
. . .
указатели перестанут быть связаны с конкретными адресами памяти (в этом случае иногда говорят, что они “разименованы”), а попытка присваивания несуществующим переменным каких-либо значений приведет к нарушению нормальной работы программы (такое обращение не контролируется транслятором) или к “зависанию” операционной системы. Поэтому во время работы с динамическими переменными следует придерживаться определенной последовательности действий, укладывающихся в схему:
выделение памяти (инициализация с помощью New);
работа с соответствующей переменной;
освобождение памяти с помощью Dispose.
Описанная семантика процедур New и Dispose этим не ограничивается и дополнительно расширена. Ее “функциональная” форма могут использоваться по отношению к любым динамически размещаемым переменным. процедура new в расширенной трактовке может использоваться как функция, возвращающая значения указателя, т. е. возможны присваивания вида:
type
ArcPtr =^Arc;
var
PArc : ArcPtr;
begin
PArc :=New(ArcPtr);
Процедуры Mark и Release. Существует еще один способ динамического размещения переменных. Его основу составляет несколько иной подход к выделению памяти в Неар. Для области Неар в модуле System выделено несколько ключевых переменных-указателей (см. таблицу): HeapOrg, HeapPtr и HeapEnd. Переменная HeapOrg всегда указывает на начало области Неар, переменная HeapEnd указывает на конец области, а переменная HeapPtr содержит указатель на начало нераспределенной памяти в Неар. Естественно, если значение HeapPtr равно значению HeapOrg, то это говорит о том, что область Неар пуста, а если HeapPtr равно HeapEnd, то полностью занята. Любое выделение памяти в Неар приводит к увеличению значениия HeapPtr.
Процедура Mark(var P:Pointer) записывает текущее значение HeapPtr в переменную-указатель Р, тем самым фиксируя текущее состояние Неар. С помощью процедуры Release(var P:Pointer) в области Неар автоматически освобождаются все динамические переменные, распределенные выше указателя Р. При этом текущее значение HeapPtr станет равным Р. Вызов процедуры Mark всегда должен предшествовать вызову процедуры Release. В примере использования Mark и Release задействованно обращение к функции MemAvail, которая возвращает размер свободной памяти в Неар.
var
HeapTop : ^Word;
. . .
begin
Mark(HeapTop);
WriteLn(Размер памяти в Heap:,MemAvail);
New(RealP);
WriteLn(Heap после размещения RealP^:,MemAvail);
New(NameStrP);
WriteLn(Heap после размещения NameStrP^:,MemAvail);
Release(HeapTop);
WriteLn(Heap после Release:,MemAvail)
end.
Процедуры GetMem и FreeMem. Для динамического распределения памяти в Неар служат еще две тесно взаимосвязанные процедуры. Подобно New и Dispose, они во время вызова выделяют и освобождают память для одной динамической переменной. Процедура GetMem(var P: Pointer; Size: Word) создает в Неар новую динамическую переменную Р^ с определенным размером Size. Переменная-указатель Р может указывать на любой допустимый тип. Процедура FreeMem(var P: Pointer; Size: Word) освобождает динамическую переменную заданого размера.
Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу, а значения Size при обращении к одной и той же переменной-указателю должны совпадать. Обращения к GetMem и FreeMem могут полностью соответствовать вызовам New и Dispose. При этом удобно использовать функцию Sizeof, которая возвращает размер памяти, требуемый для размещения значения заданного типа:
New(NameStr);
Dispose(NameStr);
GetMem(NameStrP,Sizeof(NameStr)); {будет тот же результат,}
FreeMem(NameStrP,Sizeof(NameStr)); {что для New и Dispose.}
С помощью процедур GetMem и FreeMem одной переменной-указателю можно выделить разное количество памяти в зависимости от потребностей. В этом заключено основное отличие между ними и процедурами New и Dispose:
GetMem(HeapTop, 40); {выделено 40 байт памяти для HeapTop}
. . .
FreeMem(HeapTop, 40);
GetMem(HeapTop, 2000); {выделено 2000 байт памяти для HeapTop}
. . .
FreeMem(HeapTop, 2000);
Операции со ссылками
Операциями, допустимыми применительно к переменными ссылочного типа в Стандарте языка, являются операция присваивания, т.е. настройка ссылки на некоторый объект (или настройка на фиктивный объект, если ссылке дается значение nil) и операции отношения. Такой подход представляется разумным, поскольку другие операции над ссылками в контексте рекурсивных структур данных бессмысленны. Однако с учетом введения в средства языка стандартного типа pointer, этот набор расширен операцией взятия адреса @.
Операция @ возвращает адрес переменной, т. е. строит значение-указатель, ссылающееся на эту переменную, например:
type
TChar = array[0..1] of Char;
var
Int: Integer;
TCharPtr : ^TChar;
. . .
тогда оператор
TCharPtr := @ Int ;
приводит к тому, что значением TCharPtr становится значение адреса переменной Int, несмотря на объявление TCharPtr : ^TChar.
Тип получаемого в результате применения операции @ указателя управляется директивой компилятора $T: в состоянии $T- (по умолчанию) типом результата будет pointer, т. е. нетипизированный указатель, совместимый со всеми другими типами указателей. В состоянии $T+ типом результата будет ^T, где T тип ссылки на переменную (тип результата будет совместим со всеми другими указателями на тип этой переменной).
Процедурный тип данных
Как уже упоминалось, версии языка, разрабатываемые фирмой Borland по отношению к Стандарту, содержат ряд расширений. Рассмотренные ранее расширения, включая средства модульного конструирования программ, тип указателя (pointer) и строковый тип (string) можно отнести к таким, которые в целом не изменяют основных концепций авторской версии языка.
Введение в средства языка, начиная с пятой версии Borland Pascal, процедурного типа данных следует отнести к существенным расширениям. Этот тип выходит за рамки первоначальной концепции данных и позволяет интерпретировать процедуры и функции как объекты, которые можно использовать при определении переменных и передаче параметров. Значениями процедурного типа являются целые числа в диапазоне, который соответствует значениям адресов вызова процедур и функций (точнее области памяти, в которой предусмотрено их размещение), а также значение nil. Поэтому для данных процедурного типа определение кардинального числа теряет смысл, а какие либо операции над данными этого типа кроме операции присваивания недопустимы.
Введение абстракции процедурного типа в первую очередь связано с дальнейшим расширением языка средствами поддержки объектно-ориентированной технологии программирования. Кроме того, процедурный тип позволяет упростить механизм передачи параметров в процедуру (функцию), если эти параметры сами являются процедурами или функциями и в некоторых случаях имеет самостоятельное применение.
Описание процедурного типа выполняется в разделе типов, т. е. соответствует принятому синтаксису языка, например:
type
Proc=procedure;
SwapProc=procedure(var X,Y : Integer);
StrProc=procedure(Str : String);
ReadProc = procedure (var S : String);
MathFunc=function(X : Real) : Real;
DeviceFunc=function(var F : Text) : Integer;
MaxFunc=function(A,B : Real; F: MathFunc) : Real;
Как видно из примеров, в описании процедурного типа используются зарезервированные слова procedure и function и приводится полное описание параметров, а в случае функции указываются и тип результата. Иными словами, синтаксис описания процедурного типа соответствует синтаксису обычного заголовка процедуры либо функции с той лишь разницей, что опускается их имя. Имена формальных параметров при этом так же играют играют чисто символическую роль и присутствуют в описании только для указания их количества и типа. Таким образом:
<процедурный тип> ::= procedure(<список формальных параметров>)÷
function(<список формальных параметров>) : <тип>
В приведенном выше примере при описании типа MaxFunc в качестве параметра используется процедурный тип MathFunc.
Здесь же следует отметить некоторые ограничения. В частности, запрещено определять функции, возвращающие значение процедурного типа. Вложенные процедуры и функции (определенные внутри другой процедуры или функции) не могут использоваться в качестве переменных процедурного типа вне области их действия.
Определив процедурный тип, в программах можно использовать так называемые процедурные переменные:
var
P : SwapProc;
F: MathFunc;
Переменные процедурного типа можно инициализировать идентификатором процедуры или функции совместимого типа или присваивать им значения других переменных того же типа. При обращении к такой переменной происходит выполнение соответствующей ей процедуры или функции, например:
type
SwapProc = procedure(var X, Y : Integer);
MathFunc = function(A,B : Real);
var
P: SwapProc;
F: MathFunc;
procedure Swap(var A,B : Integer); far;
var
Buf : integer;
begin
Buf := a;
A := B;
B := Buf;
end; {Swap}
function Tan(Angle : real); far;
begin
Tan := Sin(Angle) / Cos(Angle);
end; {Tan}
begin
. . .
P := Swap;
F := Tan;
P(I, J); {эквивалентно Swap(I, J)}
X :=F(X); {эквивалентно X :=Tan(x)}
. . .
Для обеспечения совместимости по присваиванию процедура либо функция в правой части от знака присваивания должна удовлетворять следующим требованиям:
описываться с директивой far и компилироваться с опцией $f+;
íå ìîæåò ÿâëÿòüñÿ ñòàíäàðòíîé ïðîöåäóðîé èëè ôóíêöèåé ÿçûêà;
íå äîëæíà áûòü ïðîöåäóðîé òèïà inline èëè interrupt, ò. å. ïðåäñòàâëÿòü ñîáîé êîäîâóþ âñòàâêó.
Первое из этих ограничений связано с тем, что процедурный тип по представлению совместим с типом pointer. Переменным этого типа в версиях Borland Pascal соответствует адресное пространство в 4 байта длиной, задающее адрес объекта данных в виде <сегмент><смещение>. Для переменных процедурного типа, как уже упоминалось, этот адрес указывает на место в памяти, где находится выполнимый код соответствующей процедуры либо функции. По этой причине при компиляции программ должна быть использована, где это необходимо, директива ($f+) для формирования far-адресации <сегмент><смещение> при последующих вызовах объектов процедурного типа. То же самое можно сказать о вложенных в процедурный тип процедурах и функциях, которые при компиляции располагаются в сегменте памяти машины, зарезервированном под стек, и имеют near - адресацию. Более подробно с директивами, управляющими в этом случае процессом трансляции, можно ознакомиться с помощью фирменной документации для используемой версии системы программирования.
Чтобы иметь возможность присваивать переменным процедурного типа значения, представляющие собой стандартные функции, необходимо создавать специальную "оболочку". Например, стандартная функция sin(x), взятая в оболочку FSin совместима по присваиванию с описанным выше типом MathFunc:
function FSin(X : real): Real; far;
begin
FSin :=sin(X);
end;
Îáîëî÷êà äëÿ ïðîöåäóðû Read может иметь вид:
tуре
($f+) {заменяет директиву far}
procedure MyRead (var S : String); {по типу совместима с ReadProc}
begin
Read (S);
end;
($f-)
. . .
Выше упоминалось, что переменные процедурного типа совместимы с не типизированными переменными-указателями. В связи с этим для получения адреса, по которому располагается выполнимый код процедуры либо функции, являющейся значением некоторой переменной, можно использовать оператор получения адреса @. Чтобы получить адрес размещения в памяти самой переменной процедурного типа, необходимо произвести двойное взятие адреса @@.
Использование процедурных переменных, которым при инициализации или по другим причинам было присвоено значение nil, совместимое с любым процедурным типом, приводит к ошибке. Для исключения подобных ошибок обычно используется проверка вида if @P <> nil then P(I, J).
Являясь по сути спецификацией действий, по представлению процедурный тип все же соответствует данным, причем по аналогии представления он может быть сопоставлен с простым типом. Поэтому на этот тип в полной мере распространяется возможность его использования в качестве компонент структурных типов. В связи с этим допустимы следующие описания:
tуре
MyProc =procedure (X,Y : Byte);
Ref= ^Node;
Node =record
K.Y : Byte;
IsVisible : Boolean;
DrawProc : MyProc;
Next : Ref
end;
NodeArray = array [1..5] of ref;
var
N : Node;
NA : NodeArray;
. . .
Приведенное описание типов и переменных соответствует некоторой динамической структуре (линейному списку), ссылки на элементы которой “собраны” в массив NA. При таком описании возможны обращения вида:
N.DrawProc(1,5);
NA[4]^.DrawProc(6, 2);
и т. п., которые в определенной мере ограничивают присущую языку Паскаль ясность построения программ. Однако подобные обращения считаются одним из профессиональных достоинств языков, ориентированных на объектно-ориентированную технологию программирования (например С++), реализация которой была бы невозможна без введения понятия процедурного типа.