Программа на Delphi
Pn1, Pn2: Pointer; {указатели без типа}
Модули System и SysUtils содержат большое количество типов для работы с
указателями. Эти типы могут быть использованы для повышения эффективности
пользовательских программ, в которых используются указатели. К их числу
относятся: PAnsiString, PString, PByteArray, PCurrency, PExtended и ряд
других указателей. Впрочем, эти типы могут быть легко заменены стандартными
типами. Например PString эквивалентен ^String и т.д.
14.1. Операции с указателями
Для указателей допустимы операции присваивания и сравнения. Указателю можно
присваивать:
содержимое указателя такого же типа;
константу Nil (пустой указатель);
адрес объекта с помощью функции Addr;
адрес с помощью оператора @;
адрес, построенный функцией Ptr.
Пример:
TrPt:= Nil;
Klo1Ptr:= Klo2Ptr;
P1:=@Pp; {эквивалентно P1:= Addr(Pp);}
P2:= Ptr($B701);
14.2. Стандартные процедуры и функции для работы с указателями
Procedure GetMem(Var: P: Pointer; Size: Word);
Выделяет блок памяти размера Size и присваивает адрес начала блока
указателю P.
Procedure FreeMem(Var: P: Pointer; Size: Word);
Освобождает блок памяти размера Size, адресованный указателем P.
Ниже приведен подробный пример, демонстрирующий экономный процесс
копирования текстового файла 't1.txt' в файл 't2.txt' с использованием
указателя Buffer.
Var
F1, F2: file; {объявление файловых переменных}
Buffer: PChar; {объявление указателя на строку }
begin
AssignFile(F1, 't1.txt'); {связывание F1 с файлом 't1.txt'}
Reset(F1, 1); {файл открыт для ввода/вывода}
AssignFile(F2, 't2.txt'); {связывание F2 с файлом 'text.txt'}
Rewrite(F2, 1); {файл открыт для вывода}
try
Size := FileSize(F1); {вычисления размера файла}
GetMem(Buffer, Size); {выделение памяти под чтение файла}
try
BlockRead(F1, Buffer^, Size); {считывание всего файла 't1.txt'}
BlockWrite(F2, Buffer^, Size); {запись в файл 't2.txt'}
finally
FreeMem(Buffer); {освобождение памяти}
end;
finally
CloseFile(F1); {закрытие файла F1}
CloseFile(F2); {закрытие файла F2}
end;
end;
В этом примере объявлен указатель на строку Buffer с завершающим нулем,
которая будет использована для копирования файла 't1.txt' в файл 't2.txt'.
Для этого оператором GetMem для переменной Buffer^ будет динамически
выделен блок памяти размером, равным размеру файла. Далее оператором
BlockRead файл 't1.txt', связанный файловой переменной F1, будет считан в
Buffer^ и затем оператором BlockWrite переменная Buffer^ будет записана в
файл 't2.txt', связанный с файловой переменной F2. Для предотвращения
исключительных ситуаций пример содержит два вложенных блока try – finally –
end. Внутренний блок обслуживает возможный сбой в ситуации, когда по какой-
либо причине файл не удалось прочитать или записать операторами BlockRead
или BlockWrite. Такой способ гарантирует освобождение памяти оператором
FreeMem как в случае успешного копирования, так и в случае возможного сбоя.
Внешний блок обслуживает ситуацию, когда у системы возможно нет того объема
памяти, который запрашивает оператор GetMem. В любых вариантах – при
успешном или безуспешном копировании файла – следующие за последним finally
операторы CloseFile закроют открытые операторами Reset и Rewrite файлы F1 и
F2 и позволяет программе продолжить работу.
Procedure New(Var: P: Pointer);
Создает новую динамическую переменную того типа, на который ссылается
указатель. Эквивалентна оператору GetMem(P, SizeOf(P^));
Procedure Dispose(Var: P: Pointer);
Уничтожает динамическую переменную, на которую указывает P. Эквивалентна
оператору FreeMem(P, SizeOf(P^));
Procedure ReallocMem(var P: Pointer; Size: Integer);
Процедура работает следующим образом:
если P= Nil и Size = 0, то оператор не выполнит никаких действий;
если P= Nil и Size > 0, то оператор сработает аналогично GetMem;
если P <> Nil и Size = 0, то оператор сработает аналогично FreeMem.
Function Addr(X): Pointer;
Адрес указанного имени.
14.3. Прочие процедуры и функции для работы с указателями
В модулях System и SysUtils объявлены процедуры и функции, которые могут
найти применение в пользовательских программах. Ниже дано описание
некоторых функций и процедур.
Function GetHeapStatus: THeapStatus;
Расположена в модуле System. Дает сведение о состоянии распределен-ной и
доступной программе памяти. Тип функции имеет вид
THeapStatus = record
TotalAddrSpace: Cardinal;
TotalUncommitted: Cardinal;
TotalCommitted: Cardinal;
TotalAllocated: Cardinal;
TotalFree: Cardinal;
FreeSmall: Cardinal;
FreeBig: Cardinal;
Unused: Cardinal;
Overhead: Cardinal;
HeapErrorCode: Cardinal;
end;
Function AllocMem(Size: Cardinal): Pointer;
Выделяет блок памяти и устанавливает каждый байт "в нуль". Освобо-ждение
памяти можно выполнить с помощью процедуры FreeMem.
Procedure GetMemoryManager(var MemMgr: TMemoryManager);
Дает текущее состояние менеджера памяти – специальной записи с типом:
TMemoryManager = record
GetMem: function(Size: Integer): Pointer;
FreeMem: function(P: Pointer): Integer;
ReallocMem: function(P: Pointer; Size: Integer): Pointer;
end;
Procedure SetMemoryManager(var MemMgr: TMemoryManager);
Устанавливает менеджер памяти – выполняет операции выделения и освобождения
памяти в соответствии с предварительно установленными в менеджере памяти
значениями.
14.4. Глобальные переменные AllocMemCount и AllocMemSize
В модуле System объявлены две глобальные переменные, значения которых могут
быть использованы при контроле за динамически создаваемыми и разрушаемыми
пользовательскими переменными.
AllocMemCount – количество блоков выделенной памяти.
AllocMemSize – размер блоков выделенной памяти.
15. Подпрограммы
Подпрограмма – это законченная алгоритмическая единица, которая
предназначена для выполнения некоторого ограниченного по отношению к
использующей ее программе круга операций.
В языке Object Pascal есть два вида подпрограмм – процедуры и функции.
Структура всякой подпрограммы во многом напоминает структуру исходного
модуля. Каждая такая подпрограмма перед ее использованием должна быть
описана. Описанием подпрограммы называется ее исходный код, а обращением к
подпрограмме является оператор или его часть, которые содержат код вызова
такой подпрограммы. Таким образом, описание – это технология, а обращение –
это действия по предписанной технологии.
Всякая подпрограмма может иметь локальные и глобальные по отношению к ней
параметры. Локальным является параметр, действие которого ограничено только
подпрограммой, в которой он описан. Всякий другой параметр будет
глобальным. Все глобальные параметры всегда описаны за пределами
подпрограммы, в которой они используются.
15.1. Процедуры
Всякая процедура имеет заголовок и тело. Тело процедуры состоит из
операторов, предназначенных для описания имен и действий над данными.
Синтаксис процедуры имеет вид
Procedure procedureName(parameterList); directives;
localDeclarations;
begin
statements;
end;
Здесь
Name – имя процедуры,
parameterList – список формальных параметров,
directives – директивы,
localDeclarations – внутренние описания,
statements – операторы тела процедуры.
procedureName – имя процедуры. Именем процедуры может быть любое имя, не
совпадающее ни с каким другим описанным в том же, что и процедура, блоке,
именем.
parameterList – список формальных параметров может быть либо пуст (в этом
случае скобки можно не использовать), либо должен содержать
последовательность входных и/или выходных величин. Отдельная величина в
описании заголовка может быть:
объявленной с помощью слова var переменной с типом или без типа;
константой;
выходной величиной (т. н. out-параметром).
Пример описания процедуры.
procedure ByRef(var X: Integer; L, K: Integer);
begin
X := X * 2 * L; {правильно}
K := 2 + L; {ошибка}
end;
Процедура c именем ByRef содержит три параметра – переменную X и две
константы L и K. Тело процедуры состоит из операторов, заключенных в
операторных скобках begin – end. Переменные L, K являются только входными и
не могут быть изменены в теле процедуры. По этой причине оператор K:= 2 + L
не даст результата и значение К останется таким же, каким оно было до
обращения к процедуре. Напротив, переменная X, объявленная с помощью слова
var, будет вычислена и на выходе будет иметь то значение, которое будет
вычислено внутри процедуры ByRef.
Out-параметр являет прямую противоположность константе, он должен быть
только выходной, определяемой внутри процедуры, величиной. Например, в
следующем коде
procedure GetInfo(out Info: SomeRecordType);
var MyRecord: SomeRecordType;
...
Proc1(MyRecord);
…
переменная MyRecord не может передавать данные в процедуру Proc1. Напротив,
только Proc1 может сформировать данные и передать их в MyRecord.
Тип параметра может быть любым. Однако нельзя объявлять новый тип прямо в
заголовке подпрограммы. Такой тип должен быть объявлен раньше и только
после этого может быть использован в заголовке подпрограммы.
localDeclarations – внутренние описания могут содержать описание локальных
имен типов или переменных. В предыдущем примере переменная MyRecord типа
SomeRecordType, объявленная внутри процедуры GetInfo, является образцом
такого описания.
directives – директивы используются для того, чтобы дать компилятору
некоторые дополнительные указания об описываемой процедуре. Директивы
описаны в параграфе 15.3.
Для вызова процедуры, т. е. для обращения к заложенному в ней алгоритму,
следует записать оператор, в котором на место формальных параметров должны
быть подставлены фактические параметры. Следующий пример содержит как
описание процедуры MatrMult, так и обращение к ней.
Program {начало программы}
…
Const n = 10; {размер матриц}
Type tMatr = array[1.. n, 1.. n] of Real; {тип матриц}
Var Q1, Q2, Q3: tMatr; {описание матриц}
Procedure MatrMult(M1, M2: tMatr; Var M3: tMatr); {заголовок}
Var i, j, k: Integer;
Begin
For i:= 1 to n do
For j:= 1 to n do
Begin {блок определения одного элемента матрицы M3}
M3[i, j]:=0;
For k:=1 to n do
M3[i, j]:= M3[i, j] + M1[i, k] * M2[k, j];
End;
End; {конец описания процедуры MatrMult}
Procedure Prim; {заголовок процедуры Prim}
Var i, j: Integer;
Begin
For i:= 1 to n do {блок задания элементов матриц Q1, Q2}
For j:= 1 to n do
Begin
Q1[i, j]:=i + j;
Q2[i, j]:=i - j;
End;
MatrMult(Q1, Q2, Q3); {оператор обращения к процедуре MatrMult}
End; {конец описания процедуры Prim}
… {текст головной программы}
Prim; {обращение к процедуре Prim}
Q3[2, 3]:= 1;
…
Приведенный в данном примере текст программы можно разделить на четыре
части:
описание глобальных констант и переменных;
текст процедуры MatrMult;
текст процедуры Prim;
текст головной программы.
В верхней части описана глобальная константа n = 10, которая используется
во всех следующих за этим структурах. Так, в секции Type объявлен тип
вещественного квадратного массива tMatr. Далее в секции Var объявлены три
переменные – матрицы Q1, Q2, Q3 типа tMatr.
Далее дан текст процедуры MatrMult, которая представляет алгоритм
перемножения матриц M1 и M2 с записью результата в переменную M3. Матрицы
M1, M2 в процедуре не меняются, поэтому их необязательно объявлять
переменными (с позиций процедуры MatrMult переменные M1, M2 выступают в
качестве констант). Напротив, матрица M3 получается как результат,
определяемый внутри процедуры MatrMult, поэтому она объявлена в заголовке
словом Var. Необъявление M3 как переменной привело бы к ошибке: измененные
внутри процедуры значения этой матрицы не были бы зафиксированы. Переменные
i, j, k, объявленные внутри процедуры, являются локальными и действуют
только в пределах этой процедуры.
Описание процедур заканчивается процедурой Prim. В теле этой процедуры
объявлено две локальные переменные i, j, выполняющих вспомогательную роль
счетчиков циклов. Далее расположено два вложенных друг в друга цикла, с
помощью которых определяются элементы матриц Q1 и Q2. По отношению к
процедуре Prim эти матрицы являются глобальными переменными и,
следовательно, они доступны не только во внешнем блоке, но и внутри
процедуры Prim. И наконец, в нижней части расположен оператор обращения к
процедуре MatrMult, который предназначен для вызова алгоритма перемножения
матриц.
В нижней части программы дан фрагмент текста головной программы, содержащей
вызов процедуры Prim.
Опишем механизм вычислений, который запускается приведенной программой.
Сначала будет выполнена процедура Prim (обращение к ней содержится в самой
нижней части текста примера). Эта процедура без параметров, поэтому
управление будет сразу передано в тело процедуры, где начнется
последовательное выполнение содержащихся в нем операторов. Сначала будут
выполнены два вложенных цикла For, где элементы матриц Q1, Q2 будут
заполнены значениями (например Q1[1, 1] = 2, Q2[1, 1] = 0 и т. д.). Далее
уже внутри Prim произойдет обращение к процедуре MatrMult. При этом сначала
произойдет подстановка фактических параметров Q1, Q2, Q3 на место
соответствующих формальных параметров M1, M2, M3. Далее управление будет
передано внутрь процедуры MatrMult, где аналогично последовательным
выполнением ее операторов произойдет перемножение матриц. После выполнения
процедуры MatrMult управление будет передано в ту же точку, с которой
производился ее вызов. Поскольку вызов MatrMult производился из Prim, то
управление будет вновь возвращено в процедуру Prim к оператору,
расположенному вслед за оператором вызова MatrMult. Поскольку в Prim больше
нет невыполненных операторов, то она также заканчивает свою работу и
управление передается в ту алгоритмическую единицу, которая и вызывала
Prim, а именно в головную программу, к оператору Q3[2, 3] := 1.
15.2. Функции
В отличие от процедуры функция предназначена для вычисления одного значения
любого типа. Тип функции указывается в конце ее заголовка. Тип
возвращаемого значения отделяется от списка формальных параметров символом
":" (двоеточие). Кроме того, в теле функции, по крайней мере, один раз
должно быть определено значение функции. Это значение присваивается имени
функции или переменной Result.
Синтаксис функции имеет вид
function functionName(parameterList): returnType; directives;
localDeclarations;
begin
statements;
end;
Здесь functionName – имя функции; ParameterList, directives,
localDeclarations, statements имеют тот же смысл, что и в процедуре;
ReturnType – тип возвращаемого результата.
Пример.
Function Fact(n: Word): LongInt; {заголовок функции Fact}
Var i: Word; j: LongInt;
Begin
j:=1;
if (n > 1) then
For i:= 2 to n do j:= j * i;
Result:= j;
End; {конец описания функции Fact }
… {текст головной программы}
Var r: Real;
…
r:= 3.4 * Fact(3) / 2.5 / (122 - Fact(5)); {обращение к функции Fact}
…
В этом примере описана функция с именем Fact вычисления факториала n!
неотрицательного целого числа. Тип функции определен как LongInt. В теле
функции размещен оператор Result:= j, который определяет возвращаемый
функцией результат. Способ обращения к функции демонстрирует последний
оператор примера. Видно, что способ обращения к функции имеет существенное
отличие от способа обращения к процедуре. Так, в этом операторе обращение к
функции Fact производится дважды – один раз с фактическим параметром 3,
другой – с параметром 5. Далее возвращенные результаты (соответственно, 6 и
120) будут подставлены в выражение правой части оператора, после чего
последний будет вычислен и переменная r получит вещественное (Real)
значение 4.08.
15.3. Параметры без типа
Это особый вид параметров, который может быть использован только в
заголовках имен процедур и функций. Каждый параметр без типа должен быть
описан как var, const или out-параметр. Например:
procedure TakeAnything(const C);
описывает С как константу без типа.
Параметр без типа компилятор расценивает как параметр потенциально любого
типа. Это означает, что в тело подпрограммы передается только адрес
параметра и компилятор не контролирует правильность выполнения операций над
таким параметром.
Однако имеется одно исключение: при обращении к подпрограммам, содержащим
параметры без типа, нельзя использовать числовые значения или
нетипизированные числовые константы.
Следующий пример использует параметры без типа в функции Compare, которая
сравнивает размеры двух переменных V1 и V2 и возвращает ответ в виде
константы –1, если размер V1 меньше размера V2, нуль – если размеры
одинаковы, 1 – если размер V1 меньше размера V2.
function Compare(var V1, V2): ShortInt;
Var i, j: LongInt;
Begin
I:=SizeOf(V1);
J:=SizeOf(V2);
If (I < J) then Result:= -1
Else
If (I > J) then Result:= 1
Else Result:= 0;
End;
Примеры обращений к функции Compare:
type
TVector = array[1..10] of Integer;
TPoint = record
X, Y: Integer;
end;
var
Vec1, Vec2: TVector;
N,i: Integer;
P: TPoint;
…
i:= Compare(Vec1, Vec2) ; {0, размеры одинаковы}
Vec[1]:= Compare(i, Vec1); {-1, размер i меньше размера Vec1}
P.X:= Compare(Vec1, P); {1, размер Vec1 больше размера P}
P.Y:= Compare(i, P); {-1, размер i меньше размера P}
Vec2[8]:= Compare(i, P.X); {0, размеры i и поля P.X одинаковы}
…
15.4. Декларации процедур и функций
Заголовок процедуры или функции может содержать декларацию. Декларация
указывается сразу вслед за списком параметров в процедуре или за типом
возвращаемого результата в функции.
1. Декларации о вызове по соглашению (calling convention). К их числу
относятся декларации register, pascal, cdecl, stdcall и safecall, например:
function MyFunction(X, Y: Real): Real; cdecl;
Этот вид деклараций предназначен для задания способа передачи параметров в
процедуру. Декларации register, pascal передают параметры слева направо,
cdecl, stdcall и safecall – наоборот, справа налево.
Директивы near, far и export предназначены для разграничения способов
обращения в 16-разрядных приложениях. Для современных 32-разрядных
приложений они не имеют значения.
Отметим, что упомянутые декларации используются в сложных, весьма тонких
ситуациях и для начинающего программиста не представляют большого интереса.
2. Директива Forward указывает на то, что заголовок процедуры или функции
объявлен раньше, чем описана сама подпрограмма, например:
function Calculate(X, Y: Integer): Real; forward;
…
function Calculate;
...
begin
...
end;
3. Директива External указывает на то, что текст процедуры содержится в
отдельном объектном (откомпилированном) модуле. Такой способ позволяет
присоединить объектный код к компилируемой программе из указанного модуля.
Для этого необходимо указать директиву компилятора со ссылкой на модуль, в
котором содержится объектный код декларируемой процедуры, например:
{$L BLOCK.OBJ}
…
procedure MoveWord(var Source, Dest; Count: Integer); external;
procedure FillWord(var Dest; Data: Integer; Count: Integer); external;
Этот пример показывает, что при компиляции коды процедур MoveWord и
FillWord следует искать в объектном коде BLOCK.OBJ.
4. Директива OverLoad позволяет использовать одно имя для нескольких
подпрограмм, например:
function Divide(X, Y: Real): Real; overload;
begin
Result := X/Y;
end;
function Divide(X, Y: Integer): Integer; overload;
begin
Result := X div Y;
end;
В этом примере описаны две функции с одним именем Divide. При обращении к
функции с таким именем вызвана будет та функция, фактические параметры
которой соответствуют формальным параметрам. Так, при обращении в виде
Divide (6.8, 3.2) будет вызвана первая функция, т. к. ее формальные
параметры также вещественны, а при обращении Divide(6, 8) будет вызвана
вторая функция.
Директива Overload разрешена для подпрограмм, в которых могут различаться
только типы параметров, поэтому недопустимы описания вида
function Cap(S: string): string; overload;
procedure Cap(var Str: string); overload;
15.5. Процедурные типы
Процедурные типы допускают использование процедур и функций в виде
значений, которые могут быть присвоены переменным или переданы в другие
процедуры или функции. Например, в нижеследующем примере определена функция
Calc с двумя целочисленными формальными параметрами X, Y, возвращающая
целый тип:
function Calc(X,Y: Integer): Integer;
Эта функция может быть определена как тип для переменной F:
var F: function(X,Y: Integer): Integer;
и связана с этой переменной оператором присваивания:
F := Calc;
Точно так же можно определить любой другой новый процедурный тип и
переменную:
Type {объявление процедурных типов}
TIntegerFunction = function: Integer;
TProcedure = procedure;
TStrProc = procedure(const S: string);
TMathFunc = function(X: Double): Double;
Var {объявление процедурных переменных}
F: TIntegerFunction; {F функция без параметров, возвращающая целое}
Proc: TProcedure; {Proc – процедура без параметров}
SP: TStrProc;
M: TMathFunc;
При использовании операторов над процедурными типами и процедурами или
функциями необходимо различать связывание процедурной переменной и
обращение к процедуре или функции. Так в нижеследующем примере объявляются
переменная F типа функции, а переменная I – простого целочисленного типа,
затем следует текст процедуры SomeFunction:
F: function(X: Integer): Integer;
I: Integer;
function SomeFunction(X: Integer): Integer;
...
В операторной части первый оператор связывает F с конкретной функцией, не
производя над ней никаких действий:
F := SomeFunction;
напротив, оператор
I := F(4);
вызывает эту функцию (запускает ее алгоритм) и после обработки возвращает
результат вычислений переменной I.
15.6. Формальные и фактические параметры
В Object Pascal есть понятия формального и фактического параметров.
Формальным называется параметр, который содержится в заголовке описания
подпрограммы, а фактическим – параметр в обращении к подпрограмме. Так, в
вышеприведенном примере параметр X является формальным, а значение 4 –
фактическим.
При вызове подпрограмм необходимо иметь в виду следующее:
. фактические значения или константы должны быть совместимы по типу с
объявленными формальными параметрами;
. фактические var или out-параметры должны быть идентичны по типу
объявленным формальным параметрам, исключением являются только
нетипизированные параметры;
. формальным параметрам без типа не могут соответствовать такие
фактические параметры, как числовые значения и нетипизированные число-
вые константы.
Приведем еще один пример, демонстрирующий способы обращения к
подпрограммам.
Const
IntCount = 1200;
Type
TFunc12 = Function(c1, c2: Integer): Integer;
Function Func12_1(k1, k2: Integer): Integer;
Begin
Result:= k1 + k2;
End;
Function Func12_2(g1, g2: Integer): Integer;
Begin
Result:= g1 - g2;
End;
Procedure AnyPro(u1: Real; Var u2: Real; Var u3; Const u4: Integer; F:
tFunc12);
Begin
u2:= u1 + u4 + F(u4, Round(u4 * 3.14));
u3:= u1 - u4 - F(u4, Round(u4 * 3.14));
End;
Var
k: Integer;
v1, v2: Real;
ss: String;
…
{примеры обращения к процедуре AnyPro:}
AnyPro(v1, v2, v1, v2, Func12_1);
AnyPro(v1, v2, ss, v1, Func12_2);
AnyPro(k, v1, ss, v2, Func12_1);
AnyPro(k + 8, v2, ss, IntCount, Func12_1);
AnyPro(8, v2, ss, v1+6.7, Func12_2);
Параметры u1, u2, u3, u4, F в заголовке процедуры AnyPro, являются
формальными параметрами: u1 – константа типа Real; u2 – переменная типа
Real; u3 – переменная без типа; u4 – константа типа Integer; F – параметр-
функция типа TFunc12, который объявлен выше в секции Type.
Параметры, заключенные в скобки в примерах обращения к процедуре AnyPro,
являются фактическими параметрами. Такие параметры могут быть значениями
(8), константами (IntCount), переменными (v1), выражениями (k + 8), именами
процедур или функций (Func12_1) и др.
15.7. Область действия имен
В подпрограммах часть параметров может быть объявлена прямо в заголовке или
ее теле. Такие параметры действуют только внутри этой подпрограммы и
поэтому называются локальными параметрами. В то же время в подпрограмме
можно использовать параметры, которые описаны за пределами подпрограммы.
Такие параметры называются глобальными параметрами.
Глобальные параметры могут быть описаны в том же модуле, который содержит
использующую их подпрограмму, или в другом модуле, на который имеется
ссылка в списке uses. Если два параметра имеют одинаковое имя и один из них
описан внутри подпрограммы, а другой – вне ее, то действует тот параметр,
который описан в подпрограмме. Аналогично определяется область доступности
параметров описанных в разных модулях. Таким образом, при описании имен
действует следующий принцип: более позднее объявление отменяет облаcть
действия ранее описанных имен. Внутри одной подпрограммы нельзя объявлять
двух и более одинаковых имен.
Поясним область действия имен на примере следующего модуля
Unit Mod4;
interface
uses Mod1, Mod2, Mod3;
….
Type
Vr = Integer; {допустимо}
…
Var
Vr: Real; {недопустимо}
…
implementation
Var Vr: Char; {недопустимо}
…
procedure Pro1; {не содержит внутреннего объявления имени Vr}
…
procedure Pro2; { содержит внутреннее объявление имени Vr}
Var
Vr: String; {допустимо}
Vr: Real; {недопустимо}
…
В приведенном тексте модуля Mod4 содержится несколько описаний имени Vr,
часть которых допустима, другая часть ошибочна. Недопустимо описание этого
имени:
в var-секции в разделе interface, так как оно уже использовано в этом же
разделе выше – в секции type;
в var-переменной в разделе implementation, так как оно уже использовано в
этом же модуле в разделе interface;
как переменной типа Real в теле процедуры Pro2, т. к. оно уже использовано
в этой же процедуре при описании String-переменной.
Более позднее объявление отменяет действие ранее описанного имени. Так,
внутри процедуры Pro2 имя Vr представляет переменную типа String, а внутри
процедуры Pro1 имя Vr действует как глобальный тип Integer, объявленный
выше – в секции type.
Если бы это имя вообще не было описано в модуле Mod4, но было бы объявлено
в одном или нескольких модулях, указанных в ссылочном списке uses, то оно
могло бы быть использовано как глобальный параметр внутри этого модуля
(Mod4). При этом действовало бы то имя, которое объявлено в разделе
interface самого последнего содержащего его модуля списка uses. Например,
если имеется описание имени Vr в модулях Mod1 и Mod2, то действовало бы
описание из Mod2. Если в списке uses поменять Mod1 и Mod2 местами, то будет
действовать описание, которое выполнено для этого имени в модуле Mod1.
Следует проявлять особую осторожность при использовании глобальных
переменных в подпрограммах. Нижеприведенный пример демонстрирует
непредсказуемое поведение программы, использующей функцию Deccy и
глобальный по отношению к ней параметр d:
Function Deccy(x: Integer) : Integer;
Begin
d:= d - x;
Deccy:= Sqr(x);
End;
…
d:= 3; a:= Deccy(3) * Deccy(d); {a= 0, d= 0}
d:= 3; a:= Deccy(d) * Deccy(3); {a= 81, d= -3}
Пример показывает, что два, казалось бы, корректных способа обраще-ния к
функции дают тем не менее разные результаты вычислений.
15.8. Рекурсивные процедуры и функции
В Object Pascal допустимо обращение подпрограммы к самой себе (рекурсивное
обращение). При таком обращении параметры, которые использует подпрограмма,
заносятся в стек и сохраняются там до конца работы подпрограммы.
Рекурсивные подпрограммы являются исключительно удобным, нередко
незаменимым инструментом построения эффективных алгоритмов. Оборотной
стороной рекурсивных процедур является опасность переполнения стека, что
часто ограничивает возможность написания таких алгоритмов.
В качестве иллюстрации приведем пример простой и чрезвычайно эффективной
процедуры сортировки (расстановки элементов в порядке неубывания) фрагмента
целочисленного одномерного массива A:
procedure QuickSortPart(var A: array of Integer; iLo, iHi: Integer);
var
Lo, Hi, Mid, T: Integer;
begin
Lo := iLo;
Hi := iHi;
Mid := A[(Lo + Hi) div 2]; {средний элемент фрагмента}
repeat {деление фрагмента на левую и правую части}
while A[Lo] < Mid do Inc(Lo);
while A[Hi] > Mid do Dec(Hi);
if Lo Hi;
if Hi > iLo then QuickSortPart(A, iLo, Hi); {сортировка левой части}
if Lo < iHi then QuickSortPart (A, Lo, iHi); {сортировка правой части}
end;
Процедура QuickSortPart сортирует фрагмент одномерного массива A,
начинающийся индексом iLo и заканчивающийся индексом iHi. Процедура
основана на методе половинного деления. В соответствии с этим методом
сначала выбирается элемент, расположенный в середине сортируемого
фрагмента, затем элементы меньшие его отправляются в левую часть фрагмента,
прочие – в правую часть. Далее сортируются левая и правая части
разделенного массива как отдельные фрагменты по той же схеме, т. е. к
каждой из них применяется та же процедура QuickSortPart. Именно обращение
процедуры к самой себе и делает ее рекурсивной.
Ниже приведена обычная (нерекурсивная) процедура QuickSort сортировки всех
элементов массива, которая выполняется обращением к рекурсивной процедуре
QuickSortPart, где фрагмент – весь массив A.
procedure QuickSort (var A: array of Integer);
begin
QuickSortPart(A, Low(A), High(A));
end;
15.9. Параметры и конструкторы открытых массивов
Открытые массивы допускают передачу массивов различного размера в качестве
параметров в процедурах и функциях. В этом случае можно объявить массив в
виде
array of type (предпочтительнее array[X .. Y] of type)
Например, операторы
procedure NullChar(A: array of Char);
begin
for i:= Low(A) to High (A) do A[i]:= '0';
end;
объявляют процедуру NullChar, которая содержит один параметр – открытый
символьный массив А любого размера. В теле процедуры используется оператор
цикла, который заполняет каждый элемент массива символом '0'. Для
определения нижней границы индекса использована стандартная функция Low,
для верхней – High.
Если к такой процедуре обратиться оператором NullChar(z), где тип
переменной z = array[5 .. 55] of Char, то весь массив z будет заполнен
символами "нуль".
Конструкторы открытых массивов допускают конструирование значений таких
массивов прямо внутри оператора обращения к подпрограмме.
Пример:
var I, J: Integer;
procedure Add (A: array of Integer);
В этом случае можно обратиться к процедуре Add, например, так:
Add ([5, 7, I, I + J]);
16. Структура программы
В среде Delphi программа как единое целое представляется в виде проекта. В
новой версии языка Object Pascal для представления проекта используется
пять основных типов файлов:
. dpr-файл головной программы;
. текстовые pas-файлы;
. откомпилированные dcu-файлы;
. res-файлы ресурсов;
. dfm-файлы ресурсов экранных форм;
. готовые к использованию программные exe-файлы.
Исходная программа, написанная в среде Delphi на языке Object Pascal всегда
состоит из нескольких модулей, каждый из которых размещается в отдельном
текстовом файле. Один модуль является головной программой. Он начинается
словом Program и размещается в файле с расширением .dpr. Все остальные
модули являются подчиненными и начинаются словом Unit. Такие модули
размещаются в файлах с расширением .pas. Все модули заканчиваются
оператором End, после которого ставится символ "точка".
Всякий модуль может использовать другие модули, к числу которых могут
относиться текстовые файлы, res- и dfm-файлы ресурсов или откомпилированные
файлы Unit-модулей. Сcылка на необходимые к использованию модули содержится
в секциях Uses. Текстовые или скомпилированные файлы обычно содержат
необходимые для использующего их модуля величины – константы, типы,
переменные, процедуры и функции. Файлы ресурсов необходимы для подключения
констант, описывающих используемые внешние ресурсы.
Вышеперечисленные модули, размещенные в *.pas-, *.dcu-, *.res-, *.dfm-
файлах, играют вспомогательную роль: они предназначены для компиляции и
последующей сборки в полноценный программный модуль – exe-файл, готовый к
исполнению на компьютере.
Ниже приведен пример исходных текстов головной программы KdnBread и одного
подчиненного (используемого) ею модуля Main.
Program KdnBread; {начало текста головной программы}
{текст содержится в файле 'c:\Borland\Projects\KdnBread.pas'}
uses {ссылки на модули типа unit }
Forms, {ссылка на модуль Forms }
main in 'main.pas' {Form1}; {ссылка на модуль main }
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end. {конец текста головной программы}
unit Main; {начало текста модуля Main}
{ текст модуля содержится в файле 'c:\Borland\Projects\Main.pas' }
interface {начало интерфейсной части модуля}
uses
Windows, Messages, SysUtils, {ссылки на другие модули }
Graphics, Controls, Forms, StdCtrls;
Type {описание типов}
TForm1 = class(TForm)
Button1: TButton;
L1: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Var {описание переменных}
Form1: TForm1;
b: boolean;
i: Integer;
IterationPar: Word;
function OneSymbString(c: Char; d: byte): String; {заголовок функции}
implementation {начало процедурного блока модуля}
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject); {заголовок процедуры}
begin
if (i > 12) and b then
L1.Caption:='Студент:'+AnsiUpperCase ('Иванов Владимир Иванович');
end; {конец процедуры}
function OneSymbString(c: Char; d: byte): String; {заголовок функции}
begin
Result:=CharStr(c, d);
end; {конец функции}
initialization
IterationPar:= 0;
end. {конец текста модуля Main}
Выполнение программы всегда начинается с модуля Program, т. е. с головной
Страницы: 1, 2, 3, 4, 5
|