A type is essentially a name for a kind of data. When you declare a variable you must specify its type, which determines the set of values the variable can hold and the operations that can be performed on it. Every expression returns data of a particular type, as does every function. Most functions and procedures require parameters of specific types.
Object Pascal is a "strongly typed" language, which means that it distinguishes a variety of data types and does not always allow you to substitute one type for another. This is usually beneficial because it lets the compiler treat data intelligently and validate your code more thoroughly, preventing hard-to-diagnose runtime errors. When you need greater flexibility, however, there are mechanisms to circumvent strong typing. These include typecasting (see "Typecasts"), pointers (see "Pointers and pointer types"), variants (see "Variant types"), variant parts in records (see "Variant parts in records"), and absolute addressing of variables (see "Absolute addresses").
There are several ways to categorize Object Pascal data types:
The outline below shows the taxonomy of Object Pascal data types.
simple
ordinal
integer
character
Boolean
enumerated
subrange
real
string
structured
set
array
record
file
class
class reference
interface
pointer
procedural
variant
type identifier
The standard function SizeOf operates on all variables and type identifiers. It returns an integer representing the amount of memory (in bytes) used to store data of the specified type. For example, SizeOf(Longint) returns 4, since a Longint variable uses four bytes of memory.
Type declarations are illustrated in the sections that follow. For general information about type declarations, see "Declaring types".
Simple types, which include ordinal types and real types, define ordered sets of values.
Ordinal types include integer, character, Boolean, enumerated, and subrange types. An ordinal type defines an ordered set of values in which each value except the first has a unique predecessor and each value except the last has a unique successor. Further, each value has an ordinality, which determines the ordering of the type. For integer types, the ordinality of a value is the value itself; for all other ordinal types except subranges, the first value has ordinality 0, the next value has ordinality 1, and so forth. If a value has ordinality n, its predecessor has ordinality n - 1 and its successor has ordinality n + 1.
Several predefined functions operate on ordinal values and type identifiers. The most important of them are summarized below.
For example, High(Byte) returns 255 because the highest value of type Byte is 255, and Succ(2) returns 3 because 3 is the successor of 2.
The standard procedures Inc and Dec increment and decrement the value of an ordinal variable. For example, Inc(I) is equivalent to I := Succ(I) and, if I is an integer variable, to I := I + 1.
An integer type represents a subset of the whole numbers. The generic integer types are Integer and Cardinal; use these whenever possible, since they result in the best performance for the underlying CPU and operating system. The table below gives their ranges and storage formats for the current 32-bit Object Pascal compiler.
Fundamental integer types include Shortint, Smallint, Longint, Int64, Byte, Word, and Longword.
In general, arithmetic operations on integers return a value of type Integer--which, in its current implementation, is equivalent to the 32-bit Longint. Operations return a value of type Int64 only when performed on an Int64 operand. Hence the following code produces incorrect results.
var I: Integer; J: Int64; ... I := High(Integer); J := I + 1;
To get an Int64 return value in this situation, cast I as Int64:
... J := Int64(I) + 1;
For more information, see "Arithmetic operators".
Note: Most standard routines that take integer arguments truncate Int64 values to 32 bits. However, the High, Low, Succ, Pred, Inc, Dec, IntToStr, and IntToHex routines fully support Int64 arguments. Also, the Round, Trunc, StrToInt64, and StrToInt64Def functions return Int64 values. A few routines--including Ord--cannot take Int64 values at all.
When you increment the last value or decrement the first value of an integer type, the result wraps around the beginning or end of the range. For example, the Shortint type has the range -128..127; hence, after execution of the code
var I: Shortint; ... I := High(Shortint); I := I + 1;
the value of I is -128. If compiler range-checking is enabled, however, this code generates a runtime error.
The fundamental character types are AnsiChar and WideChar. AnsiChar values are byte-sized (8-bit) characters ordered according to the extended ANSI character set. WideChar values are word-sized (16-bit) characters ordered according to the Unicode character set. The first 256 Unicode characters correspond to the ANSI characters.
The generic character type is Char, which is equivalent to AnsiChar. Because the implementation of Char is subject to change, it's a good idea to use the standard function SizeOf rather than a hard-coded constant when writing programs that may need to handle characters of different sizes.
A string constant of length 1, such as 'A', can denote a character value. The predefined function Chr returns the character value for any integer in the range of AnsiChar or WideChar; for example, Chr(65) returns the letter A.
Character values, like integers, wrap around when decremented or incremented past the beginning or end of their range (unless range-checking is enabled). For example, after execution of the code
var
Letter: Char;
I: Integer;
begin
Letter := High(Letter);
for I := 1 to 66 do
Inc(Letter);
end;
Letter has the value A (ASCII 65).
For more information about Unicode characters, see "About extended character sets" and "Working with null-terminated strings".
The four predefined Boolean types are Boolean, ByteBool, WordBool, and LongBool. Boolean is the preferred type. The others exist to provide compatibility with different languages and the Windows environment.
A Boolean variable occupies one byte of memory, a ByteBool variable also occupies one byte, a WordBool variable occupies two bytes (one word), and a LongBool variable occupies four bytes (two words).
Boolean values are denoted by the predefined constants True and False. The following relationships hold.
A value of type ByteBool, LongBool, or WordBool is considered True when its ordinality is nonzero. If such a value appears in a context where a Boolean is expected, the compiler automatically converts any value of nonzero ordinality to True.
The remarks above refer to the ordinality of Boolean values--not to the values themselves. In Object Pascal, Boolean expressions cannot be equated with integers or reals. Hence, if X is an integer variable, the statement
if X then ...;
generates a compilation error. Casting the variable to a Boolean type is unreliable, but each of the following alternatives will work.
if X <> 0 then ...; { use longer expression that returns Boolean value }
var OK: Boolean { use Boolean variable }
...
if X <> 0 then OK := True;
if OK then ...;
An enumerated type defines an ordered set of values by simply listing identifiers that denote these values. The values have no inherent meaning, and their ordinality follows the sequence in which the identifiers are listed.
To declare an enumerated type, use the syntax
type typeName = (val1, ..., valn)
where typeName and each val are valid identifiers. For example, the declaration
type Suit = (Club, Diamond, Heart, Spade);
defines an enumerated type called Suit whose possible values are Club, Diamond, Heart, and Spade.
When you declare an enumerated type, you are declaring each val to be a constant of type typeName. If the val identifiers are used for another purpose within the same scope, naming conflicts occur. For example, suppose you declare the type
type TSound = (Click, Clack, Clock);
Unfortunately, Click is also the name of a method defined for TControl and all of the objects in Delphi's VCL that descend from it. So if you're writing a Delphi application and you create an event handler like
procedure TForm1.DBGrid1Enter(Sender: TObject); var Thing: TSound; begin ... Thing := Click; ... end;
you'll get a compilation error; the compiler interprets Click within the scope of the procedure as a reference to TForm's Click method. You can work around this by qualifying the identifier; thus, if TSound is declared in MyUnit, you would use
Thing := MyUnit.Click;
A better solution, however, is to choose constant names that are not likely to conflict with other identifiers. Examples:
type TSound = (tsClick, tsClack, tsClock); TMyColor = (mcRed, mcBlue, mcGreen, mcYellow, mcOrange); Answer = (ansYes, ansNo, ansMaybe);
You can use the (val1, ..., valn) construction directly in variable declarations, as if it were a type name:
var MyCard: (Club, Diamond, Heart, Spade);
But if you declare MyCard this way, you can't declare another variable within the same scope using these constant identifiers. Thus
var Card1: (Club, Diamond, Heart, Spade); var Card2: (Club, Diamond, Heart, Spade);
generates a compilation error. But
var Card1, Card2: (Club, Diamond, Heart, Spade);
type Suit = (Club, Diamond, Heart, Spade); var Card1: Suit; Card2: Suit;
A subrange type represents a subset of the values in another ordinal type (called the base type). Any construction of the form Low..High, where Low and High are constant expressions of the same ordinal type and Low is less than High, identifies a subrange type that includes all values between Low and High. For example, if you declare the enumerated type
type TColors = (Red, Blue, Green, Yellow, Orange, Purple, White, Black);
you can then define a subrange type like
type TMyColors = Green..White;
Here TMyColors includes the values Green, Yellow, Orange, Purple, and White.
You can use numeric constants and characters (string constants of length 1) to define subrange types:
type SomeNumbers = -128..127; Caps = 'A'..'Z';
When you use numeric or character constants to define a subrange, the base type is the smallest integer or character type that contains the specified range.
The Low..High construction itself functions as a type name, so you can use it directly in variable declarations. For example,
var SomeNum: 1..500;
declares an integer variable whose value can be anywhere in the range from 1 to 500.
The ordinality of each value in a subrange is preserved from the base type. (In the first example above, if Color is a variable that holds the value Green, Ord(Color) returns 2 regardless of whether Color is of type TColors or TMyColors.) Values do not wrap around the beginning or end of a subrange, even if the base is an integer or character type; incrementing or decrementing past the boundary of a subrange simply converts the value to the base type. Hence, while
type Percentile = 0..99; var I: Percentile; ... I := 100;
... I := 99; Inc(I);
assigns the value 100 to I (unless compiler range-checking is enabled).
The use of constant expressions in subrange definitions introduces a syntactic difficulty. In any type declaration, when the first meaningful character after = is a left parenthesis, the compiler assumes that an enumerated type is being defined. Hence the code
const X = 50; Y = 10; type Scale = (X - Y) * 2..(X + Y) * 2;
produces an error. Work around this problem by rewriting the type declaration to avoid the leading parenthesis:
type Scale = 2 * (X - Y)..(X + Y) * 2;
A real type defines a set of numbers that can be represented with floating-point notation. The table below gives the ranges and storage formats for the fundamental real types.
The generic type Real, in its current implementation, is equivalent to Double.
Note: The six-byte Real48 type was called Real in earlier versions of Object Pascal. If you are recompiling code that uses the older, six-byte Real type, you may want to change it to Real48. You can also use the {$REALCOMPATIBILITY ON} compiler directive to turn Real back into the six-byte type.
The following remarks apply to fundamental real types.
A string represents a sequence of characters. Object Pascal supports the following predefined string types.
AnsiString, sometimes called the long string, is the preferred type for most purposes.
String types can be mixed in assignments and expressions; the compiler automatically performs required conversions. But strings passed by reference to a function or procedure (as var and out parameters) must be of the appropriate type. Strings can be explicitly cast to a different string type (see "Typecasts").
The reserved word string functions like a generic type identifier. For example,
var S: string;
creates a variable S that holds a string. In the default {$H+} state, the compiler interprets string (when it appears without a bracketed number after it) as AnsiString. Use the {$H-} directive to turn string into ShortString.
The standard function Length returns the number of characters in a string. The SetLength procedure adjusts the length of a string. See the online Help for details.
Comparison of strings is defined by the ordering of the characters in corresponding positions. Between strings of unequal length, each character in the longer string without a corresponding character in the shorter string takes on a greater-than value. For example, "AB" is greater than "A"; that is, 'AB' > 'A' returns True. Zero-length strings hold the lowest values.
You can index a string variable just as you would an array. If S is a string variable and i an integer expression, S[i] represents the ith character--or, strictly speaking, the ith byte--in S. For a ShortString or AnsiString, S[i] is of type AnsiChar; for a WideString, S[i] is of type WideChar. The statement MyString[2] := 'A'; assigns the value A to the second character of MyString. The following code uses the standard UpCase function to convert MyString to uppercase.
var I: Integer;
begin
I := Length(MyString);
while I > 0 do
begin
MyString[I] := UpCase(MyString[I]);
I := I - 1;
end;
end;
Be careful indexing strings in this way, since overwriting the end of a string can cause access violations. Also, avoid passing long-string indexes as var parameters, because this results in inefficient code.
You can assign the value of a string constant--or any other expression that returns a string--to a variable. The length of the string changes dynamically when the assignment is made. Examples:
MyString := 'Hello world!';
MyString := 'Hello ' + 'world';
MyString := MyString + '!';
MyString := ' '; { space }
MyString := ''; { empty string }
For more information, see "Character strings" and "String operators".
A ShortString is 0 to 255 characters long. While the length of a ShortString can change dynamically, its memory is a statically allocated 256 bytes; the first byte stores the length of the string, and the remaining 255 bytes are available for characters. If S is a ShortString variable, Ord(S[0]), like Length(S), returns the length of S; assigning a value to S[0], like calling SetLength, changes the length of S. ShortString uses 8-bit ANSI characters and is maintained for backward compatibility only.
Object Pascal supports short-string types--in effect, subtypes of ShortString--whose maximum length is anywhere from 0 to 255 characters. These are denoted by a bracketed numeral appended to the reserved word string. For example,
var MyString: string[100];
creates a variable called MyString whose maximum length is 100 characters. This is equivalent to the declarations
type CString = string[100]; var MyString: CString;
Variables declared in this way allocate only as much memory as the type requires--that is, the specified maximum length plus one byte. In our example, MyString uses 101 bytes, as compared to 256 bytes for a variable of the predefined ShortString type.
When you assign a value to a short-string variable, the string is truncated if it exceeds the maximum length for the type.
The standard functions High and Low operate on short-string type identifiers and variables. High returns the maximum length of the short-string type, while Low returns zero.
AnsiString, also called a long string, represents a dynamically allocated string whose maximum length is limited only by available memory. It uses 8-bit ANSI characters.
A long-string variable is a pointer occupying four bytes of memory. When the variable is empty--that is, when it contains a zero-length string--the pointer is nil and the string uses no additional storage. When the variable is nonempty, it points to a dynamically allocated block of memory that contains the string value, a 32-bit length indicator, and a 32-bit reference count. This memory is allocated on the heap, but its management is entirely automatic and requires no user code.
Because long-string variables are pointers, two or more of them can reference the same value without consuming additional memory. The compiler exploits this to conserve resources and execute assignments faster. Whenever a long-string variable is destroyed or assigned a new value, the reference count of the old string (the variable's previous value) is decremented and the reference count of the new value (if there is one) is incremented; if the reference count of a string reaches zero, its memory is deallocated. This process is called reference-counting. When indexing is used to change the value of a single character in a string, a copy of the string is made if--but only if--its reference count is greater than one. This is called copy-on-write semantics.
The WideString type represents a dynamically allocated string of 16-bit Unicode characters. In most respects it is similar to AnsiString, but it is less efficient because it does not implement reference-counting and copy-on-write semantics.
WideString is compatible with the COM BSTR type. Delphi has COM support features that convert AnsiString values to WideString, but if you make calls to the COM API, you may need to explicitly cast or convert your strings to WideString.
Windows supports single-byte and multibyte character sets as well as Unicode. With a single-byte character set (SBCS), each byte in a string represents one character. The ANSI character set used by most Western versions of Windows is a single-byte character set.
In a multibyte character set (MBCS), some characters are represented by one byte and others by more than one byte. The first byte of a multibyte character is called the lead byte. In general, the lower 128 characters of a multibyte character set map to the 7-bit ASCII characters, and any byte whose ordinal value is greater than 127 is the lead byte of a multibyte character. Only single-byte characters can contain the null value (#0). Multibyte character sets--especially double-byte character sets (DBCS)--are widely used for Asian languages.
In the Unicode character set, each character is represented by two bytes. Thus a Unicode string is a sequence not of individual bytes but of two-byte words. Unicode characters and strings are also called wide characters and wide character strings. The first 256 Unicode characters map to the ANSI character set.
Object Pascal supports single-byte and multibyte characters and strings through the Char, PChar, AnsiChar, PAnsiChar, and AnsiString types. Indexing of multibyte strings is not reliable, since S[i] represents the ith byte (not necessarily the ith character) in S. However, Delphi's standard string-handling functions have multibyte-enabled counterparts that also implement locale-specific ordering for characters. (Names of multibyte functions usually start with Ansi-. For example, the multibyte version of StrPos is AnsiStrPos.) Multibyte character support is operating-system dependent and based on the current Windows locale.
Object Pascal supports Unicode characters and strings through the WideChar, PWideChar, and WideString types.
Many programming languages, including C and C++, lack a dedicated string data type. These languages, and environments like Windows that are built with them, rely on null-terminated strings. A null-terminated string is a zero-based array of characters that ends with NULL (#0); since the array has no length indicator, the first NULL character marks the end of the string. You can use Object Pascal constructions and special routines in the SysUtils unit (see "Standard routines and I/O") to handle null-terminated strings when you need to share data with systems that use them.
For example, the following type declarations could be used to store null-terminated strings.
type TIdentifier = array[0..15] of Char; TFileName = array[0..259] of Char; TMemoText = array[0..1023] of WideChar;
You can assign a string constant to a statically allocated zero-based character array. (Dynamic arrays won't work for this purpose.) If you initialize an array constant with a string that is shorter than the declared length of the array, the remaining characters are set to #0. For more information about arrays, see "Arrays".
To manipulate null-terminated strings, it is often necessary to use pointers. (See "Pointers and pointer types".) String constants are assignment-compatible with the PChar and PWideChar types, which represent pointers to null-terminated arrays of Char and WideChar values. For example,
var P: PChar; ... P := 'Hello world!';
points P to an area of memory that contains a null-terminated copy of "Hello world!" This is equivalent to
const TempString: array[0..12] of Char = 'Hello world!'#0; var P: PChar; ... P := @TempString;
You can also pass string constants to any function that takes value or const parameters of type PChar or PWideChar--for example StrUpper('Hello world!'). As with assignments to a PChar, the compiler generates a null-terminated copy of the string and gives the function a pointer to that copy. Finally, you can initialize PChar or PWideChar constants with string literals, alone or in a structured type. Examples:
const
Message: PChar = 'Program terminated';
Prompt: PChar = 'Enter values: ';
Digits: array[0..9] of PChar = (
'Zero', 'One', 'Two', 'Three', 'Four',
'Five', 'Six', 'Seven', 'Eight', 'Nine');
Zero-based character arrays are compatible with PChar and PWideChar. When you use a character array in place of a pointer value, the compiler converts the array to a pointer constant whose value corresponds to the address of the first element of the array. For example,
var MyArray: array[0..32] of Char; MyPointer: PChar; begin MyArray := 'Hello'; MyPointer := MyArray; SomeProcedure(MyArray); SomeProcedure(MyPointer); end;
This code calls SomeProcedure twice with the same value.
A character pointer can be indexed as if it were an array. In the example above, MyPointer[0] returns H. The index specifies an offset added to the pointer before it is dereferenced. (For PWideChar variables, the index is automatically multiplied by two.) Thus, if P is a character pointer, P[0] is equivalent to P^ and specifies the first character in the array, P[1] specifies the second character in the array, and so forth; P[-1] specifies the "character" immediately to the left of P[0]. The compiler performs no range checking on these indexes.
The StrUpper function illustrates the use of pointer indexing to iterate through a null-terminated string:
function StrUpper(Dest, Source: PChar; MaxLen: Integer): PChar;
var
I: Integer;
begin
I := 0;
while (I < MaxLen) and (Source[I] <> #0) do
begin
Dest[I] := UpCase(Source[I]);
Inc(I);
end;
Dest[I] := #0;
Result := Dest;
end;
You can mix long strings (AnsiString values) and null-terminated strings (PChar values) in expressions and assignments, and you can pass PChar values to functions or procedures that take long-string parameters. The assignment S := P, where S is a string variable and P is a PChar expression, copies a null-terminated string into a long string.
In a binary operation, if one operand is a long string and the other a PChar, the PChar operand is converted to a long string.
You can cast a PChar value as a long string. This is useful when you want to perform a string operation on two PChar values. For example,
S := string(P1) + string(P2);
You can also cast a long string as a null-terminated string. The following rules apply.
PChar(S) casts S as a null-terminated string; it returns a pointer to the first character in S. For example, if Str1 and Str2 are long strings, you could call the Win32 API MessageBox function like this:
MessageBox(0, PChar(Str1), PChar(Str2), MB_OK);
(MessageBox is declared in the Windows interface unit.)
Pointer(S) to cast a long string to an untyped pointer. But if S is empty, the typecast returns nil.
The same rules apply when mixing WideString values with PWideChar values.
Instances of a structured type hold more than one value. Structured types include sets, arrays, records, and files as well as class, class-reference, and interface types. (For information about class and class-reference types, see "Classes and objects." For information about interfaces, see "Object interfaces.") Except for sets, which hold ordinal values only, structured types can contain other structured types; a type can have unlimited levels of structuring.
By default, the values in a structured type are aligned on word or double-word boundaries for faster access. When you declare a structured type, you can include the reserved word packed to implement compressed data storage. For example,
type TNumbers = packed array[1..100] of Real;
Using packed slows data access and, in the case of a character array, affects type compatibility. For more information, see "Memory management."
A set is a collection of values of the same ordinal type. The values have no inherent order, nor is it meaningful for a value to be included twice in a set.
The range of a set type is the power set of a specific ordinal type, called the base type; that is, the possible values of the set type are all the subsets of the base type, including the empty set. The base type can have no more than 256 possible values, and their ordinalities must fall between 0 and 255. Any construction of the form
where baseType is an appropriate ordinal type, identifies a set type.
Because of the size limitations for base types, set types are usually defined with subranges. For example, the declarations
type TSomeInts = 1..250; TIntSet = set of TSomeInts;
create a set type called TIntSet whose values are collections of integers in the range from 1 to 250. You could accomplish the same thing with
type TIntSet = set of 1..250;
Given this declaration, you can create a sets like this:
var Set1, Set2: TIntSet; ... Set1 := [1, 3, 5, 7, 9]; Set2 := [2, 4, 6, 8, 10]
You can also use the set of ... construction directly in variable declarations:
var MySet: set of 'a'..'z'; ... MySet := ['a','b','c'];
Other examples of set types include
set of Byte set of (Club, Diamond, Heart, Spade) set of Char;
The in operator tests set membership:
if 'a' in MySet then ... { do something } ;
Every set type can hold the empty set, denoted by []. For more information about sets, see "Set constructors" and "Set operators".
An array represents an indexed collection of elements of the same type (called the base type). Because each element has a unique index, arrays, unlike sets, can meaningfully contain the same value more than once. Arrays can be allocated statically or dynamically.
Static array types are denoted by constructions of the form
array[indexType1, ..., indexTypen] of baseType
where each indexType is an ordinal type whose range does not exceed 2GB. Since the indexTypes index the array, the number of elements an array can hold is limited by the product of the sizes of the indexTypes. In practice, indexTypes are usually integer subranges.
In the simplest case of a one-dimensional array, there is only a single indexType. For example,
var MyArray: array[1..100] of Char;
declares a variable called MyArray that holds an array of 100 character values. Given this declaration, MyArray[3] denotes the third character in MyArray. If you create a static array but don't assign values to all its elements, the unused elements are still allocated and contain random data; they are like uninitialized variables.
A multidimensional array is an array of arrays. For example,
type TMatrix = array[1..10] of array[1..50] of Real;
type TMatrix = array[1..10, 1..50] of Real;
Whichever way TMatrix is declared, it represents an array of 500 real values. A variable MyMatrix of type TMatrix can be indexed like this: MyMatrix[2,45]; or like this: MyMatrix[2][45]. Similarly,
packed array[Boolean,1..10,TShoeSize] of Integer;
packed array[Boolean] of packed array[1..10] of packed array[TShoeSize] of Integer;
The standard functions Low and High operate on array type identifiers and variables. They return the low and high bounds of the array's first index type. The standard function Length returns the number of elements in the array's first dimension.
A one-dimensional, packed, static array of Char values is called a packed string. Packed-string types are compatible with string types and with other packed-string types that have the same number of elements. See "Type compatibility and identity".
An array type of the form array[0..x] of Char is called a zero-based character array. Zero-based character arrays are used to store null-terminated strings and are compatible with PChar values. See "Working with null-terminated strings".
Dynamic arrays do not have a fixed size or length. Instead, memory for a dynamic array is reallocated when you assign a value to the array or pass it to the SetLength procedure. Dynamic-array types are denoted by constructions of the form
var MyFlexibleArray: array of Real;
declares a one-dimensional dynamic array of reals. The declaration does not allocate memory for MyFlexibleArray. To create the array in memory, call SetLength. For example, given the declaration above,
SetLength(MyFlexibleArray, 20);
allocates an array of 20 reals, indexed 0 to 19. Dynamic arrays are always integer-indexed, always starting from 0.
Dynamic-array variables are implicitly pointers and are managed by the same reference-counting technique used for long strings. To deallocate a dynamic array, assign nil to a variable that references the array or pass the variable to Finalize; either of these methods disposes of the array, provided there are no other references to it. Dynamic arrays of length 0 have the value nil. Do not apply the dereference operator (^) to a dynamic-array variable or pass it to the New or Dispose procedure.
If X and Y are variables of the same dynamic-array type, X := Y points X to the same array as Y. (There is no need to allocate memory for X before performing this operation.) Unlike strings and static arrays, dynamic arrays are not automatically copied before they are written to. For example, after this code executes--
var A, B: array of Integer; begin SetLength(A, 1); A[0] := 1; B := A; B[0] := 2; end;
--the value of A[0] is 2. (If A and B were static arrays, A[0] would still be 1.)
Assigning to a dynamic-array index (for example, MyFlexibleArray[2] := 7) does not reallocate the array. Out-of-range indexes are not reported at compile time.
When dynamic-array variables are compared, their references are compared, not their array values. Thus, after execution of the code
var A, B: array of Integer; begin SetLength(A, 1); SetLength(B, 1); A[0] := 2; B[0] := 2; end;
A = B returns False but A[0] = B[0] returns True.
To truncate a dynamic array, pass it to the Copy function and assign the result back to the array variable. For example, if A is a dynamic array, A := Copy(A, 0, 20) truncates all but the first 20 elements of A.
Once a dynamic array has been allocated, you can pass it to the standard functions Length, High, and Low. Length returns the number of elements in the array, High returns the array's highest index (that is, Length - 1), and Low returns 0. In the case of a zero-length array, High returns -1 (with the anomalous consequence that High < Low).
Note: In some function and procedure declarations, array parameters are represented as array of baseType, without any index types specified. For example,
function CheckStrings(A: array of string): Boolean;
This indicates that the function operates on all arrays of the specified base type, regardless of their size, how they are indexed, or whether they are allocated statically or dynamically. See "Open array parameters".
To declare multidimensional dynamic arrays, use iterated array of ... constructions. For example,
type TMessageGrid = array of array of string; var Msgs: TMessageGrid;
declares a two-dimensional array of strings. To instantiate this array, call SetLength with two integer arguments. For example, if I and J are integer-valued variables,
SetLength(Msgs,I,J);
allocates an I-by-J array, and Msgs[0,0] denotes an element of that array.
You can create multidimensional dynamic arrays that are not rectangular. The first step is to call SetLength, passing it parameters for the first n dimensions of the array. For example,
var Ints: array of array of Integer; SetLength(Ints,10);
allocates ten rows for Ints but no columns. Later, you can allocate the columns one at a time (giving them different lengths); for example
SetLength(Ints[2], 5);
makes the third column of Ints five integers long. At this point (even if the other columns haven't been allocated) you can assign values to the third column--for example, Ints[2,4] := 6.
The following example uses dynamic arrays (and the IntToStr function declared in the SysUtils unit) to create a triangular matrix of strings.
var
A : array of array of string;
I, J : Integer;
begin
SetLength(A, 10);
for I := Low(A) to High(A) do
begin
SetLength(A[I], I);
for J := Low(A[I]) to High(A[I]) do
A[I,J] := IntToStr(I) + ',' + IntToStr(J) + ' ';
end;
end;
Arrays are assignment-compatible only if they are of the same type. Because Pascal uses name-equivalence for types, the following code will not compile.
var Int1: array[1..10] of Integer; Int2: array[1..10] of Integer; ... Int1 := Int2;
To make the assignment work, declare the variables as
var Int1, Int2: array[1..10] of Integer;
type IntArray = array[1..10] of Integer; var Int1: IntArray; Int2: IntArray;
A record (analogous to a structure in some languages) represents a heterogeneous set of elements. Each element is called a field; the declaration of a record type specifies a name and type for each field. The syntax of a record type declaration is
type recordTypeName = record
fieldList1: type1;
...
fieldListn: typen;
end
where recordTypeName is a valid identifier, each type denotes a type, and each fieldList is a valid identifier or a comma-delimited list of identifiers. The final semicolon is optional.
For example, the following declaration creates a record type called TDateRec.
type
TDateRec = record
Year: Integer;
Month: (Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
Day: 1..31;
end;
Each TDateRec contains three fields: an integer value called Year, a value of an enumerated type called Month, and another integer between 1 and 31 called Day. The identifiers Year, Month, and Day are the field designators for TDateRec, and they behave like variables. The TDateRec type declaration, however, does not allocate any memory for the Year, Month, and Day fields; memory is allocated when you instantiate the record, like this:
var Record1, Record2: TDateRec;
This variable declaration creates two instances of TDateRec, called Record1 and Record2.
You can access the fields of a record by qualifying the field designators with the record's name:
Record1.Year := 1904; Record1.Month := Jun; Record1.Day := 16;
with Record1 do begin Year := 1904; Month := Jun; Day := 16; end;
You can now copy the values of Record1's fields to Record2:
Record2 := Record1;
Because the scope of a field designator is limited to the record in which it occurs, you don't have to worry about naming conflicts between field designators and other variables.
Instead of defining record types, you can use the record ... construction directly in variable declarations:
var S: record Name: string; Age: Integer; end;
However, a declaration like this largely defeats the purpose of records, which is to avoid repetitive coding of similar groups of variables. Moreover, separately declared records of this kind will not be assignment-compatible, even if their structures are identical.
A record type can have a variant part, which looks like a case statement. The variant part must follow the other fields in the record declaration.
To declare a record type with a variant part, use the following syntax.
type recordTypeName = record
fieldList1: type1;
...
fieldListn: typen;
case tag: ordinalType of
constantList1: (variant1);
...
constantListn: (variantn);
end;
The first part of the declaration--up to the reserved word case--is the same as that of a standard record type. The remainder of the declaration--from case to the optional final semicolon--is called the variant part. In the variant part,
: type constructions in the main part of the record type. That is, a variant has the form
fieldList1: type1;
...
fieldListn: typen;
where each fieldList is a valid identifier or comma-delimited list of identifiers, each type denotes a type, and the final semicolon is optional. The types must not be long strings, dynamic arrays, variants (that is, Variant types), or interfaces, nor can they be structured types that contain long strings, dynamic arrays, variants, or interfaces; but they can be pointers to these types.
Records with variant parts are complicated syntactically but deceptively simple semantically. The variant part of a record contains several variants which share the same space in memory. You can read or write to any field of any variant at any time; but if you write to a field in one variant and then to a field in another variant, you may be overwriting your own data. The tag, if there is one, functions as an extra field (of type ordinalType) in the non-variant part of the record.
Variant parts have two purposes. First, suppose you want to create a record type that has fields for different kinds of data, but you know that you will never need to use all of the fields in a single record instance. For example,
type
TEmployee = record
FirstName, LastName: string[40];
BirthDate: TDate;
case Salaried: Boolean of
True: (AnnualSalary: Currency);
False: (HourlyWage: Currency);
end;
The idea here is that every employee has either a salary or an hourly wage, but not both. So when you create an instance of TEmployee, there is no reason to allocate enough memory for both fields. In this case, the only difference between the variants is in the field names, but the fields could just as easily have been of different types. Consider some more complicated examples:
type
TPerson = record
FirstName, LastName: string[40];
BirthDate: TDate;
case Citizen: Boolean of
True: (Birthplace: string[40]);
False: (Country: string[20];
EntryPort: string[20];
EntryDate, ExitDate: TDate);
end;
type
TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other);
TFigure = record
case TShapeList of
Rectangle: (Height, Width: Real);
Triangle: (Side1, Side2, Angle: Real);
Circle: (Radius: Real);
Ellipse, Other: ();
end;
For each record instance, the compiler allocates enough memory to hold all the fields in the largest variant. The optional tag and the constantLists (like Rectangle, Triangle, and so forth in the last example above) play no role in the way the compiler manages the fields; they are there only for the convenience of the programmer.
The second reason for variant parts is that they let you treat the same data as belonging to different types, even in cases where the compiler would not allow a typecast. For example, if you have a 64-bit Real as the first field in one variant and a 32-bit Integer as the first field in another, you can assign a value to the Real field and then read back the first 32 bits of it as the value of the Integer field (passing it, say, to a function that requires integer parameters).
A file is an ordered set of elements of the same type. Standard I/O routines use the predefined TextFile or Text type, which represents a file containing characters organized into lines. For more information about file input and output, see "Standard routines and I/O."
To declare a file type, use the syntax
type fileTypeName = file of type
where fileTypeName is any valid identifier and type is a fixed-size type. Pointer types--whether implicit or explicit--are not allowed, so a file cannot contain dynamic arrays, long strings, classes, objects, pointers, variants, other files, or structured types that contain any of these.
type
PhoneEntry = record
FirstName, LastName: string[20];
PhoneNumber: string[15];
Listed: Boolean;
end;
PhoneList = file of PhoneEntry;
declares a file type for recording names and telephone numbers.
You can also use the file of ... construction directly in a variable declaration. For example,
var List1: file of PhoneEntry;
The word file by itself indicates an untyped file:
var DataFile: file;
For more information, see "Untyped files".
Files are not allowed in arrays or records.
A pointer is a variable that denotes a memory address. When a pointer holds the address of another variable, we say that it points to the location of that variable in memory or to the data stored there. In the case of an array or other structured type, a pointer holds the address of the first element in the structure.
Pointers are typed to indicate the kind of data stored at the addresses they hold. The general-purpose Pointer type can represent a pointer to any data, while more specialized pointer types reference only specific types of data. Pointers occupy four bytes of memory.
To see how pointers work, look at the following example.
1 var 2 X, Y: Integer; // X and Y are Integer variables 3 P: ^Integer; // P points to an Integer 4 begin 5 X := 17; // assign a value to X 6 P := @X; // assign the address of X to P 7 Y := P^; // dereference P; assign the result to Y 8 end;
Line 2 declares X and Y as variables of type Integer. Line 3 declares P as a pointer to an Integer value; this means that P can point to the location of X or Y. Line 5 assigns a value to X, and line 6 assigns the address of X (denoted by @X) to P. Finally, line 7 retrieves the value at the location pointed to by P (denoted by ^P) and assigns it to Y. After this code executes, X and Y have the same value, namely 17.
The @ operator, which we have used here to take the address of a variable, also operates on functions and procedures. For more information, see "The @ operator" and "Procedural types in statements and expressions".
The symbol ^ has two purposes, both of which are illustrated in our example. When it appears before a type identifier--
--it denotes a type that represents pointers to variables of type typeName. When it appears after a pointer variable--
--it dereferences the pointer; that is, it returns the value stored at the memory address held by the pointer.
Our example may seem like a roundabout way of copying the value of one variable to another--something that we could have accomplished with a simple assignment statement. But pointers are useful for several reasons. First, understanding pointers will help you to understand Object Pascal, since pointers often operate behind the scenes in code where they don't appear explicitly. Any data type that requires large, dynamically allocated blocks of memory uses pointers. Long-string variables, for instance, are implicitly pointers, as are class variables. Moreover, some advanced programming techniques require the use of pointers.
Finally, pointers are sometimes the only way to circumvent Object Pascal's strict data typing. By referencing a variable with an all-purpose Pointer, casting the Pointer to a more specific type, and then dereferencing it, you can treat the data stored by any variable as if it belonged to any type. For example, the following code assigns data stored in a real variable to an integer variable.
type PInteger = ^Integer; var R: Single; I: Integer; P: Pointer; PI: PInteger; begin ... P := @R; PI := PInteger(P); I := PI^; end;
Of course, reals and integers are stored in different formats. This assignment simply copies raw binary data from R to I, without converting it.
In addition to assigning the result of an @ operation, you can use several standard routines to give a value to a pointer. The New and GetMem procedures assign a memory address to an existing pointer, while the Addr and Ptr functions return a pointer to a specified address or variable.
Dereferenced pointers can be qualified and can function as qualifiers, as in the expression P1^.Data^.
The reserved word nil is a special constant that can be assigned to any pointer. When nil is assigned to a pointer, the pointer doesn't reference anything.
You can declare a pointer to any type, using the syntax
When you define a record or other data type, it's a common practice also to define a pointer to that type. This makes it easy to manipulate instances of the type without copying large blocks of memory.
Standard pointer types exist for many purposes. The most versatile is Pointer, which can point to data of any kind. But a Pointer variable cannot be dereferenced; placing the ^ symbol after a Pointer variable causes a compilation error. To access the data referenced by a Pointer variable, first cast it to another pointer type and then dereference it.
The fundamental types PAnsiChar and PWideChar represent pointers to AnsiChar and WideChar values, respectively. The generic PChar represents a pointer to a Char (that is, in its current implementation, to an AnsiChar). These character pointers are used to manipulate null-terminated strings. (See "Working with null-terminated strings".)
The System and SysUtils units declare many standard pointer types. While these types are not built-in, they are commonly used in Delphi programming.
Procedural types allow you to treat procedures and functions as values that can be assigned to variables or passed to other procedures and functions. For example, suppose you define a function called Calc that takes two integer parameters and returns an integer:
function Calc(X,Y: Integer): Integer;
You can assign the Calc function to the variable F:
var F: function(X,Y: Integer): Integer; F := Calc;
If you take any procedure or function heading and remove the identifier after the word procedure or function, what's left is the name of a procedural type. You can use such type names directly in variable declarations (as in the example above) or to declare new types:
type
TIntegerFunction = function: Integer;
TProcedure = procedure;
TStrProc = procedure(const S: string);
TMathFunc = function(X: Double): Double;
var
F: TIntegerFunction; { F is a parameterless function that returns an integer }
Proc: TProcedure; { Proc is a parameterless procedure }
SP: TStrProc; { SP is a procedure that takes a string parameter }
M: TMathFunc; { M is a function that takes a Double (real) parameter
and returns a Double }
procedure FuncProc(P: TIntegerFunction); { FuncProc is a procedure whose only parameter
is a parameterless integer-valued function }
The variables above are all procedure pointers--that is, pointers to the address of a procedure or function. If you want to reference a method of an instance object (see "Classes and objects"), you need to add the words of object to the procedural type name. For example
type TMethod = procedure of object; TNotifyEvent = procedure(Sender: TObject) of object;
These types represent method pointers. A method pointer is really a pair of pointers; the first stores the address of a method, and the second stores a reference to the object the method belongs to. Given the declarations
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
...
end;
var
MainForm: TMainForm;
OnClick: TNotifyEvent
we could make the following assignment.
OnClick := MainForm.ButtonClick;
Two procedural types are compatible if they have
Procedure pointer types are always incompatible with method pointer types. The value nil can be assigned to any procedural type.
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values, nor can predefined procedures and functions. If you want to use a predefined routine like Length as a procedural value, write a wrapper for it:
function FLength(S: string): Integer; begin Result := Length(S); end;
When a procedural variable is on the left side of an assignment statement, the compiler expects a procedural value on the right. The assignment makes the variable on the left a pointer to the function or procedure indicated on the right. In other contexts, however, using a procedural variable results in a call to the referenced procedure or function. You can even use a procedural variable to pass parameters:
var F: function(X: Integer): Integer; I: Integer; function SomeFunction(X: Integer): Integer; ... F := SomeFunction; // assign SomeFunction to F I := F(4); // call function; assign result to I
In assignment statements, the type of the variable on the left determines the interpretation of procedure or method pointers on the right. For example,
var F, G: function: Integer; I: Integer; function SomeFunction: Integer; ... F := SomeFunction; // assign SomeFunction to F G := F; // copy F to G I := G; // call function; assign result to I
The first statement assigns a procedural value to F. The second statement copies that value to another variable. The third statement makes a call to the referenced function and assigns the result to I. Because I is an integer variable, not a procedural one, the last assignment actually calls the function (which returns an integer).
In some situations it is less clear how a procedural variable should be interpreted. Consider the statement
if F = MyFunction then ...;
In this case, the occurrence of F results in a function call; the compiler calls the function pointed to by F, then calls the function MyFunction, then compares the results. The rule is that whenever a procedural variable occurs within an expression, it represents a call to the referenced procedure or function. In a case where F references a procedure (which doesn't return a value), or where F references a function that requires parameters, the statement above causes a compilation error. To compare the procedural value of F with MyFunction, use
if @F = @MyFunction then ...;
@F converts F into an untyped pointer variable that contains an address, and @MyFunction returns the address of MyFunction.
To get the memory address of a procedural variable (rather than the address stored in it), use @@. For example, @@F returns the address of F.
The @ operator can also be used to assign an untyped pointer value to a procedural variable. For example,
var StrComp: function(Str1, Str2: PChar): Integer; ... @StrComp := GetProcAddress(KernelHandle, 'lstrcmpi');
calls the Windows GetProcAddress function and points StrComp to the result.
Any procedural variable can hold the value nil, which means that it points to nothing. But attempting to call a nil-valued procedural variable is an error. To test whether a procedural variable is assigned, use the standard function Assigned:
if Assigned(OnClick) then OnClick(X);
Sometimes it is necessary to manipulate data whose type varies or cannot be determined at compile time. In these cases, one option is to use variables and parameters of type Variant, which represent values that can change type at runtime. Variants, as they are called, offer greater flexibility but consume more memory than regular variables, and operations on them are slower than on statically bound types. Moreover, illicit operations on variants often result in runtime errors, where similar mistakes with regular variables would have been caught at compile time.
Variants can hold values of any type except records, sets, static arrays, files, classes, class references, pointers, and Int64. In other words, with the exception of Int64, variants can hold anything but structured types and pointers. They can hold COM and CORBA objects, whose methods and properties can be accessed through them. (See "Object interfaces".) They can hold dynamic arrays, and they can hold a special kind of static array called a variant array. (See "Variant arrays".) Variants can mix with other variants and with integer, real, string, and Boolean values in expressions and assignments; the compiler automatically performs type conversions.
Variants that contain strings cannot be indexed. That is, if V is a variant that holds a string value, the construction V[1] is illegitimate.
A variant occupies 16 bytes of memory and consists of a type code and a value, or pointer to a value, of the type specified by the code. All variants are initialized on creation to the special value Unassigned. The special value Null indicates unknown or missing data.
The standard function VarType returns a variant's type code. The varTypeMask constant is a bit mask used to extract the code from VarType's return value, so that, for example,
VarType(V) and varTypeMask = varDouble
returns True if V contains a Double or an array of Double. (The mask simply hides the first bit, which indicates whether the variant holds an array.) The TVarData record type defined in the System unit can be used to typecast variants and gain access to their internal representation. See the online Help on VarType for a list if codes, and note that new type codes may be added in future implementations of Object Pascal.
All integer, real, string, character, and Boolean types (except Int64) are assignment-compatible with Variant. Expressions can be explicitly cast as variants, and the VarAsType and VarCast standard routines can be used to change the internal representation of a variant. The following code demonstrates the use of variants and some of the automatic conversions performed when variants are mixed with other types.
var
V1, V2, V3, V4, V5: Variant;
I: Integer;
D: Double;
S: string;
begin
V1 := 1; { integer value }
V2 := 1234.5678; { real value }
V3 := 'Hello world!'; { string value }
V4 := '1000'; { string value }
V5 := V1 + V2 + V4; { real value 2235.5678}
I := V1; { I = 1 (integer value) }
D := V2; { D = 1234.5678 (real value) }
S := V3; { S = 'Hello world!' (string value) }
I := V4; { I = 1000 (integer value) }
S := V5; { S = '2235.5678' (string value) }
end;
The compiler performs type conversions according to the following rules.
Out-of-range assignments often result in the target variable getting the highest value in its range. Invalid assignments or casts raise the EVariantError exception.
Special conversion rules apply to the TDateTime real type declared in the System unit. When a TDateTime is converted to any other type, it treated as a normal Double. When an integer, real, or Boolean is converted to a TDateTime, it is first converted to a Double, then read as a date-time value. When a string is converted to a TDateTime, it is interpreted as a date-time value using the Windows regional settings. When an Unassigned value is converted to TDateTime, it is treated like the real or integer value 0. Converting a Null value to TDateTime raises an exception.
If a variant references a COM object, any attempt to convert it reads the object's default property and converts that value to the requested type. If the object has no default property, an exception is raised.
All operators except ^, is, and in take variant operands. Operations on variants return Variant values; they return Null if one or both operands is Null, and raise an exception if one or both operands is Unassigned. In a binary operation, if only one operand is a variant, the other is converted to a variant.
The return type of an operation is determined by its operands. In general, the same rules that apply to operands of statically bound types apply to variants. For example, if V1 and V2 are variants that hold an integer and a real value, then V1 + V2 returns a real-valued variant. (See "Operators".) With variants, however, you can perform binary operations on combinations of values that would not be allowed using statically typed expressions. When possible, the compiler converts mismatched variants using the rules summarized in Table 5.7. For example, if V3 and V4 are variants that hold a numeric string and an integer, the expression V3 + V4 returns an integer-valued variant; the numeric string is converted to an integer before the operation is performed.
You cannot assign an ordinary static array to a variant. Instead, create a variant array by calling either of the standard functions VarArrayCreate or VarArrayOf. For example,
V: Variant; ... V := VarArrayCreate([0,9], varInteger);
creates a variant array of integers (of length 10) and assigns it to the variant V. The array can be indexed using V[0], V[1], and so forth, but it is not possible to pass a variant array element as a var parameter. Variant arrays are always indexed with integers.
The second parameter in the call to VarArrayCreate is the type code for the array's base type. For a list of these codes, see the online Help on VarType. Never pass the code varString to VarArrayCreate; to create a variant array of strings, use varOleStr.
Variants can hold variant arrays of different sizes, dimensions, and base types. The elements of a variant array can be of any type allowed in variants except ShortString and AnsiString, and if the base type of the array is Variant, its elements can even be heterogeneous. Use the VarArrayRedim function to resize a variant array. Other standard routines that operate on variant arrays include VarArrayDimCount, VarArrayLowBound, VarArrayHighBound, VarArrayRef, VarArrayLock, and VarArrayUnlock.
When a variant containing a variant array is assigned to another variant or passed as a value parameter, the entire array is copied. Don't perform such operations unnecessarily, since they are memory-inefficient.
The OleVariant type represents variants that contain only COM-compatible types. When a Variant is assigned to an OleVariant, incompatible types are converted to their compatible counterparts. For example, if a variant containing an AnsiString is assigned to an OleVariant, the AnsiString becomes a WideString.
To understand which operations can be performed on which expressions, we need to distinguish several kinds of compatibility among types and values. These include type identity, type compatibility, and assignment-compatibility.
Type identity is almost straightforward. When one type identifier is declared using another type identifier, without qualification, they denote the same type. Thus, given the declarations
type T1 = Integer; T2 = T1; T3 = Integer; T4 = T2;
T1, T2, T3, T4, and Integer all denote the same type. To create distinct types, repeat the word type in the declaration. For example,
type TMyInteger = type Integer;
creates a new type called TMyInteger which is not identical to Integer.
Language constructions that function as type names denote a different type each time they occur. Thus the declarations
type TS1 = set of Char; TS2 = set of Char;
create two distinct types, TS1 and TS2. Similarly, the variable declarations
var S1: string[10]; S2: string[10];
create two variables of distinct types. To create variables of the same type, use
var S1, S2: string[10];
type MyString = string[10]; var S1: MyString; S2: MyString;
Every type is compatible with itself. Two distinct types are compatible if they satisfy at least one of the following conditions.
array[0..n] of Char.
Assignment-compatibility is not a symmetric relation. An expression of type T2 can be assigned to a variable of type T1 if the value of the expression falls in the range of T1 and at least one of the following conditions is satisfied.
array[0..n] of Char.
A type declaration specifies an identifier that denotes a type. The syntax for a type declaration is
where newTypeName is a valid identifier. For example, given the type declaration
type TMyString = string;
you can make the variable declaration
var S: TMyString;
A type identifier's scope doesn't include the type declaration itself (except for pointer types). So you cannot, for example, define a record type that uses itself recursively.
When you declare a type that is identical to an existing type, the compiler treats the new type identifier as an alias for the old one. Thus, given the declarations
type TValue = Real; var X: Real; Y: TValue;
X and Y are of the same type; at runtime, there is no way to distinguish TValue from Real. This is usually of little consequence, but if your purpose in defining a new type is to utilize runtime type information--for example, to associate a Delphi property editor with properties of a particular type--the distinction between "different name" and "different type" becomes important. In this case, use the syntax
type TValue = type Real;
forces the compiler to create a new, distinct type called TValue.
A variable is an identifier whose value can change at runtime. Put differently, a variable is a name for a location in memory; you can use the name to read or write to the memory location. Variables are like containers for data, and, because they are typed, they tell the compiler how to interpret the data they hold.
The basic syntax for a variable declaration is
where identifierList is a comma-delimited list of valid identifiers and type is any valid type. For example,
var I: Integer;
declares a variable I of type Integer, while
var X, Y: Real;
declares two variables--X and Y--of type Real.
Consecutive variable declarations do not have to repeat the reserved word var:
var X, Y, Z: Double; I, J, K: Integer; Digit: 0..9; Okay: Boolean;
Variables declared within a procedure or function are sometimes called local, while other variables are called global. Global variables can be initialized at the same time they are declared, using the syntax
var identifier: type = constantExpression;
where constantExpression is any constant expression representing a value of type type. (For more information about constant expressions, see "Constant expressions".) Thus the declaration
var I: Integer = 7;
is equivalent to the declaration and statement
var I: Integer; ... I := 7;
Multiple variable declarations (such as var X, Y, Z: Real;) cannot include initializations, nor can declarations of variant and file-type variables.
If you don't explicitly initialize a global variable, the compiler initializes it to 0. Local variables, in contrast, cannot be initialized in their declarations and contain random data until a value is assigned to them.
When you declare a variable, you are allocating memory which is freed automatically when the variable is no longer used. In particular, local variables exist only until the program exits from the function or procedure in which they are declared. For more information about variables and memory management, see "Memory management."
To declare a variable that resides at a specified memory address, put the word absolute after the type name, followed by an address. Example:
var CrtMode: Byte absolute $0040;
This technique is useful only in low-level programming, for example when writing device drivers.
To create a new variable that resides at the same address as an existing variable, use the name of the existing variable (instead of an address) after the word absolute. For example,
var Str: string[32]; StrLen: Byte absolute Str;
specifies that the variable StrLen should start at the same address as Str. Since the first byte of a short string contains the string's length, the value of StrLen is the length of Str.
You cannot initialize a variable in an absolute declaration.
You can create dynamic variables by calling the GetMem or New procedure. Such variables are allocated on the heap and are not managed automatically. Once you create one, it is your responsibility ultimately to free the variable's memory; use FreeMem to destroy variables created by GetMem and Dispose to destroy variables created by New. Other standard routines that operate on dynamic variables include ReallocMem, Initialize, StrAlloc, and StrDispose.
Long strings and dynamic arrays are also heap-allocated dynamic variables, but their memory is managed automatically.
Thread-local (or thread) variables are used in multithreaded applications. A thread-local variable is like a global variable, except that each thread of execution gets its own private copy of the variable, which cannot be accessed from other threads. Thread-local variables are declared with threadvar instead of var. For example,
threadvar X: Integer;
Reference-counted variables (such as long strings, dynamic arrays, or interfaces) are not thread-safe, even if they are declared with threadvar. Do not use dynamic thread variables, since there is in general no way to free the heap-allocated memory created by each thread of execution. Finally, do not create pointer- or procedural-type thread variables.
Several different language constructions are referred to as "constants". There are numeric constants (also called numerals) like 17, and string constants (also called character strings or string literals) like 'Hello world!'; for information about numeric and string constants, see "Syntactic elements." Every enumerated type defines constants that represent the values of that type. There are predefined constants like True, False, and nil. Finally, there are constants that, like variables, are created individually by declaration.
Declared constants are either true constants or typed constants. These two kinds of constant are superficially similar, but they are governed by different rules and used for different purposes.
A true constant is a declared identifier whose value cannot change. For example,
const MaxValue = 237;
declares a constant called MaxValue that returns the integer 237. The syntax for declaring a true constant is
const identifier = constantExpression
where identifier is any valid identifier and constantExpression is an expression that the compiler can evaluate without executing your program. (See "Constant expressions" for more information.)
If constantExpression returns an ordinal value, you can specify the type of the declared constant using a value typecast. For example
const MyNumber = Int64(17);
declares a constant called MyNumber, of type Int64, that returns the integer 17. Otherwise, the type of the declared constant is the type of the constantExpression.
Here are some examples of constant declarations:
const
Min = 0;
Max = 100;
Center = (Max - Min) div 2;
Beta = Chr(225);
NumChars = Ord('Z') - Ord('A') + 1;
Message = 'Out of memory';
ErrStr = ' Error: ' + Message + '. ';
ErrPos = 80 - Length(ErrStr) div 2;
Ln10 = 2.302585092994045684;
Ln10R = 1 / Ln10;
Numeric = ['0'..'9'];
Alpha = ['A'..'Z', 'a'..'z'];
AlphaNum = Alpha + Numeric;
A constant expression is an expression that the compiler can evaluate without executing the program in which it occurs. Constant expressions include numerals; character strings; true constants; values of enumerated types; the special constants True, False, and nil; and expressions built exclusively from these elements with operators, typecasts, and set constructors. Constant expressions cannot include variables, pointers, or function calls, except calls to the following predefined functions:
This definition of a constant expression is used in several places in Object Pascal's syntax specification. Constant expressions are required for initializing global variables, defining subrange types, specifying default parameter values, writing case statements, and declaring both true and typed constants.
Examples of constant expressions:
100
'A'
256 - 1
(2.5 + 1) / (2.5 - 1)
'Borland' + ' ' + 'Delphi'
Chr(32)
Ord('Z') - Ord('A') + 1
Resource strings are stored as resources and linked into the executable or library so that they can be modified without recompiling the program. For more information, see the online Help topics on localizing applications.
Resource strings are declared like other true constants, except that the word const is replaced by resourcestring. The expression to the right of the = symbol must be a constant expression and must return a string value. For example,
resourcestring
CreateError = 'Cannot create file %s'; { for explanations of format specifiers, }
OpenError = 'Cannot open file %s'; { see 'Format strings' in the online Help }
LineTooLong = 'Line too long';
ProductName = 'Borland Delphi\000\000';
SomeResourceString = SomeTrueConstant;
The compiler automatically resolves naming conflicts among resource strings in different libraries.
Typed constants, unlike true constants, can hold values of array, record, procedural, and pointer types. Typed constants cannot occur in constant expressions.
In the default {$J+} compiler state, typed constants can have new values assigned to them; they behave essentially like initialized variables. But if the {$J-} compiler directive is in effect, typed constants cannot change value at runtime; they are, in effect, read-only variables.
Declare a typed constant like this:
const identifier: type = value
where identifier is any valid identifier, type is any type except files and variants, and value is an expression of type type. For example,
const Max: Integer = 100;
In most cases, value must be a constant expression; but if type is an array, record, procedural, or pointer type, special rules apply.
To declare an array constant, enclose the values of the array's elements, separated by commas, in parentheses at the end of the declaration. These values must be represented by constant expressions. For example,
const Digits: array[0..9] of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
declares a typed constant called Digits that holds an array of characters.
Zero-based character arrays often represent null-terminated strings, and for this reason string constants can be used to initialize character arrays. So the declaration above can be more conveniently represented as
const Digits: array[0..9] of Char = '0123456789';
To define a multidimensional array constant, enclose the values of each dimension in a separate set of parentheses, separated by commas. For example,
type TCube = array[0..1, 0..1, 0..1] of Integer; const Maze: TCube = (((0, 1), (2, 3)), ((4, 5), (6,7)));
creates an array called Maze where
Maze[0,0,0] = 0
Maze[0,0,1] = 1
Maze[0,1,0] = 2
Maze[0,1,1] = 3
Maze[1,0,0] = 4
Maze[1,0,1] = 5
Maze[1,1,0] = 6
Maze[1,1,1] = 7
Array constants cannot contain file-type values at any level.
To declare a record constant, specify the value of each field--as fieldName: value, with the field assignments separated by semicolons--in parentheses at the end of the declaration. The values must be represented by constant expressions. The fields must be listed in the order in which they appear in the record type declaration, and the tag field, if there is one, must have a value specified; if the record has a variant part, only the variant selected by the tag field can be assigned values.
type
TPoint = record
X, Y: Single;
end;
TVector = array[0..1] of TPoint;
TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
TDate = record
D: 1..31;
M: TMonth;
Y: 1900..1999;
end;
const
Origin: TPoint = (X: 0.0; Y: 0.0);
Line: TVector = ((X: -3.1; Y: 1.5), (X: 5.8; Y: 3.0));
SomeDay: TDate = (D: 2; M: Dec; Y: 1960);
Record constants cannot contain file-type values at any level.
To declare a procedural constant, specify the name of a function or procedure that is compatible with the declared type of the constant. For example,
function Calc(X, Y: Integer): Integer; begin ... end; type TFunction = function(X, Y: Integer): Integer; const MyFunction: TFunction = Calc;
Given these declarations, you can use the procedural constant MyFunction in a function call:
I := MyFunction(5, 7)
You can also assign the value nil to a procedural constant.
When you declare a pointer constant, you must initialize it to a value that can be resolved--at least as a relative address--at compile time. There are three ways to do this: with the @ operator, with nil, and (if the constant is of type PChar) with a string literal. For example, if I is a global variable of type Integer, you can declare a constant like
const PI: ^Integer = @I;
The compiler can resolve this because global variables are part of the code segment. So are functions and global constants:
const PF: Pointer = @MyFunction;
Because string literals are allocated as global constants, you can initialize a PChar constant with a string literal:
const WarningStr: PChar = 'Warning!';
Addresses of local (stack-allocated) and dynamic (heap-allocated) variables cannot be assigned to pointer constants.
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.
Otros Links de Interés:
Cocina - Videos - Juegos Gratis - Postales cachondas - Cine - Programas Gratis - Letras de Canciones
Listas de todos los Tutoriales Gratis. 1998- 2007 - -
Los
tutoriales y cursos aquí reunidos son una recopilación de los mejores encontrados en
Internet.
El crédito y copyright de los mismos si lo hubiere corresponde al autor de cada
uno de ellos.
Si tu tutorial o curso está aquí, y deseas darlo de baja de esta recopilación o
quieres añadir el tuyo,
envíanos un mensaje desde
aquí