RAD Studio (Common)
|
This topic describes the syntax of Delphi declarations and statements.
Aside from the uses clause (and reserved words like implementation that demarcate parts of a unit), a program consists entirely of declarations and statements, which are organized into blocks.
This topic covers the following items:
The names of variables, constants, types, fields, properties, procedures, functions, programs, units, libraries, and packages are called identifiers. (Numeric constants like 26057 are not identifiers.) Identifiers must be declared before you can use them; the only exceptions are a few predefined types, routines, and constants that the compiler understands automatically, the variable Result when it occurs inside a function block, and the variable Self when it occurs inside a method implementation.
A declaration defines an identifier and, where appropriate, allocates memory for it. For example,
var Size: Extended;
declares a variable called Size that holds an Extended (real) value, while
function DoThis(X, Y: string): Integer;
declares a function called DoThis that takes two strings as arguments and returns an integer. Each declaration ends with a semicolon. When you declare several variables, constants, types, or labels at the same time, you need only write the appropriate reserved word once:
var Size: Extended; Quantity: Integer;Description:string;
The syntax and placement of a declaration depend on the kind of identifier you are defining. In general, declarations can occur only at the beginning of a block or at the beginning of the interface or implementation section of a unit (after the uses clause). Specific conventions for declaring variables, constants, types, functions, and so forth are explained in the documentation for those topics.
The 'hint' directives platform, deprecated, and library may be appended to any declaration. These directives will produce warnings at compile time. Hint directives can be applied to type declarations, variable declarations, class, interface and structure declarations, field declarations within classes or records, procedure, function and method declarations, and unit declarations.
When a hint directive appears in a unit declaration, it means that the hint applies to everything in the unit. For example, the Windows 3.1 style OleAuto.pas unit on Windows is completely deprecated. Any reference to that unit or any symbol in that unit will produce a deprecation message.
The platform hinting directive on a symbol or unit indicates that it may not exist or that the implementation may vary considerably on different platforms. The library hinting directive on a symbol or unit indicates that the code may not exist or the implementation may vary considerably on different library architectures.
The platform and library directives do not specify which platform or library. If your goal is writing platform-independent code, you do not need to know which platform a symbol is specific to; it is sufficient that the symbol be marked as specific to some platform to let you know it may cause problems for your goal of portability.
In the case of a procedure or function declaration, the hint directive should be separated from the rest of the declaration with a semicolon. Examples:
procedure SomeOldRoutine; stdcall deprecated; var VersionNumber: Real library; type AppError = class(Exception) ... end platform;
When source code is compiled in the {$HINTS ON} {$WARNINGS ON} state, each reference to an identifier declared with one of these directives generates an appropriate hint or warning. Use platform to mark items that are specific to a particular operating environment (such as Windows), deprecated to indicate that an item is obsolete or supported only for backward compatibility, and library to flag dependencies on a particular library or component framework.
The RAD Studio compiler also recognizes the hinting directive experimental. You can use this directive to designate units which are in an unstable, development state. The compiler will emit a warning when it builds an application that uses the unit.
Statements define algorithmic actions within a program. Simple statements like assignments and procedure calls can combine to form loops, conditional statements, and other structured statements.
Multiple statements within a block, and in the initialization or finalization section of a unit, are separated by semicolons.
A simple statement doesn't contain any other statements. Simple statements include assignments, calls to procedures and functions, and goto jumps.
An assignment statement has the form
variable := expression
where variable is any variable reference, including a variable, variable typecast, dereferenced pointer, or component of a structured variable. The expression is any assignment-compatible expression (within a function block, variable can be replaced with the name of the function being defined. See Procedures and functions). The := symbol is sometimes called the assignment operator.
An assignment statement replaces the current value of variable with the value of expression. For example,
I := 3;
assigns the value 3 to the variable I. The variable reference on the left side of the assignment can appear in the expression on the right. For example,
I := I + 1;
increments the value of I. Other assignment statements include
X := Y + Z;
Done := (I >= 1) and (I < 100);
Hue1 := [Blue, Succ(C)];
I := Sqr(J) - I * K;
Shortint(MyChar) := 122;
TByteRec(W).Hi := 0;
MyString[I] := 'A';
SomeArray[I + 1] := P^;
TMyObject.SomeProperty := True;
A procedure call consists of the name of a procedure (with or without qualifiers), followed by a parameter list (if required). Examples include
PrintHeading; Transpose(A, N, M); Find(Smith, William); Writeln('Hello world!'); DoSomething(); Unit1.SomeProcedure; TMyObject.SomeMethod(X,Y);
With extended syntax enabled ({$X+}), function calls, like calls to procedures, can be treated as statements in their own right:
MyFunction(X);
When you use a function call in this way, its return value is discarded.
For more information about procedures and functions, see Procedures and functions.
A goto statement, which has the form
goto label
transfers program execution to the statement marked by the specified label. To mark a statement, you must first declare the label. Then precede the statement you want to mark with the label and a colon:
label: statement
Declare labels like this:
labellabel;
You can declare several labels at once:
labellabel1, ..., labeln;
A label can be any valid identifier or any numeral between 0 and 9999.
The label declaration, marked statement, and goto statement must belong to the same block. (See Blocks and Scope, below.) Hence it is not possible to jump into or out of a procedure or function. Do not mark more than one statement in a block with the same label.
For example,
label StartHere; ... StartHere: Beep; goto StartHere;
creates an infinite loop that calls the Beep procedure repeatedly.
Additionally, it is not possible to jump into or out of a try-finally or try-except statement.
The goto statement is generally discouraged in structured programming. It is, however, sometimes used as a way of exiting from nested loops, as in the following example.
procedure FindFirstAnswer; var X, Y, Z, Count: Integer; label FoundAnAnswer; begin Count := SomeConstant; for X := 1 to Count do for Y := 1 to Count do for Z := 1 to Count do if ... { some condition holds on X, Y, and Z } then goto FoundAnAnswer; ... { Code to execute if no answer is found } Exit; FoundAnAnswer: ... { Code to execute when an answer is found } end;
Notice that we are using goto to jump out of a nested loop. Never jump into a loop or other structured statement, since this can have unpredictable effects.
Structured statements are built from other statements. Use a structured statement when you want to execute other statements sequentially, conditionally, or repeatedly.
A compound statement is a sequence of other (simple or structured) statements to be executed in the order in which they are written. The compound statement is bracketed by the reserved words begin and end, and its constituent statements are separated by semicolons. For example:
begin Z := X; X := Y; X := Y; end;
The last semicolon before end is optional. So we could have written this as
begin Z := X; X := Y; Y := Z end;
Compound statements are essential in contexts where Delphi syntax requires a single statement. In addition to program, function, and procedure blocks, they occur within other structured statements, such as conditionals or loops. For example:
begin I := SomeConstant; while I > 0 do begin ... I := I - 1; end; end;
You can write a compound statement that contains only a single constituent statement; like parentheses in a complex term, begin and end sometimes serve to disambiguate and to improve readability. You can also use an empty compound statement to create a block that does nothing:
begin end;
A with statement is a shorthand for referencing the fields of a record or the fields, properties, and methods of an object. The syntax of a with statement is
For example, given the declarations
type TDate = record Day: Integer; Month: Integer; Year: Integer; end; var OrderDate: TDate;
you could write the following with statement.
with OrderDate do if Month = 12 then begin Month := 1; Year := Year + 1; end else Month := Month + 1;
you could write the following with statement.
if OrderDate.Month = 12 then begin OrderDate.Month := 1; OrderDate.Year := OrderDate.Year + 1; end else OrderDate.Month := OrderDate.Month + 1;
If the interpretation of obj involves indexing arrays or dereferencing pointers, these actions are performed once, before statement is executed. This makes with statements efficient as well as concise. It also means that assignments to a variable within statement cannot affect the interpretation of obj during the current execution of the with statement.
Each variable reference or method name in a with statement is interpreted, if possible, as a member of the specified object or record. If there is another variable or method of the same name that you want to access from the with statement, you need to prepend it with a qualifier, as in the following example.
with OrderDate do begin Year := Unit1.Year; ... end;
When multiple objects or records appear after with, the entire statement is treated like a series of nested with statements. Thus
withobj1, obj2, ..., objndostatement
is equivalent to
with obj1 do with obj2 do ... with objn do // statement
In this case, each variable reference or method name in statement is interpreted, if possible, as a member of objn; otherwise it is interpreted, if possible, as a member of objn1; and so forth. The same rule applies to interpreting the objs themselves, so that, for instance, if objn is a member of both obj1 and obj2, it is interpreted as obj2.objn.
There are two forms of if statement: if...then and the if...then...else. The syntax of an if...then statement is
ifexpressionthenstatement
where expression returns a Boolean value. If expression is True, then statement is executed; otherwise it is not. For example,
if J <> 0 then Result := I / J;
The syntax of an if...then...else statement is
ifexpressionthenstatement1elsestatement2
where expression returns a Boolean value. If expression is True, then statement1 is executed; otherwise statement2 is executed. For example,
if J = 0 then Exit else Result := I / J;
The then and else clauses contain one statement each, but it can be a structured statement. For example,
if J <> o then begin Result := I / J; Count := Count + 1; end else if Count = Last then Done := True else Exit;
Notice that there is never a semicolon between the then clause and the word else. You can place a semicolon after an entire if statement to separate it from the next statement in its block, but the then and else clauses require nothing more than a space or carriage return between them. Placing a semicolon immediately before else (in an if statement) is a common programming error.
A special difficulty arises in connection with nested if statements. The problem arises because some if statements have else clauses while others do not, but the syntax for the two kinds of statement is otherwise the same. In a series of nested conditionals where there are fewer else clauses than if statements, it may not seem clear which else clauses are bound to which ifs. Consider a statement of the form
ifexpression1thenifexpression2thenstatement1elsestatement2;
There would appear to be two ways to parse this:
ifexpression1 then [ ifexpression2thenstatement1elsestatement2 ];
ifexpression1then [ ifexpression2thenstatement1 ] elsestatement2;
The compiler always parses in the first way. That is, in real code, the statement
if ... { expression1} then if ... {expression2} then ... {statement1} else ... {statement2}
is equivalent to
if ... {expression1} then begin if ... {expression2} then ... {statement1} else ... {statement2} end;
The rule is that nested conditionals are parsed starting from the innermost conditional, with each else bound to the nearest available if on its left. To force the compiler to read our example in the second way, you would have to write it explicitly as
if ... {expression1} then begin if ... {expression2} then ... {statement1} end end else ... {statement2};
The case statement may provide a readable alternative to deeply nested if conditionals. A case statement has the form
case selectorExpression of caseList1: statement1; ... caseListn: statementn; end
where selectorExpression is any expression of an ordinal type smaller than 32 bits (string types and ordinals larger than 32 bits are invalid) and each caseList is one of the following:
case selectorExpression of caseList1: statement1; ... caselistn: statementn; else statements; end
where statements is a semicolon-delimited sequence of statements. When a case statement is executed, at most one of statement1 ... statementn is executed. Whichever caseList has a value equal to that of selectorExpression determines the statement to be used. If none of the caseLists has the same value as selectorExpression, then the statements in the else clause (if there is one) are executed.
The case statement
case I of 1..5: Caption := 'Low'; 6..9: Caption := 'High'; 0, 10..99: Caption := 'Out of range'; else Caption := ''; end
is equivalent to the nested conditional
if I in [1..5] then Caption := 'Low'; else if I in [6..10] then Caption := 'High'; else if (I = 0) or (I in [10..99]) then Caption := 'Out of range' else Caption := '';
Other examples of case statements
case MyColor of Red: X := 1; Green: X := 2; Blue: X = 3; Yellow, Orange, Black: X := 0; end; case Selection of Done: Form1.Close; Compute: calculateTotal(UnitCost, Quantity); else Beep; end;
Loops allow you to execute a sequence of statements repeatedly, using a control condition or variable to determine when the execution stops. Delphi has three kinds of control loop: repeat statements, while statements, and for statements.
You can use the standard Break and Continue procedures to control the flow of a repeat, while, or for statement. Break terminates the statement in which it occurs, while Continue begins executing the next iteration of the sequence.
The syntax of a repeat statement is
repeatstatement1; ...; statementn;untilexpression
where expression returns a Boolean value. (The last semicolon before until is optional.) The repeat statement executes its sequence of constituent statements continually, testing expression after each iteration. When expression returns True, the repeat statement terminates. The sequence is always executed at least once because expression is not evaluated until after the first iteration.
Examples of repeat statements include
repeat K := I mod J; I := J; J := K; until J = 0; repeat Write('Enter a value (0..9): '); Readln(I); until (I >= 0) and (I <= 9);
A while statement is similar to a repeat statement, except that the control condition is evaluated before the first execution of the statement sequence. Hence, if the condition is false, the statement sequence is never executed.
The syntax of a while statement is
whileexpressiondostatement
where expression returns a Boolean value and statement can be a compound statement. The while statement executes its constituent statement repeatedly, testing expression before each iteration. As long as expression returns True, execution continues.
Examples of while statements include
while Data[I] <> X do I := I + 1; while I > 0 do begin if Odd(I) then Z := Z * X; I := I div 2; X := Sqr(X); end; while not Eof(InputFile) do begin Readln(InputFile, Line); Process(Line); end;
A for statement, unlike a repeat or while statement, requires you to specify explicitly the number of iterations you want the loop to go through. The syntax of a for statement is
for counter := initialValue to finalValue do statement
or
for counter := initialValue downto finalValue do statement
where
begin counter := initialValue; while counter <= finalValue do begin ... {statement}; counter := Succ(counter); end; end
The difference between this construction and the for...to statement is that the while loop reevaluates finalValue before each iteration. This can result in noticeably slower performance if finalValue is a complex expression, and it also means that changes to the value of finalValue within statement can affect execution of the loop.
Examples of for statements:
for I := 2 to 63 do if Data[I] > Max then Max := Data[I]; for I := ListBox1.Items.Count - 1 downto 0 do ListBox1.Items[I] := UpperCase(ListBox1.Items[I]); for I := 1 to 10 do for J := 1 to 10 do begin X := 0; for K := 1 to 10 do X := X + Mat1[I,K] * Mat2[K,J]; Mat[I,J] := X; end; for C := Red to Blue do Check(C);
Delphi for Win32 supports for-element-in-collection style iteration over containers. The following container iteration patterns are recognized by the compiler:
type TIntArray = array[0..9] of Integer; TGenericIntArray = array of Integer; var IArray1: array[0..9] of Integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IArray2: array[1..10] of Integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IArray3: array[1..2] of TIntArray = ((11, 12, 13, 14, 15, 16, 17, 18, 19, 20), (21, 22, 23, 24, 25, 26, 27, 28, 29, 30)); MultiDimTemp: TIntArray; DynArray: TGenericIntArray; I: Integer; begin for I in IArray1 do begin { Do something with I... } end; { Indexing begins at lower array bound of 1. } for I in IArray2 do begin { Do something with I... } end; { Iterating a multi-dimensional array } for MultiDimTemp in IArray3 do // Indexing from 1..2 for I in MultiDimTemp do // Indexing from 0..9 begin { Do something with I... } end; { Iterating over a dynamic array } DynArray := TGenericIntArray.Create(1, 2, 3, 4); for I in DynArray do begin { Do something with I... } end; end.
The following example demonstrates iteration over string expressions:
var C: Char; S1, S2: String; Counter: Integer; OS1, OS2: ShortString; AC: AnsiChar; begin S1 := 'Now is the time for all good men to come to the aid of their country.'; S2 := ''; for C in S1 do S2 := S2 + C; if S1 = S2 then WriteLn('SUCCESS #1') else WriteLn('FAIL #1'); OS1 := 'When in the course of human events it becomes necessary to dissolve...'; OS2 := ''; for AC in OS1 do OS2 := OS2 + AC; if OS1 = OS2 then WriteLn('SUCCESS #2'); else WriteLn('FAIL #2'); end.
The following example demonstrates iteration over set expressions:
type TMyThing = (one, two, three); TMySet = set of TMyThing; TCharSet = set of Char; var MySet: TMySet; MyThing: TMyThing; CharSet: TCharSet; {$IF DEFINED(CLR)} C: AnsiChar; {$ELSE} C: Char; {$IFEND} begin MySet := [one, two, three]; for MyThing in MySet do begin // Do something with MyThing... end; CharSet := [#0..#255]; for C in CharSet do begin // Do something with C... end; end.
To use the for-in loop construct on a class or interface, the class or interface must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes:
The following code demonstrates iterating over an enumerable container in Delphi.
type TMyIntArray = array of Integer; TMyEnumerator = class Values: TMyIntArray; Index: Integer; public constructor Create; function GetCurrent: Integer; function MoveNext: Boolean; property Current: Integer read GetCurrent; end; TMyContainer = class public function GetEnumerator: TMyEnumerator; end; constructor TMyEnumerator.Create; begin inherited Create; Values := TMyIntArray.Create(100, 200, 300); Index := -1; end; function TMyEnumerator.MoveNext: Boolean; begin if Index < High(Values) then begin Inc(Index); Result := True; end else Result := False; end; function TMyEnumerator.GetCurrent: Integer; begin Result := Values[Index]; end; function TMyContainer.GetEnumerator: TMyEnumerator; begin Result := TMyEnumerator.Create; end; var MyContainer: TMyContainer; I: Integer; Counter: Integer; begin MyContainer := TMyContainer.Create; Counter := 0; for I in MyContainer do Inc(Counter, I); WriteLn('Counter = ', Counter); end.
The following classes and their descendents support the for-in syntax:
Declarations and statements are organized into blocks, which define local namespaces (or scopes) for labels and identifiers. Blocks allow a single identifier, such as a variable name, to have different meanings in different parts of a program. Each block is part of the declaration of a program, function, or procedure; each program, function, or procedure declaration has one block.
A block consists of a series of declarations followed by a compound statement. All declarations must occur together at the beginning of the block. So the form of a block is
{declarations} begin {statements} end
The declarations section can include, in any order, declarations for variables, constants (including resource strings), types, procedures, functions, and labels. In a program block, the declarations section can also include one or more exports clauses (see Libraries and packages).
For example, in a function declaration like
function UpperCase(const S: string): string; var Ch: Char; L: Integer; Source, Dest: PChar; begin ... end;
the first line of the declaration is the function heading and all of the succeeding lines make up the block. Ch, L, Source, and Dest are local variables; their declarations apply only to the UpperCase function block and override, in this block only, any declarations of the same identifiers that may occur in the program block or in the interface or implementation section of a unit.
An identifier, such as a variable or function name, can be used only within the scope of its declaration. The location of a declaration determines its scope. An identifier declared within the declaration of a program, function, or procedure has a scope limited to the block in which it is declared. An identifier declared in the interface section of a unit has a scope that includes any other units or programs that use the unit where the declaration occurs. Identifiers with narrower scope, especially identifiers declared in functions and procedures, are sometimes called local, while identifiers with wider scope are called global.
The rules that determine identifier scope are summarized below.
If the identifier is declared in ... |
its scope extends ... |
the declaration section of a program, function, or procedure |
from the point where it is declared to the end of the current block, including all blocks enclosed within that scope. |
the interface section of a unit |
from the point where it is declared to the end of the unit, and to any other unit or program that uses that unit. (See Programs and Units.) |
the implementation section of a unit, but not within the block of any function or procedure |
from the point where it is declared to the end of the unit. The identifier is available to any function or procedure in the unit, including the initialization and finalization sections, if present. |
the definition of a record type (that is, the identifier is the name of a field in the record) |
from the point of its declaration to the end of the record-type definition. (See Records.) |
the definition of a class (that is, the identifier is the name of a data field property or method in the class) |
from the point of its declaration to the end of the class-type definition, and also includes descendants of the class and the blocks of all methods in the class and its descendants. (See Classes and Objects.) |
When one block encloses another, the former is called the outer block and the latter the inner block. If an identifier declared in an outer block is redeclared in an inner block, the inner declaration takes precedence over the outer one and determines the meaning of the identifier for the duration of the inner block. For example, if you declare a variable called MaxValue in the interface section of a unit, and then declare another variable with the same name in a function declaration within that unit, any unqualified occurrences of MaxValue in the function block are governed by the second, local declaration. Similarly, a function declared within another function creates a new, inner scope in which identifiers used by the outer function can be redeclared locally.
The use of multiple units further complicates the definition of scope. Each unit listed in a uses clause imposes a new scope that encloses the remaining units used and the program or unit containing the uses clause. The first unit in a uses clause represents the outermost scope and each succeeding unit represents a new scope inside the previous one. If two or more units declare the same identifier in their interface sections, an unqualified reference to the identifier selects the declaration in the innermost scope, that is, in the unit where the reference itself occurs, or, if that unit doesn't declare the identifier, in the last unit in the uses clause that does declare the identifier.
The System and SysInit units are used automatically by every program or unit. The declarations in System, along with the predefined types, routines, and constants that the compiler understands automatically, always have the outermost scope.
You can override these rules of scope and bypass an inner declaration by using a qualified identifier (see Qualified Identifiers) or a with statement (see With Statements, above).
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
|
What do you think about this topic? Send feedback!
|