Cha 04

Download as pdf or txt
Download as pdf or txt
You are on page 1of 8

02: Variables and Data Types - 53

note Oddly enough, Object Pascal does allow you to change the value of a typed constant at run-time,
as if it was a variable but only if you enable the $J compiler directive, or use the corresponding
Assignable typed constants compiler option. This optional behavior is included for backward
compatibility of code which was written with an old compiler. This is clearly not a suggested cod-
ing style, and I've covered it in this note most as a historical anecdote about such programming
techniques.

Resource String Constants


Although this is a slightly more advanced topic, when you define a string constant,
instead of writing a standard constant declaration you can use a specific directive,
resourcestring, that indicates to the compiler and linker to treat the string like a
Windows resource (or an equivalent data structure on non-Windows platforms
Object Pascal supports):
const
sAuthorName = 'Marco';

resourcestring
strAuthorName = 'Marco';

begin
ShowMessage (strAuthorname);
In both cases you are defining a constant; that is, a value you don't change during
program execution. The difference is only in the internal implementation. A string
constant defined with the resourcestring directive is stored in the resources of the
program, in a string table.
In short, the advantages of using resources are more efficient memory handling per-
formed by Windows, a corresponding implementation for other platforms, and a
better way of localizing a program (translating the strings to a different language)
without having to modify its source code. As a rule of thumb, you should use
resourcestring for any text that is shown to users and might need translating, and
internal constants for every other internal program string, like a fixed configuration
file name.

tip The IDE editor has an automatic refactoring you can use to replace a string constant in your code
with a corresponding resourcestring declaration. Place the edit cursor within a string literal and
press Ctrl+Shift+L to activate this refactoring.

Marco Cantù, Object Pascal Handbook


54 - 02: Variables and Data Types

Lifetime and Visibility of Variables


Depending on how you define a variable, it will use different memory locations and
remain available for a different amount of time (something generally called the vari-
able lifetime) and will be available in different portions of your code (a feature
referred to by the term visibility).
Now, we cannot have a complete description of all of the options so early in the
book, but we can certainly consider the most relevant cases:
● Global variables: If you declare a variable (or any other identifier) in the
interface portion of the unit, its scope extends to any other unit that uses the
one declaring it. The memory for this variable is allocated as soon as the pro-
gram starts and exists until it terminates. You can assign a default value to it
or use the initialization section of the unit in case the initial value is com-
puted in a more complex way.
● Global hidden variables: If you declare a variable in the implementation
portion of a unit, you cannot use it outside that unit, but you can use it in
any block of code and procedure defined within the unit, from the position of
the declaration onwards. Such a variable uses global memory and has the
same lifetime as the first group; the only difference is in its visibility. The ini-
tialization is the same as that of global variable.
● Local variables: If you declare a variable within the block defining a func-
tion, procedure, or method, you cannot use this variable outside that block
of code. The scope of the identifier spans the whole function or method,
including nested routines (unless an identifier with the same name in the
nested routine hides the outer definition). The memory for this variable is
allocated on the stack when the program executes the routine defining it. As
soon as the routine terminates, the memory on the stack is automatically
released.
Any declarations in the interface portion of a unit are accessible from any part of the
program that includes the unit in its uses clause. Variables of form classes are
declared in the same way, so that you can refer to a form (and its public fields, meth-
ods, properties, and components) from the code of any other form. Of course, it’s
poor programming practice to declare everything as global. Besides the obvious
memory consumption problems, using global variables makes a program harder to
maintain and update. In short, you should use the smallest possible number of
global variables.

Marco Cantù, Object Pascal Handbook


02: Variables and Data Types - 55

Data Types
In Pascal there are several predefined data types, which can be divided into three
groups: ordinal types, real types, and strings. We'll discuss ordinal and real types in
the following sections, while strings will be specifically covered in Chapter 6.
Delphi also includes a non-typed data type, called variant, and other “flexible”
types, such as TValue (part of the enhanced RTTI support). Some of these more
advanced data types will be discussed later in Chapter 5.

Ordinal and Numeric Types


Ordinal types are based on the concept of order or sequence. Not only can you com-
pare two values to see which is higher, but you can also ask for the next or previous
values of any value and compute the lowest and highest possible values the data type
can represent.
The three most important predefined ordinal types are Integer, Boolean, and Char
(character). However, there are other related types that have the same meaning but
a different internal representation and support a different range of values. The fol-
lowing table lists the ordinal data types used for representing numbers:
Size Signed Unsigned
8 bits ShortInt: -128 to 127 Byte: 0 to 255

SmallInt: -32768 to 32767 Word: 0 to 65,535


16 bits
(-32K to 32K) (0 to 64K)
Integer: -2,147,483,648 to Cardinal: 0 to 4,294,967,295
32 bits
2,147,483,647 (-2GB to +2GB) (0 to 4 GB)
Int64: UInt64: 0 to
64 bits -9,223,372,036,854,775,808 to 18,446,744,073,709,551,615
9,223,372,036,854,775,807 (if you can read it!)

As you can see, these types correspond to different representations of numbers,


depending on the number of bits used to express the value, and the presence or
absence of a sign bit. Signed values can be positive or negative, but have a smaller
range of values (half of the corresponding unsigned value), because one less bit is
available for storing the value itself.

Marco Cantù, Object Pascal Handbook


56 - 02: Variables and Data Types

The Int64 type represents integer numbers with up to 18 digits. This type is fully
supported by some of the ordinal type routines (such as High and Low), numeric rou-
tines (such as Inc and Dec), and string-conversion routines (such as IntToStr) of
the run time library.

Aliased Integral Types


If you have a hard time remembering the difference between a ShortInt and a
SmallInt (including which one is effectively smaller), rather than the actual type you
can use one of the predefined aliases declared in the System unit:
type
Int8 = ShortInt;
Int16 = SmallInt;
Int32 = Integer;
UInt8 = Byte;
UInt16 = Word;
UInt32 = Cardinal;
Again, these types don't add anything new, but are probably easier to use, as it is
simple to remember the actual implementation of an Int16 rather than that of a
SmallInt. These type aliases are also easier to use for developers coming from C and
other languages that use similar type names.

Integer Type, 64 Bit, NativeInt, and LargeInt


In 64-bit versions of Object Pascal you may be surprised to learn that the Integer
type is still 32 bit. It is so because this is the most efficient type for numeric process-
ing.
It is the Pointer type (more about pointers later on) and other related reference
types that are 64 bit. If you need a numeric type that adapts to the pointer size and
the native CPU platform, you can use the two special NativeInt and NativeUInt
aliased types. These are 32 bit on 32-bit platform and 64 bit on 64-bit platforms.
A slightly different scenario happens for the LargeInt type, which is often used to
map to native platform API functions. This is 32 bit on 32-bit platforms and on Win-
dows 32 bit, while it is 64 bit on 64-bit ARM platform. Better stay away from it
unless you need it specifically for native code in a way it adapts to the underlying
operating system.

Integer Types Helpers


While the Integer types are treated separately from objects in the Object Pascal lan-
guage, it is possible to operate on variables (and constant values) of these types with

Marco Cantù, Object Pascal Handbook


02: Variables and Data Types - 57

operations that you apply using “dot notation”. This is the notation generally used to
apply methods to objects.

note Technically these operations on native data types are defined using “intrinsic record helpers”.
Class and record helpers are covered in Chapter 12. In short, you can customize the operations
applicable to core data types. Expert developers can notice that type operations are defined as
class static methods in the matching intrinsic record helper.

You can see a couple of examples in the following code extracted from the Inte-
gersTest demo:
var
N: Integer;
begin
N := 10;
Show (N.ToString);

// display a constant
Show (33.ToString);

// type operation, show the bytes required to store the type


Show (Integer.Size.ToString);

note The Show function used in this code snippet is a simple procedure used to display some string out-
put in a memo control, to avoid having to close multiple ShowMessage dialogs. A side advantage is
this approach makes easier to copy the output and paste in the text (as I've done below). You'll see
this approach used through most of the demos of this book.

The output of the program is the following


10
33
4
Given these operations are very important (more than others that are part of the run
time library) it is worth listing them here:
ToString Convert to the number to a string, using a decimal format
ToBoolean Conversion to Boolean type
ToHexString Convert to a string, using a hexadecimal format
ToSingle Conversion to single floating point data type
ToDouble Conversion to double floating point data type
ToExtended Conversion to extended floating point data type
The first and third operations convert to the number to a string, using a decimal or
hexadecimal operation. The second is a conversion to Boolean, while the last three
are conversions to floating point types described later.

Marco Cantù, Object Pascal Handbook


58 - 02: Variables and Data Types

There are other operations you can apply to the Integer type (and most other
numerical types), such as:
Size The number of bytes required to store a variable of this type
Parse Convert a string to the numeric value it represents
TryParse Try to convert the string a a number

Standard Ordinal Types Routines


Beside the operations defined by Integer type helpers and listed above, there are
several standard and “classic” functions you can apply to any ordinal type (not just
the numeric ones). A classic example is asking for information about the type itself,
using the functions SizeOf, High, and Low. The result of the SizeOf system function
(that you can apply to any data type of the language) is an integer indicating the
number of bytes required to represent values of the given type (just like the Size
helper function shown above)
The system routines that work on ordinal types are shown in the following table:
Dec Decrements the variable passed as parameter, by one or by the value
of the optional second parameter
Inc Increments the variable passed as parameter, by one or by the speci-
fied value
Odd Returns True if the argument is an odd number. For testing for even
numbers, you should use a not expression (not Odd)
Pred Returns the value before the argument in the order determined by
the data type, the predecessor
Succ Returns the value after the argument, the successor
Ord Returns a number indicating the order of the argument within the
set of values of the data type (used for non-numerical ordinal types)
Low Returns the lowest value in the range of the ordinal type passed as
parameter
High Returns the highest value in the range of the ordinal data type

note C and C++ programmers should notice that the two versions of the Inc procedure, with one or
two parameters, correspond to the ++ and += operators (the same holds for the Dec procedure
which corresponds to the -- and -= operators). The Object Pascal compiler optimizes these incre-
ment and decrement operations, similarly to the way C and C++ compilers do.

Notice that some of these routines are automatically evaluated by the compiler and
replaced with their value. For example, if you call High(X) where X is defined as an
Integer, the compiler replaces the expression with the highest possible value of the
Integer data type.

Marco Cantù, Object Pascal Handbook


02: Variables and Data Types - 59

In the IntegersTest application project I've added an event with a few of these ordi-
nal type functions:
var
n: UInt16;
begin
n := Low (UInt16);
Inc (n);
Show (IntToStr (n));
Inc (n, 10);
Show (IntToStr (n));
if Odd (n) then
Show (IntToStr (n) + ' is odd');
This is the output you should see:
1
11
11 is odd
You can change the data type from Uint16 to Integer or other ordinal types to see
how the output changes.

Out-Of-Range Operations
A variable like n above has only a limited range of valid values. If the value you
assign to it is negative or too big, this results in an error. There are actually three dif-
ferent types of errors you can encounter with out-of-range operations.
The first type of error is a compiler error, which happens if you assign a constant
value (or a constant expression) that is out of range. For example, if you add to the
code above:
n := 100 + High (n);
the compiler will issue the error:
[dcc32 Error] E1012 Constant expression violates subrange bounds
The second scenario takes place when the compiler cannot anticipate the error con-
dition, because it depends on the program flow. Suppose we write (in the same piece
of code):
Inc (n, High (n));
Show (IntToStr (n));
The compiler won't trigger an error because there is a function call, and the compiler
doesn't know its effect in advance (and the error would also depend on the initial
value of n). In this case there are two possibilities. By default, if you compile and run
this application, you'll end up with a completely illogical value in the variable (in this
case the operation will result in subtracting 1!). This is the worst possible scenario,
as you get no error, but your program is not correct.

Marco Cantù, Object Pascal Handbook

You might also like