вторник, 5 апреля 2011 г.

Процедуры и функции

Перевод раздела Procedures and Functions (Delphi) из справочной системы Delphi

О процедурах и функциях

Процедуры и функции обобщенно называют подпрограммами. Они представляют собой автономные блоки инструкций, которые могут быть вызваны из различных мест программы. Функция – это подпрограмма, которая при выполнении возвращает значение. Процедура – это подпрограмма, которая не возвращает значения.

Вызовы функций, поскольку они возвращают значение, могут быть использованы как выражения в инструкциях присваивания и прочих операциях. Например:

I := SomeFunction(X);

Вызывает SomeFunction и присваивает результат переменной I. Вызов функции не может выполняться в левой части инструкции присваивания.

Вызовы процедур (и функций при включенном расширенного синтаксиса{$X+}), могут представлять собой завершенные инструкции. Пример:

DoSomething;

Вызывает подпрограмму DoSomething ; в том случае, если DoSomething является функцией, возвращаемое ею значение отбрасывается.

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

Объявление процедур и функций

Когда вы объявляете процедуру или функцию, вы указываете ее имя, количество и тип параметров, которые она принимает, и, в том случае, если вы объявляете функцию, вам необходимо указать тип значения, которое она возвращает (эта часть объявления иногда называется прототипом, заглавием или заголовком). Затем вы пишете блок программного кода, который выполняется при вызове процедуры или функции. Эта часть иногда называется телом или блоком подпрограммы.


Объявления процедур

Объявление процедуры имеет следующий синтаксис:
procedure procedureName(parameterList); directives;
  localDeclarations;
begin
  statements
end;

procedureName – любой допустимый идентификатор, statements – это последовательность инструкций, которая выполняется при вызове процедуры, (parameterList), directives; и localDeclarations; - необязательны. Далее приведен пример объявления процедуры:

procedure NumString(N: Integer; var S: string);
var
  V: Integer;
begin
  V := Abs(N);
  S := ;
  repeat
    S := Chr(V mod 10 + Ord('0')) + S;
    V := V div 10;
  until V = 0;
  if N < 0 then S := '-' + S;
end;
После этого объявления вы можете вызывать процедуру NumString procedure следующим образом:
NumString(17, MyString);
Вызов этой процедуры присваивает значение '17' переменной MyString (которая должна быть строкового типа).

Внутри блока инструкций процедуры вы можете пользоваться переменными и прочими идентификаторами, объявленными в разделе localDeclarations. Кроме того, вы можете использовать имена параметров из списка параметров (как, например, N и S в приведенном примере). Список параметров определяет множество локальных переменных. То есть не следует пытаться повторно объявить в секции localDeclarations переменные с именами, указанными в списке параметров. Наконец, вы можете использовать любые идентификаторы, в области видимости которых объявлена процедура.


Объявления функций

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

function functionName(parameterList): returnType; directives;
  localDeclarations;
begin
  statements
end;

functionName – любой допустимый идентификатор, returnType – это идентификатор типа, statements – это последовательность инструкций, которая выполняется при вызове функции, а (parameterList), directives; и localDeclarations; являются необязательными.

В теле функции действуют те же правила, которые относятся и к процедурам. Внутри блока инструкций вы можете пользоваться идентификаторами, объявленными в секции localDeclarations, параметрами, указанными в parameter list и любыми идентификаторами, в области видимости которых объявлена функция. Кроме того, имя функции так же действует как специальная переменная, которая содержит значение, возвращаемое функцией. Аналогично действует и предопределенная переменная Result.

При включении расширенного синтаксиса ({$X+}), переменная Result неявно объявляется в каждой функции. Не пытайтесь объявить ее повторно. Пример:

function WF: Integer;
begin
  WF := 17;
end;

объявляет функцию с именем WF, которая не имеет параметров и всегда возвращает целочисленное значение 17. Это объявление эквивалентно:

function WF: Integer;
begin
  Result := 17;
end;
Далее приведено объявление более сложной функции:
function Max(A: array of Real; N: Integer): Real;
var
  X: Real;
  I: Integer;
begin
  X := A[0];
  for I := 1 to N - 1 do
    if X < A[I] then X := A[I];
  Max := X;
end;

В теле функции вы можете повторно присваивать значение переменной Result или имени функции (присваиваемые значения должны быть соответствующего типа). Когда выполнение функции прекращается, последнее значение, присвоенное переменной Result или имени функции, становится значением, возвращаемым функцией. Например:

function Power(X: Real; Y: Integer): Real;
var
  I: Integer;
begin
  Result := 1.0;
  I := Y;
  while I > 0 do
   begin
    if Odd(I) then Result := Result * X;
    I := I div 2;
    X := Sqr(X);
   end;
end;
Result и имя функции всегда имеют одинаковое значение. То есть:
function MyFunction: Integer;
begin
  MyFunction := 5;
  Result := Result * 2;
  MyFunction := Result + 1;
end;

возвращает значение 11. Но переменная Result и имя функции не полностью взаимозаменяемы. Когда имя функции оказывается в левой части инструкции присваивания, компилятор предполагает, что его необходимо использовать как переменную Result для извлечения возвращаемого значения. Когда имя функции оказывается в любом другом месте в теле функции – компилятор интерпретирует его как рекурсивный вызов. Переменная Result, может быть использована как переменная в инструкциях, преобразованиях типов, конструкторах множеств, индексах и вызовах других подпрограмм.

Если при вызове функции, ее выполнение прерывается до того момента, когда переменной Result или имени функции будет присвоено какое-либо значение, функция вернет неопределенное значение.


Конвенции вызова

Когда вы объявляете процедуру или функцию, применив директивы register, pascal, cdecl, stdcall и safecall, вы можете указать конвенцию вызова. Например:

function MyFunction(X, Y: Real): Real; cdecl;

Конвенция вызова определяет порядок передачи параметров в подпрограмму. Они так же влияют на удаление параметров из стека, использование регистров для передачи параметров и обработку ошибок и исключительных ситуаций. Конвенция вызова по умолчанию - register.

Конвенции register и pascal передают параметры слева направо, то есть первый параметр слева вычисляется и передается в первую очередь, а первый параметр справа – вычисляется и передается последним. Конвенции cdecl, stdcall и safecall передают параметры справа налево.

Для всех конвенций, кроме cdecl, процедура или функция удаляет параметры из стека после выхода. Конвенция cdecl подразумевает, что удаление параметров из стека выполняет вызывающая программа.

Конвенция register при передаче параметров использует до трех регистров ЦПУ, в то время как остальные конвенции передают параметры через стек.

Конвенция реализует исключительную ситуацию 'firewalls'. В Win32, это означает межпроцессное уведомление об ошибке COM. Далее в таблицу сведены сведения по конвенциям. Конвенции вызова:

ДирективаОбработка параметровОчистка стекаПередача параметров через регистры
registerСлева-направоПодпрограммаДа
pascalСлева-направоПодпрограммаНет
cdeclСправа-налевоВызывающая программаНет
stdcallСправа-налевоПодпрограммаНет
safecallСправа-налевоПодпрограммаНет

Установленная по умолчанию конвенция register является самой эффективной, поскольку она обычно позволяет избегать создания стекового кадра. (Методы доступа для открытых (published) свойств дожны использовать конвенцию register.) Конвенция cdecl полезна, когда вы вызываете функции из библиотек, написанных на C или C++, поскольку stdcall и safecall вообще рекомендованы для вызова внешнего кода. В Win32, API использует stdcall и safecall. Другие операционные системы обычно используют cdecl. (Напоминаем, что stdcall более эффективна, чем cdecl.)

Конвенция safecall должна быть использована при объявлении методов с двойным интерфейсом. Конвенция pascal поддерживается для обратной совместимости.

Директивы near, far и export относятся к конвенциям вызова Win16. Они не действуют в Win32 и поддерживаются только для обратной совместимости.


Объявления Forward и Interface

Директива forward в объявлении процедуры или функции замещает блок, включающий объявления локальных переменных и инструкции. Например:

function Calculate(X, Y: Integer): Real; forward;

объявляет функцию Calculate. Где-то после отложенного объявления forward подпрограмма должна быть объявлена повторно с объявлениями, которые содержит блок. Повторное объявление Calculate может выглядеть, например так:

function Calculate;
  ... { declarations }
begin
  ... { statement block }
end;

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

Отложенное объявление и соответствующее повторное объявление должны быть указаны в одной секции объявления типов. То есть вы не можете добавить новую секцию (например секцию var или секцию const) между отложенным объявлением и повторным объявлением. Повторное объявление может быть приведено с директивами external или assembler, но оно не может быть повторно помечено директивой forward.

Задачей отложенного объявления является расширение области видимости идентификатора процедуры или функции до некоторой предшествующей точки в программном коде. Это позволяет процедурам и функциям вызывать подпрограммы с отложенным объявлением до того, как они фактически объявлены. Кроме того, они обеспечивают более гибкую организацию кода и иногда необходимы для обеспечения обоюдной рекурсии.

Директива forward в модулях не действует в секции interface. Заголовки процедур и функций в этой секции действуют аналогично отложенному объявлениям и должны иметь повторные объявления в секции implementation. Подпрограмма, объявленная в секции interface, доступна в любой точке модуля и во всех точках остальных модулей и программ, которые подключающих модуль, в котором она объявлена.


Объявления External

Директива external, которая заменяет блок инструкций в объявлении процедуры или функции, позволяет вам вызывать подпрограммы, которые скомпилированы отдельно от вашей программы. Внешние подпрограммы могут загружаться из объектных файлов или динамически загружаемых библиотек.

При импорте функции, написанной на С, которая принимает переменное количество параметров, следует использовать директиву varargs. Например:

function printf(Format: PChar): Integer; cdecl; varargs;

Директива varargs действует только с внешними подпрограммами и только при указании конвенции вызова cdecl.


Линковка объектных файлов

Для вызова подпрограмм из скомпилированных объектных файлов, прежде всего следует связать объектный файл с вашим приложением, используя директиву компилятора $L (or $LINK). Например, директива:

{$L BLOCK.OBJ}

линкует BLOCK.OBJ в программу или модуль, в котором она указана. Далее следует объявить функции и процедуры, которые вы хотите вызвать:

procedure MoveWord(var Source, Dest; Count: Integer); 
external;
procedure FillWord(var Dest; Data: Integer; Count: Integer); 
external;
Теперь вы можете вызывать подпрограммы MoveWord и FillWord, находящиеся в BLOCK.OBJ.

В платформе Win32 объявления, указанные выше часто используют для доступа к внешним подпрограммам, написанным на ассемблере. Вы можете размещать подпрограммы, написанные на ассемблере, прямо в программном коде Delphi.


Импорт функций из библиотек

Чтобы импортировать подпрограммы из динамически загружаемых библиотек (.DLL), укажите директиву

external stringConstant;

в конце обычного заголовка процедуры или функции (stringConstant – это имя файла библиотеки в одинарных кавычках). Например, в Win32

function SomeFunction(S: string): string; 
external 'strlib.dll';
импортирует функцию с именем SomeFunction из strlib.dll.

Вы можете импортировать подпрограмму под именем, отличным от того, которое она имеет в библиотеке. Для того чтобы сделать это, укажите ее настоящее имя в директиве external:

external stringConstant1 name stringConstant2;

stringConstant1 определяет имя файла библиотеки и stringConstant2 – оригинальное название подпрограммы. Следующее объявление импортирует функцию из user32.dll (часть Win32 API):

function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer;
stdcall; external 'user32.dll' name 'MessageBoxA';

Оригинальное название функции - MessageBoxA, но она импортируется под именем MessageBox.

Вместо имени для идентификации подпрограммы, которую вы хотите импортировать, вы можете пользоваться номером:

external stringConstant index integerConstant;

где integerConstant – это индекс подпрограммы в таблице экспорта.

При импорте подпрограмм убедитесь, что написание и регистр в имени функции совпадает с вашим объявлением При вызове импортированных подпрограмм регистр не имеет значения.

Перегрузка процедур и функций

При перегрузке подпрограмм, в одной и той же области видимости вы можете объявить более чем одну процедуру с одинаковым именем.

Перегружаемые подпрограммы должны быть объявлены с директивой 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, компилятор по списку переданных подпрограмме параметров определяет, какую функцию нужно подключить. Например Divide(6.0, 3.0) вызывает первую функцию Divide, поскольку ее аргументами являются вещественными числами.

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

procedure Store(X: Longint); overload;
procedure Store(X: Shortint); overload;

В таких случаях, когда возможно избежать неопределенности, компилятор подключает подпрограмму, параметры которой имеют наименьший диапазон, удовлетворяющий действительным параметрам вызова. (Следует помнить, что вещественные константы всегда имеют тип Extended.)

Перегруженные подпрограммы должны отличаться по количеству принимаемых параметров или по их типу. Таким образом, следующая пара объявлений вызовет ошибку компиляции:

function Cap(S: string): string; overload;
  ...
procedure Cap(var Str: string); overload;
  ...
При этом следующая пара объявлений является верной:
function Func(X: Real; Y: Integer): Real; overload;
  ...
function Func(X: Integer; Y: Real): Real; overload;
  ...

При объявлении перегружаемых подпрограмм с директивой forward или в секции interface, отложенное объявление должно полностью повторять список параметров.

Компилятор способен отличать функции, принимающие в качестве параметров значения типов AnsiString/PAnsiChar, UnicodeString/PChar и WideString/PWideChar на одних и тех же позициях. Строковые или символьные константы, передаваемые в перегружаемые функции, транслируются в первичный строковый или символьный тип, то етсь в UnicodeString/PChar.

procedure test(const A: AnsiString); overload;
procedure test(const W: WideString); overload;
procedure test(const U: UnicodeString); overload;
procedure test(const PW: PWideChar); overload;
var
  a: AnsiString;
  b: WideString;
  c: UnicodeString;
  d: PWideChar;
  e: string;
begin
  a := 'a';
  b := 'b';
  c := 'c';
  d := 'd';
  e := 'e';
  test(a);    // calls AnsiString version
  test(b);    // calls WideString version
  test(c);    // calls UnicodeString version
  test(d);    // calls PWideChar version
  test(e);    // calls UnicodeString version
  test('abc');    // calls UnicodeString version
  test(AnsiString ('abc'));    // calls AnsiString version
  test(WideString('abc'));    // calls WideString version
  test(PWideChar('PWideChar'));    
  // calls PWideChar version
end;

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

Это может стать причиной некоторых побочных эффектов в случае с вещественными типами. Числа с плавающей точкой сравниваются по размеру. Если для вещественной переменной, переданнай в качестве параметра перегружаемой подпрограмме, не находится точного совпадения, но присутствует параметр типа variant, он принимается за вещественный тип с меньшим диапазоном. Например:

procedure foo(i: integer); overload;
procedure foo(d: double); overload;
procedure foo(v: variant); overload;
var
  v: variant;
begin
  foo(1);       // версия с integer
  foo(v);       // версия с variant
  foo(1.2);     // версия с variant 
//(вещественная константа -> тип extended)
end;

Этот пример вызывает версии процедуры foo с параметром variant, а не double, поскольку константа 1.2 явно определяется как тип extended, который не является точным совпадением для типа double. Extended так же не является точным совпадением для variant, но variant рассматривается как более обобщенный тип (в то время как double является типом с диапазоном, меньшим чем у extended).

foo(Double(1.2));

Такой вызов с преобразованием типа не будет работать. Вместо этого следует использовать типизированные константы:

const  d: double = 1.2;
  begin
    foo(d);
  end;

Приведенный выше код работает корректно и вызывает версию foo с параметром double. Следующий пример также вызывает версию foo с параметром double. Single лучше совпадает по типу с double, чем с variant.

const  s: single = 1.2;
  begin
    foo(s);
  end;

При объявлении нескольких перегружаемых подпрограмм, лучшим способом избежать обработки вещественного типа как variant является объявление отдельной версии подпрограммы для каждого вещественного типа (Single, Double, Extended) помимо версии с типом variant.

Если вы используете параметры по умолчанию в перегружаемых процедурах, избегайте объявлять неопределенные параметры.

Вы можете избежать возможных побочных эффектов при перегрузке, специфицируя имя подпрограммы при вызове. Например, Unit1.MyProcedure(X, Y) может вызвать только подпрограммы, объявленные в Unit1; если в модуле Unit1 не окажется подпрограмм, список параметров которых совпадет с действительными параметрами, возникнет ошибка.

Локальные объявления

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

Вложенные подпрограммы

Функции и процедуры внутри своих блоков локальных объявлений иногда могут содержать другие функции и процедуры. Например, следующее объявление процедуры с именем DoSomething содержит объявление вложенной процедуры.

procedure DoSomething(S: string);
var
  X, Y: Integer;

  procedure NestedProc(S: string);
  begin
  ...
  end;

begin
  ...
  NestedProc(S);
  ...
end;

Область видимости вложенной процедуры ограничена процедурой или функцией, в которой она объявлена. В нашем примере NestedProc может быть вызвана только внутри DoSomething.

Для реальных примеров вложенных подпрограмм обратитесь к процедуре DateTimeToString, функции ScanDate и прочим подпрограммам, объявленным в модуле SysUtils.

Комментариев нет:

Отправить комментарий