Parameters (Delphi)

From RAD Studio
Jump to: navigation, search

Go Up to Procedures and Functions Index


This topic covers the following items:

  • Parameter semantics
  • String parameters
  • Array parameters
  • Default parameters

About Parameters

Most procedure and function headers include a parameter list. For example, in the header:

function Power(X: Real; Y: Integer): Real;

the parameter list is (X: Real; Y: Integer).

A parameter list is a sequence of parameter declarations separated by semicolons and enclosed in parentheses. Each declaration is a comma-delimited series of parameter names, followed in most cases by a colon and a type identifier, and in some cases by the = symbol and a default value. Parameter names must be valid identifiers. Any declaration can be preceded by var, const, or out. Examples:

(X, Y: Real)
(var S: string; X: Integer)
(HWnd: Integer; Text, Caption: PChar; Flags: Integer)
(const P; I: Integer)

The parameter list specifies the number, order, and type of parameters that must be passed to the routine when it is called. If a routine does not take any parameters, omit the identifier list and the parentheses in its declaration:

procedure UpdateRecords;
begin
  ...
end;

Within the procedure or function body, the parameter names (X and Y in the first example) can be used as local variables. Do not redeclare the parameter names in the local declarations section of the procedure or function body.

Parameter Semantics

Parameters are categorized in several ways:

  • Every parameter is classified as value, variable, constant, or out. Value parameters are the default; the reserved words var, const, and out indicate variable, constant, and out parameters, respectively.
  • Value parameters are always typed, while constant, variable, and out parameters can be either typed or untyped.
  • Special rules apply to array parameters.

Files and instances of structured types that contain files can be passed only as variable (var) parameters.

Value and Variable Parameters

Most parameters are either value parameters (the default) or variable (var) parameters. Value parameters are passed by value, while variable parameters are passed by reference. To see what this means, consider the following functions:

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;

These functions return the same result, but only the second one - DoubleByRef can change the value of a variable passed to it. Suppose we call the functions like this:

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;

After this code executes, the variable I, which was passed to DoubleByValue, has the same value we initially assigned to it. But the variable V, which was passed to DoubleByRef, has a different value.

A value parameter acts like a local variable that gets initialized to the value passed in the procedure or function call. If you pass a variable as a value parameter, the procedure or function creates a copy of it; changes made to the copy have no effect on the original variable and are lost when program execution returns to the caller.

A variable parameter, on the other hand, acts like a pointer rather than a copy. Changes made to the parameter within the body of a function or procedure persist after program execution returns to the caller and the parameter name itself has gone out of scope.

Even if the same variable is passed in two or more var parameters, no copies are made. This is illustrated in the following example:

procedure AddOne(var X, Y: Integer);
begin
  X := X + 1;
  Y := Y + 1;
end;

var I: Integer;
begin
  I := 1;
  AddOne(I, I);
end;

After this code executes, the value of I is 3.

If a routine's declaration specifies a var parameter, you must pass an assignable expression - that is, a variable, typed constant (in the {$J+} state), dereferenced pointer, field, or indexed variable to the routine when you call it. To use our previous examples, DoubleByRef(7) produces an error, although DoubleByValue(7) is legal.

Indexes and pointer dereferences passed in var parameters - for example, DoubleByRef(MyArray[I]) - are evaluated once, before execution of the routine.

Constant Parameters

A constant (const) parameter is like a local constant or read-only variable. Constant parameters are similar to value parameters, except that you cannot assign a value to a constant parameter within the body of a procedure or function, nor can you pass one as a var parameter to another routine. (But when you pass an object reference as a constant parameter, you can still modify the object's properties.)

Using const allows the compiler to optimize code for structured - and string-type parameters. It also provides a safeguard against unintentionally passing a parameter by reference to another routine.

Here, for example, is the header for the CompareStr function in the SysUtils unit:

function CompareStr(const S1, S2: string): Integer;

Because S1 and S2 are not modified in the body of CompareStr, they can be declared as constant parameters.

Constant parameters may be passed to the function by value or by reference, depending on the specific compiler used. To force the compiler to pass a constant parameter by reference, you can use the [Ref] decorator with the const keyword.

The following example shows how you can specify the [Ref] decorator either before or after the const keyword:

function FunctionName(const [Ref] parameter1: Class1Name; [Ref] const parameter2: Class2Name);

Out Parameters

An out parameter, like a variable parameter, is passed by reference. However, with an out parameter, the initial value of the referenced variable for managed types, such as strings or arrays, is discarded by the caller before it is passed to the routine. Meanwhile, the initial value for unmanaged types, such as integers or pointers, is ignored. Therefore, for unmanaged types, it is not guaranteed that the initial value of the referenced variable will be overwritten in the routine to which it is passed.

Note: An out parameter does not get cleaned up by the function taking the out parameter. Any cleanup for an out parameter occurs in the caller and not the callee.

The out parameter is for output only; that is, it tells the function or procedure where to store output but does not provide any input.

For example, consider the procedure heading:

procedure GetInfo(out Info: SomeRecordType);

When you call GetInfo, you must pass it a variable of type SomeRecordType:

var MyRecord: SomeRecordType;
   ...
GetInfo(MyRecord);

But you're not using MyRecord to pass any data to the GetInfo procedure; MyRecord is just a container where you want GetInfo to store the information it generates. The call to GetInfo immediately frees the memory used by MyRecord, before program control passes to the procedure.

Out parameters are frequently used with distributed-object models like COM. In addition, you should use out parameters when you pass an uninitialized variable to a function or procedure.

Untyped Parameters

You can omit type specifications when declaring var, const, and out parameters. (Value parameters must be typed.) For example:

procedure TakeAnything(const C);

declares a procedure called TakeAnything that accepts a parameter of any type. When you call such a routine, you cannot pass it a numeral or untyped numeric constant.

Within a procedure or function body, untyped parameters are incompatible with every type. To operate on an untyped parameter, you must cast it. In general, the compiler cannot verify that operations on untyped parameters are valid.

The following example uses untyped parameters in a function called Equal that compares a specified number of bytes of any two variables:

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;

Given the declarations:

type
  TVector = array[1..10] of Integer;
  TPoint = record
    X, Y: Integer;  // Integer occupies 4 bytes. Therefore 8 bytes in a whole
  end;
var
  Vec1, Vec2: TVector;
  N: Integer;
  P: TPoint;

you could make the following calls to 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, 8);                    // compare Vec1[1] to P.X and Vec1[2] to P.Y
                                         // each Vec1[x] is integer and occupies 4 bytes
Note: FreeAndNil does not accepts untyped var parameters.

For example, to cast TObject Obj should be typed:

procedure FreeAndNil(var Obj: TObject);

String Parameters

When you declare routines that take short-string parameters, you cannot include length specifiers in the parameter declarations. That is, the following declaration causes a compilation error:

procedure Check(S: string[20]);   // syntax error

But the following declaration is valid:

type TString20 = string[20];
procedure Check(S: TString20);

The special identifier OpenString can be used to declare routines that take short-string parameters of varying length:

procedure Check(S: OpenString);

When the {$H} and {$P+} compiler directives are both in effect, the reserved word string is equivalent to OpenString in parameter declarations.

Short strings, OpenString, $H, and $P are supported for backward compatibility only. In new code, you can avoid these considerations by using long strings.

Array Parameters

When you declare routines that take array parameters, you cannot include index type specifiers in the parameter declarations. That is, the declaration:

procedure Sort(A: array[1..10] of Integer)  // syntax error

causes a compilation error. But:

type TDigits = array[1..10] of Integer;
procedure Sort(A: TDigits);

is valid. Another approach is to use open array parameters.

Since the Delphi language does not implement value semantics for dynamic arrays, 'value' parameters in routines do not represent a full copy of the dynamic array. In this example:

type
  TDynamicArray = array of Integer;
  procedure p(Value: TDynamicArray);
    begin
      Value[0] := 1;
    end;

  procedure Run;
    var
      a: TDynamicArray;
    begin
      SetLength(a, 1);
      a[0] := 0;
      p(a);
      Writeln(a[0]); // Prints '1'
    end;

Note that the assignment to Value[0] in routine p will modify the content of dynamic array of the caller, despite Value being a by-value parameter. If a full copy of the dynamic array is required, use the Copy standard procedure to create a value copy of the dynamic array.

Open Array Parameters

Open array parameters allow arrays of different sizes to be passed to the same procedure or function. To define a routine with an open array parameter, use the syntax array of type (rather than array[X..Y] of type) in the parameter declaration. For example:

function Find(A: array of Char): Integer;

declares a function called Find that takes a character array of any size and returns an integer.

Note: The syntax of open array parameters resembles that of dynamic array types, but they do not mean the same thing. The previous example creates a function that takes any array of Char elements, including (but not limited to) dynamic arrays. To declare parameters that must be dynamic arrays, you need to specify a type identifier:

type TDynamicCharArray = array of Char;
function Find(A: TDynamicCharArray): Integer;

Within the body of a routine, open array parameters are governed by the following rules:

  • They are always zero-based. The first element is 0, the second element is 1, and so forth. The standard Low and High functions return 0 and Length - 1, respectively. The SizeOf function returns the size of the actual array passed to the routine.
  • They can be accessed by element only. Assignments to an entire open array parameter are not allowed.
  • They can be passed to other procedures and functions only as open array parameters or untyped var parameters. They cannot be passed to SetLength.
  • Instead of an array, you can pass a variable of the open array parameter's base type. It will be treated as an array of length 1.

When you pass an array as an open array value parameter, the compiler creates a local copy of the array within the routine's stack frame. Be careful not to overflow the stack by passing large arrays.

The following examples use open array parameters to define a Clear procedure that assigns zero to each element in an array of reals and a Sum function that computes the sum of the elements in an array of reals:

procedure Clear(var A: array of Real);
var
   I: Integer;
begin
   for I := 0 to High(A) do A[I] := 0;
end;

function Sum(const A: array of Real): Real;
var
  I: Integer;
  S: Real;
begin
  S := 0;
  for I := 0 to High(A) do S := S + A[I];
  Sum := S;
end;

When you call routines that use open array parameters, you can pass open array constructors to them.

Variant Open Array Parameters

Variant open array parameters allow you to pass an array of differently typed expressions to a single procedure or function. To define a routine with a variant open array parameter, specify array of const as the parameter's type. Thus:

procedure DoSomething(A: array of const);

declares a procedure called DoSomething that can operate on heterogeneous arrays.

The array of const construction is equivalent to array of TVarRec. System.TVarRec, which is declared in the System unit, represents a record with a variant part that can hold values of integer, Boolean, character, real, string, pointer, class, class reference, interface, and variant types. TVarRec's VType field indicates the type of each element in the array. Some types are passed as pointers rather than values; in particular, strings are passed as Pointer and must be typecast to string.

The following Win32 example, uses a variant open array parameter in a function that creates a string representation of each element passed to it and concatenates the results into a single string. The string-handling routines called in this function are defined in SysUtils:

function MakeStr(const Args: array of const): string;
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 + BoolToStr(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;
          vtWideChar: Result := Result + VWideChar;
          vtPWideChar: Result := Result + VPWideChar;
          vtAnsiString:  Result := Result + string(AnsiString(VAnsiString));
          vtUnicodeString:  Result := Result + string(UnicodeString(VUnicodeString));
          vtCurrency:    Result := Result + CurrToStr(VCurrency^);
          vtVariant:     Result := Result + string(VVariant^);
          vtWideString:  Result := Result + string(WideString(VWideString));
          vtInt64:       Result := Result + IntToStr(VInt64^);
  end;
end;

We can call this function using an open array constructor. For example:

MakeStr(['test', 100, ' ', True, 3.14159, TForm])

returns the string 'test100 T3.14159TForm'.

Default Parameters

You can specify default parameter values in a procedure or function heading. Default values are allowed only for typed const and value parameters. To provide a default value, end the parameter declaration with the = symbol followed by a constant expression that is assignment-compatible with the parameter's type.

For example, given the declaration:

procedure FillArray(A: array of Integer; Value: Integer = 0);

the following procedure calls are equivalent.

FillArray(MyArray);
FillArray(MyArray, 0);

A multiple-parameter declaration cannot specify a default value. Thus, while the following declaration is legal:

function MyFunction(X: Real = 3.5; Y: Real = 3.5): Real;

The following declaration is not legal:

function MyFunction(X, Y: Real = 3.5): Real;  // syntax error

Parameters with default values must occur at the end of the parameter list. That is, all parameters following the first declared default value must also have default values. So the following declaration is illegal:

procedure MyProcedure(I: Integer = 1; S: string);  // syntax error

Default values specified in a procedural type override those specified in an actual routine. Thus, given the declarations:

type TResizer = function(X: Real; Y: Real = 1.0): Real;
function Resizer(X: Real; Y: Real = 2.0): Real;
var
  F: TResizer;
  N: Real;

the statements:

F := Resizer;
F(N);

result in the values (N, 1.0) being passed to Resizer.

Default parameters are limited to values that can be specified by a constant expression. Hence parameters of a dynamic-array, procedural, class, class-reference, or interface type can have no value other than nil as their default. Parameters of a record, variant, file, static-array, or object type cannot have default values at all.

Default Parameters and Overloaded Functions

If you use default parameter values in an overloaded routine, avoid ambiguous parameter signatures. Consider, for example, the following:

procedure Confused(I: Integer); overload;
   ...
procedure Confused(I: Integer; J: Integer = 0); overload;
   ...
Confused(X);   //  Which procedure is called?

In fact, neither procedure is called. This code generates a compilation error.

Default Parameters in Forward and Interface Declarations

If a routine has a forward declaration or appears in the interface section of a unit, default parameter values if there are any must be specified in the forward or interface declaration. In this case, the default values can be omitted from the defining (implementation) declaration; but if the defining declaration includes default values, they must match those in the forward or interface declaration exactly.

See Also