«Любой дурак может написать программу, которую поймет компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты»
20. Параметры подпрограмм
Список формальных параметров подпрограммы определяет число, порядок и тип фактических параметров, которые д.б. указаны при вызове подпрограммы.
Формальные параметры можно классифицировать на следующие виды: параметры значения, переменные, константы и выходные (var, const и out).
Параметры-значения всегда имеют тип, т.е. являются типизированными, в то время как все другие могут быть типизированными или нетипизированными. Кроме того, специфические правила применяются к параметрам массивам.
Файловые переменные и структурные типы, включающие файловые типы, должны всегда объявляться как параметры переменные.
Параметры переменные и параметры значения.
Параметры значения передаются как значения, в то время как параметры переменные передаются по ссылке (reference). Для пояснения различия между ними сравните две функции:
function DoubleByValue(X: Integer): Integer; // X is a value parameter
begin
X := X * 2; Result := X;
end;
function DoubleByRef(var X: Integer): Integer; // X is a variable parameter
begin
X := X * 2; Result := X;
end;
Эти функции возвращают одинаковые значения, однако вторая изменяет значение соответствующего фактического параметра:
var I, J, V, W: Integer;
begin
I := 4; V := 4;
J := DoubleByValue(I); // J = 8, I = 4
W := DoubleByRef(V); // W = 8, V = 8
end;
Даже если одна и та же переменная используется как два или более параметров, никаких копий не создается, например:
procedure AddOne(var X, Y: Integer);
begin
X := X + 1; Y := Y + 1;
end;
var I: Integer;
begin
I := 1;
AddOne(I, I); // i=3
end;
Фактический параметр, соответствующий параметру переменной, должен быть сущностью, которой можно присвоить значение: переменной, типизированной константой, разыменованным указателем, полем или индексированным именем массива.
Параметры константы.
Эти параметры подобны параметрам значениям за тем исключением, что их значения нельзя изменять в подпрограмме. Их нельзя также передавать (внутри подпрограммы) другим подпрограммам как параметры переменные. Вместе с тем, когда передается ссылка на объект (как параметр константа), свойства объекта модифицировать можно.
Использование параметров констант позволяет компилятору оптимизировать код для строковых и структурных параметров. Кроме того, это обеспечивает также защиту от непредумышленной передачи параметра другой подпрограмме в качестве параметра переменной.
Выходные параметры.
Эти параметры (out parameters), подобно параметрам переменным, также передаются по ссылке. Вместе с тем начальное значение выходного параметра игнорируется, так как он предназначен только для возврата результата. Например,
procedure GetInfo(out Info: SomeRecordType);
При вызове этой процедуры ей передается некоторая переменная типа SomeRecordType и она не используется для передачи каких-либо данных процедуре. Выходные параметры часто используются при использовании COM и CORBA моделей. Кроме того, параметр необходимо объявлять выходным, если процедуре передается неинициализированная переменная.
Нетипизированные параметры.
Любой из параметров – var, const или out – может быть объявлен как не имеющий типа, т.е. нетипизированный. Исключение составляет лишь параметр значение, тип которого должен быть указан обязательно. Пример:
procedure TakeAnything(var A; const B; out C);
Если параметр объявлен нетипизированным, соответствующий фактический параметр не может быть числом или нетипизированной числовой константой. Внутри подпрограммы нетипизированный параметр несовместим ни с каким типом и для его использования необходимо обязательно выполнять приведение типа (typecast).
Приведенная ниже функция иллюстрирует использование нетипизированных параметров для того, чтобы выполнять сравнение параметров любых типов:
Function Equal(var Source, Dest; Size: Integer): Boolean;
type
TBytes = array[0..MaxInt - 1] of Byte;
var
N: Integer;
Begin
N := 0;
while (N < Size) and (TBytes(Dest)[N] = TBytes(Source)[N]) do Inc(N);
Equal := N = Size;
End;
Теперь при наличии описаний
type
TVector = array[1..10] of Integer;
TPoint = record
X, Y: Integer;
end;
var
Vec1, Vec2: TVector;
N: Integer;
P: TPoint;
допустимы такие вызовы функции Equal:
Equal(Vec1, Vec2, SizeOf(TVector)) // compare Vec1 to Vec2
Equal(Vec1, Vec2, SizeOf(Integer) * N) // compare first N elements of Vec1 and Vec2
Equal(Vec1[1], Vec1[6], SizeOf(Integer) * 5) // compare first 5 to last 5 elements of Vec1
Equal(Vec1[1], P, 4) // compare Vec1[1] to P.X and Vec1[2] to P.Y
Строковые параметры.
При передаче строковых параметров рекомендуется использовать длинные строки вместо коротких строк (Shortstring) и открытых строк (OpenString). При директивах компилятора {$H–} и {$P+} тип string эквивалентен типу OpenString в описании параметров и такая возможность оставлена только для обратной совместимости. (Директива Н+ включает трактовку типа string как типа AnsiString.)
Параметры открытые массивы (open arrays).
Эти параметры позволяют передавать в подпрограммы массивы произвольных размеров. Благодаря наличию таких параметров появляется возможность избежать предварительного описания типа массива (дать пример).
Например, описание
function Find(A: array of Char): Integer;
позволяет передавать функции массив символов произвольного размера. Отметим, что синтаксис описания открытых массивов напоминает описание динамических массивов, однако это не одно и то же. Функция Find в качестве фактического параметра может получать любой массив символов, включая динамический. Для того чтобы объявить параметр "динамический массив" необходимо предварительно описать тип динамический массив, например:
type TDynamicCharArray = array of Char;
function Find(A: TDynamicCharArray): Integer;
В теле подпрограммы при обработке открытых массивов должны соблюдаться следующие правила:
Ø открытые массивы всегда индексируются с нуля (zero-based array). Для такого массива функция Low всегда возвращает 0, а High– число элементов массива минус 1. Функция SizeOfвозвращает размер массива, переданного ей как фактический параметр;
Ø доступ возможен только к отдельным элементам массива, но не к массиву целиком (имеется в виду присваивание);
Ø внутри подпрограммы открытые массивы могут передаваться в другие подпрограммы только как открытые массивы или нетипизированные параметры;
Ø в качестве фактического параметра, соответствующего открытому массиву, можно передать переменную того же типа. Она трактуется как массив из одного элемента.
Когда открытый массив описан как параметр значение, компилятор создает массив-копию в стеке подпрограммы. Примеры:
procedure Clear(var A: array of Real);
var
I: Integer;
begin
for I := 0 to High(A)-1 do A[I] := 0;
end;
function Sum(const A: array of Real): Real;
var
I: Integer;
begin
Result := A[0];
for I := 1 to High(A)-1 do Result := Result + A[I];
end;
Вариантный массив как параметр открытый массив.
Объявление параметра как вариантного массива позволяет передавать в подпрограмму массивы, состоящие из элементов разного типа. Такие массивы называются гетерогенными (heterogeneous), т.е. неоднородными.
Для описания подпрограммы с параметром вариантный массив надо тип параметра описать как array of const, например
procedure DoSomething(A: array of const);
Описание array of const эквивалентно описанию array of TVarRec (которое тоже можно использовать). Тип TVarRec, описанный в модуле System, представляет собой запись с вариантной частью, которая может содержать значения таких типов, integer, Boolean, character, real, string, pointer, class, class reference, interface и variant. Поле VType этой записи указывает на тип каждого элемента массива.
Некоторые типы передаются как указатели, а не как значения. Например, длинные строки передаются как указатели (Pointer) и их нужно приводить к типу string.
Приведенная ниже функция иллюстрирует использование вариантного массива (как параметра открытый массив) для того, чтобы преобразовать каждый элемент массива в строку, конкатенировать эти строки и возвратить одну длинную строку как результат.
Function MakeStr(const Args: array of const): string;
const
BoolChars: array[Boolean] of Char = ('F', 'T');
var
I: Integer;
Begin
Result := '';
for I := 0 to High(Args) do
with Args[I] do
case VType of
vtInteger: Result := Result + IntToStr(VInteger);
vtBoolean: Result := Result + BoolChars[VBoolean];
vtChar: Result := Result + VChar;
vtExtended: Result := Result + FloatToStr(VExtended^);
vtString: Result := Result + VString^;
vtPChar: Result := Result + VPChar;
vtObject: Result := Result + VObject.ClassName;
vtClass: Result := Result + VClass.ClassName;
vtAnsiString: Result := Result + string(VAnsiString);
vtCurrency: Result := Result + CurrToStr(VCurrency^);
vtVariant: Result := Result + string(VVariant^);
vtInt64: Result := Result + IntToStr(VInt64^);
end;
End;
Идентификаторы VInteger, VBoolean, VChar и др. представляют собой имена полей записи TVarRec. Теперь мы можем вызвать функцию MakeStr, использовав, например, конструктор вариантного массива в качестве фактического параметра:
MakeStr(['test', 100, ' ', True, 3.14159, TForm])
Это выражение вернет строку “test100 T3.14159TForm”.
Параметры по умолчанию (default parameters).
В заголовке подпрограммы можно определить для параметра(ов) подпрограммы значения по умолчанию, которые будут использоваться в том случае, если при вызове подпрограммы соответствующий фактический параметр будет опущен. Значения по умолчанию можно назначить только для параметров значений и параметров констант. Задаваемое значение по умолчанию должно быть константным выражением, совместимым по присваиванию с типом параметра.
Например, если объявить процедуру
procedure FillArray(A: array of Integer; Value: Integer = 0);
то тогда следующие ее вызовы будут эквивалентными:
FillArray(MyArray);
FillArray(MyArray, 0);
Значение по умолчанию должно задаваться для каждого параметра в отдельности. Например, такое описание является недопустимым:
function MyFunction(X, Y: Real = 3.5): Real; // syntax error
Параметры со значениями по умолчанию должны быть последними в списке формальных параметров подпрограммы. Допустимо назначить значения по умолчанию для всех параметров подпрограммы.
Значения по умолчанию могут быть указаны и при объявлении процедурного типа. В этом случае они перекрывают те значения по умолчанию, которые могут быть указаны в заголовке конкретной подпрограммы. Так, при таких описаниях
type TResizer = function(X: Real; Y: Real = 1.0): Real;
function Resizer(X: Real; Y: Real = 2.0): Real;
Begin … End;
…
var
F: TResizer;
N: Real;
операторы
F := Resizer;
F(N);
эквивалентны вызову функции с параметрами (N, 1.0).
Использование параметров со значениями по умолчанию ограничено типами, для которых можно задать значения с помощью константных выражений. Отсюда следует, что параметры типов динамический массив, процедурный, ссылка на класс или интерфейс в качестве значений по умолчанию могут иметь только nil. Параметры типов запись, вариант, файл, статический массив или объект не могут иметь значений по умолчанию вовсе.
Параметры по умолчанию и перегружаемые подпрограммы.
При использовании параметров по умолчанию в перегружаемых подпрограммах нужно избегать неоднозначности интерпретации списка фактических параметров (ambiguous parameter signatures). Сравните, например, такие процедуры:
procedure Confused(I: Integer); overload;
. . .
procedure Confused(I: Integer; J: Integer = 0); overload;
. . .
Confused(X); //Какая из процедур должна вызываться?
Такой код вызовет ошибку компилятора.
Параметры по умолчанию при объявлении подпрограмм в интерфейсной секции и с директивой forward.
В этих случаях значения по умолчанию должны быть указаны в интерфейсной секции или в опережающем описании. Ниже, при описании подпрограммы, их можно опустить, но если параметры вновь приводятся, их описание должно в точности повторять предыдущее.
«19. Перегрузка подпрограмм»
21. Вызовы подпрограмм