This chapter introduces some of the types available in Ada as well as the operations available on and attributes of those types.
All types Elementary Scalar Discrete Universal_integer -- all integer literals
Root_integer -- Ada95 only Signed integer Modular integer -- Ada95 nsigned types Enumeration User defined Character Boolean Real Universal_real -- all real literals Root_real -- Ada95 only Floating point Fixed point
ordinary fixed point
decimal fixed point -- Ada 95 only Access
Access-to-object
Access-to-subprogram -- Ada 95 only
Composite
Array
String
Other array
Untagged record
Tagged record -- Ada95
Task
Protected -- Ada95
The predefined package Standard contains declarations for the standard types such as integer, float, character and boolean, as well as (notionally) defining the operations available on them.
All numeric literals belong to the class universal_integer or universal_float. Many of the attributes of the language (discussed later) also return a universal value. These universal types are compatible with any corresponding integer, float or fixed type.
E.g.
Max_Customers : constant := 10_000; -- 10_000 is a "universal integer"
-- It is not of type "integer"
Subtypes can be created in Ada by restricting an existing type, by defining a new type based on an existing type or by enumerating the possible values of the type. A discussion of how to create these new types follows a look at the predefined types and their attributes.
The following operations are defined for all scalar types.
=, /= Equality, inequality
<, <=, >, >=
in, not in Range membership text
Count : Integer;
X,Y,Z : Integer;
Amount : Integer := 0;
Unity : constant Integer := 1;
Speed_Of_Light : constant := 300_000; -- type universal_integer
A_Month : Integer range 1..12;
subtype Months is Integer range 1..12; -- a restricted integer
-- subtypes are compatable with their base type (here integer) -- i.e. variables of type month can be mixed with integer variables
type File_Id is new Integer; -- a new integer family derived
-- from type integer;
type result_range is new Integer range 1..20_000;
-- a derived type with a constraint
type other_result_range is range 1..100_000;
-- a type derived from root_integer
-- the compiler chooses an appropriate sized integer to suit the range.
The following operators are also defined for all integer types.
+,-,*,/
** Exponentiation (integer exponent only)
mod Modulus
rem Remainder
abs Absolute value
The following are examples of floating point declarations. Floating point numbers have a relative error.
x : float; a,b,c : float; pi : constant float := 3.14_2; Avogadro : constant := 6.027E23; -- type universal_float subtype temperatures is float range 0.0..100.0; type result is new float range 0.0..20_000.0;
type Velocity is new Float; type Height is new Float; -- can't accidentally mix velocities and heights without an explicit -- type conversion. type Time is digits 6 range 0.0..10_000.0; -- six decimal digits of accuracy required, in this range. type Degrees is digits 2 range -20.00..100.00; -- two decimal digits of accuracy required.
The following operators are also defined for all float types.
+,*,/,- ** Exponentiation (integer exponent only) abs Absolute value
type Volt is delta 0.125 range 0.0 .. 255.0;
type Fraction is delta System.Fine_Delta range -1.0..1.0; -- Ada95
-- Fraction'last = 1.0 - System.Fine_Delta
type Money is delta 0.01 digits 15; -- decimal fixed point
subtype Salary is Money digits 10;
The last example shows the usefulness of fixed point types - the ability to specify exactly how accurate the type should be. This allows control over facilities such as errors in rounding expressions, for example.
An enumeration type is defined by listing all the possible values of the type.
type Computer_Language is (Assembler, Cobol, Lisp, Pascal, Ada); type C_Letter_Languages is (Cobol, C);
Values of this type can be defined as follows:
a_language : computer_language; early_language : computer_language := cobol; first_language : constant computer_language := assembler; example : c_letter_language := cobol;
Note that Ada can distinguish between enumeration literals from different types in most cases by examining the context. If this is not possible then type qualification must be used.
Enumeration types are useful to encode simple control codes used internally in a program.
There are two predefined enumerated types in the package STANDARD, the type character and the type boolean.
The two values of boolean variables is true and false.
The following opeartors can be used with boolean types
and or not xor /= = 'and then' 'or else'
Ada will not allow an unparenthesied expression to contain both and's and or's. This decreases the likelihood of misreading the intent of a complicated boolean expression.
E.g.
(a < b) and (b > c) or (d < e) -- illegal ((a < b) and (b > c)) or (d < e) -- ok
Usually when evaluating a boolean expression, the compiler is free to rearrange the evaluation of the terms as it sees fit. Both terms will be evaluated. For example in the following either term may be evaluated first.
if a < b and c > d then ...
However in some instances we wish to evaluate the terms in a defined order, and stop evaluations as soon as the value of the expression can be determined.
For example
if a /= 0 and then b/a > 5.0 then . . .
Here we see if a is non zero before further evaluation.
The 'or else' statement is similar, only evaluation stops as soon as a term evaluates to true. This can be useful, for example, in a recursive search of a tree.
E.g.
return Present(Node.Left, Key) or else Present(Node.Right, Key);
Ada83 initially had 7 bit characters. This restriction was eased before Ada95 arrived, but is still enforced by older compilers such as the Meridian Ada compiler. This creates problems when attempting to display graphic characters on a PC; generally you have to use integers to display characters above Ascii 127, using special routines supplied by the compiler vendor.
Ada95's Character type is based on Latin-1 and provides for 256 character
positions. Ada95 also supports wide characters (ISO 10646 Basic Multilingual
Plane (BMP)) and so all modern compilers can cope with 8 bit characters.
The 7 bit character set is described in the obsolecent package Standard.Ascii. The 8 bit character set is described in the package Standard. The package Ada.Characters.Latin_1 provides usable names for the characters.
subtype Name is Base_Type; subtype Name is Base_Type range lowerbound . . upperbound;
Examples of declaring subtypes are given below.
type Processors is (M68000, i8086, i80386, M68030, Pentium, PowerPC);
subtype Old_Processors is Processors range M68000..i8086;
subtype New_Processors is Processors range Pentium..PowerPC;
subtype Data is Integer;
subtype Age is Data range 0 . . 140;
subtype Temperatures is Float range -50.0 .. 200.0;
subtype Upper_Chars is Character range 'A' .. 'Z';
Subtypes are compatable with their base types . They can be placed in the same place as any variable of the base type can. Also variables of different subtypes that are derived from the same base type are compatable.
My_Age : Age; Height : Integer; Height := My_Age; -- silly, but never causes a problem. My_Age := Height; -- will cause a problem if height's -- value is outside the range of -- my_age (0..140), but still -- compilable.
When subtypes are created they are still compatable with their base type. Sometimes we may wish to create distinctly new types that are not associated with the original type at all. This concept of type is very different to that provided by Pascal.
To do this we create a derived type from a parent type using the following syntax
type Name is new Parent_Type; type Name is new Parent_Type range lower bound . . upper bound;
A derived type is a completely new type and is incompatable with any other type, even those derived from the same parent type.
Derived types should be used when the modelling of a particular object suggests that the parent type is inappropriate, or you wish to partition the objects into distinct and unmixable classes.
type Employee_No is new Integer; type Account_No is new Integer range 0..999_999;
Here employee_nos and account_nos are distinct and unmixable, they cannot be combined together without using explicit type conversion. Derived types inherit any operation defined on the base type. For example if a record was declared that had procedures push and pop, a derived type could be declared that would automatically have inherit the procedures.
Another important use of derived types is to produce portable code. Ada allows us to create a new level of abstraction, one level higher than, for example, the abstraction of Integer over a series of bits.
This is specified by using derived types, without a parent type.
type Name is range <some range>;
For example,
type Data is range 0..2_000_000;
Here the compiler is responsible for choosing an appropriately sized integer type. On a PC, it would be a 32 bit size, equivalent to long_integer. On a Unix workstation it would still be a 32 bit integer, but this would be equivalent to an integer. Letting the compiler choose frees the programmer from having to choose. Compiling it on a new host does not require changing the source code.
Despite the usefullness of being able to create distinct types, there are still occasions where we wish to convert from one type to another. One typical instance is to convert from one integer to float, or vice versa.
X : Integer:= 4; Y : Float; Y := float(X); . . . X := Integer(Y);
This causes the compiler to insert the appropriate code for type conversion (if needed) as part of the translation.
Do not confuse this with unchecked conversions (covered later) which often perform no internal representation transformation.
It needs to be stressed however that types are created distinct for a reason and that attempts to subvert the compiler's checks by performing type conversions should be either discouraged or performed only when semantically meaningfull.
In some situations an expression's or value's type can be ambiguous.
For example,
type primary is (red, green, blue); type rainbow is (red, yellow, green, blue, violet); ... for i in red..blue loop -- this is ambiguous
Here we need to specify precisely what type is required. This is done with type qualification.
for i in rainbow'(red)..rainbow'(blue) loop for i in rainbow'(red)..blue loop -- only one qualification needed for i in primary'(red)..blue loop
Type qualification does not change a value's type. It merely informs the compiler of what type the programmer thinks it should be.
Ada also provides the ability to enquire about a type or object from within the code by using attributes. Some of the attributes for discrete types are
Integer'first -- the smallest Integer
Integer'last -- the largest integer
Processors'succ(M68000) -- successor of the M68000
Upper_Chars'pred('C') -- the predecessor of 'C' ('B')
Integer'image(67) -- the string " 67" -- space for a '-'
Integer'value("67") -- the integer 67.
Processors'pos(M68030) -- the position of M68030 in the type.
-- (3, position 0 is first).
An example of the use of an attribute is
subtype Positive is Integer range 1..Integer'last;
Here we achieve a maximal positive integer range without introducing any system dependent features.
In Ada83 non discrete types such as float, fixed and all their subtypes and derived types, the concepts of pred, succ and pos do not have meaning. In Ada95 they do. All other scalar attributes apply to the real types.