One of the most important features of Ada is that it permits the declaration of data types. Many of these data types will be discussed in later chapters. In this section we will discuss the declaration of subtypes. Recall that a type is a set of values and a set of operations appropriate for those values. A subtype defines a subset of the values associated with the original type, or base type; the operations of the subtype are the same as those of the base type.
A scalar type is one each of whose values consists of a single
component. All the types we have seen so far, except for strings, are in this
category. Composite types, whose values may consist of several
components, are introduced in Chapter 8. In this
section we shall consider how
to create subranges of the predefined scalar types Integer
,
Float
, and Character
and of programmer-defined
enumeration types like the names of the days in the week. Subtypes are used
both to make a program more readable and to enable detection of an attempt to
give a variable a value that is unreasonable in the problem environment.
So far in this book we have used a number of different subtypes. The first two are predefined in the Ada language and are thus always available:
SUBTYPE Natural IS Integer RANGE 0..Integer'Last; SUBTYPE Positive IS Integer RANGE 1..Integer'Last;
The
three subtypes introduced in Section 3.6, are predefined, not in the language
itself, but in the standard package Ada.Calendar
, and are
therefore available to any program with a context clause WITH
Ada.Calendar
:
SUBTYPE Year_Number IS Integer RANGE 1901..2099; SUBTYPE Month_Number IS Integer RANGE 1..12; SUBTYPE Day_Number IS Integer RANGE 1..31;
In Section 2.6 we introduced a programmer-defined subtype,
SUBTYPE NonNegFloat IS Float RANGE 0.0 .. Float'Last;
Finally, we presented the subtypes Width
and
Depth
in Section 3.7.
SUBTYPE Depth IS Integer RANGE 1..Screen_Depth; SUBTYPE Width IS Integer RANGE 1..Screen_Width;
All these subtypes (and subtypes in general) have a common
characteristic. An attempt to assign to a variable a value that is not in the
defined set of values causes a compilation error or warning, if the compiler
can detect the attempt. If the compiler cannot detect the attempt--for example,
because the out-of-range value is not computed until the program is
executed--the compiler builds a check into your program to ensure that
Constraint_Error
is raised if, during execution, the value is
indeed out of range.
Example 5.7
Subtype declarations begin with the reserved word
SUBTYPE
. Two subtypes are declared below, as well as some
variables.
SUBTYPE SmallInt IS Integer RANGE -50..50; SUBTYPE CapitalLetter IS Character RANGE 'A'..'Z';X, Y, Z : SmallInt; NextChar : CapitalLetter; InDay : Calendar.Month_Number; Hours_Worked : NonNegFloat;
The first subtype, SmallInt
, is a subtype with
base type Integer
. The following sequence of assignment statements
will cause Constraint_Error
to be raised at run time:
X := 26; Y := 25; Z := X + Y;
Why is there no compilation error? Remember that the compiler does not actually carry out the computation you specify; it only produces an object program, which carries out the computation when it is executed. Even though it may be obvious to you that this simple computation will produce an out-of-range result, it is not obvious to the compiler, so the checking can be done, and the exception raised, only at run time.
CapitalLetter
has the base type Character
. Any
character from A
to Z
inclusive may be stored in a
variable of type CapitalLetter
. Constraint_Error
will
be raised if an attempt is made to store any other character in a variable of
type CapitalLetter
. For example, the assignment statement
NextChar := 'a';causes the exception to be raised because the character
'a'
is not included in the subtype CapitalLetter
. The
compiler might notice this attempted out-of-range assignment, but instead of
considering this an outright error, it will often give a warning stating that
the statement will cause Constraint_Error
to be raised at run time.
Month_Number
is a subtype with base type Integer
.
A variable of type Month_Number
may be used to keep track of the
current date, a value between 1 and 31 inclusive. The statement
Ada.Integer_Text_IO.Get(Item => InDay);reads a data value into
InDay
.
Constraint_Error
is raised if the data value entered from the
keyboard is less than 1 or greater than 31. This is clearly a situation where
the compiler cannot guess whether the value entered from the keyboard will be
in range or not.
Subtypes of programmer-defined types can be defined just as easily as subtypes of predefined types. As an example, consider the month-name type introduced in Section 3.6:
TYPE Months IS (January, February, March, April, May, June, July, August, September, October, November, December);
Now we can define subtypes for three seasons as follows:
SUBTYPE Spring IS Months RANGE March .. May; SUBTYPE Summer IS Months RANGE June .. August; SUBTYPE Autumn IS Months RANGE September .. November;
We cannot easily define a subtype Winter
(the
months December
, January
, and February
)
because unfortunately Ada requires that the values of a subtype be specified in
the form of a range and therefore contiguous, that is, adjacent in the
base type definition. Sometimes a way can be found to work around this, as in
the case of the day-names type introduced in Section 4.6:
TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
Since Mon
through Fri
are contiguous,
and Sat
and Sun
are contiguous, we can define
subtypes for weekdays and weekend days:
SUBTYPE Weekdays IS Days RANGE Mon .. Fri; SUBTYPE Weekend IS Days RANGE Sat .. Sun;
However, this work-around requires the Days
type
to look different from the "normal" American calendar in which the week starts
on Sunday.
SYNTAX DISPLAY
Subtype Declaration
SUBTYPE subtype-name IS base-type-name RANGE minvalue .. maxvalue;
SUBTYPE Uppercase IS Character RANGE 'A'..'Z';
PROGRAM STYLE
Motivation for Using Subtypes
Constraint_Error
, to be
raised. This should happen only as the result of an error by either the
programmer or the program user.
The use of subtypes ensures the immediate detection of an out-of-range value. This contributes to a program's reliability and usefulness because it ensures that variables do not acquire values that are meaningless in the problem being solved (such as a negative number of hours worked in a week).
In this book we use subtypes extensively, especially where it is necessary that a variable be nonnegative.
Ada does not allow a programmer accidentally to mix the types of
operands for an operator. This means that the expression V1 + V2
leads to a compilation error such as "type incompatible operands" if
V1
is one data type (say Integer
) and V2
is another (say Float
). However, what if V1
is type
Integer
and V2
is type SmallInt
(a
subtype of Integer
)? In this case, the expression is valid because
SmallInt
and Integer
are considered compatible
types. Ada has simple compatibility rules: Two values are compatible if
they have the same type name or one value's type is a subtype of the other
value's type (Integer
and SmallInt
, for example) or
if their types are subtypes of the same base type (Positive
and
SmallInt
, for example).
The compatibility relationship between operands determines what operators can be used with the operands. An operator can be used only with operands that are compatible with it and with each other. Assignment of a value to a variable is possible only if the value and the variable are compatible. And an actual parameter supplied to a function or procedure must be compatible with the corresponding formal parameter.
These rules ensure, for example, that a Float
value is not
assigned to an Integer
variable, that an Integer
value is not assigned to a Float
variable, and that an
Integer
value is not supplied to Ada.Text_IO.Put
(which expects a character). On the other hand, a Positive
value
can be supplied to Ada.Integer_Text_IO.Put
(which expects an
Integer
) because of the subtype relationship.
An important operator that applies to almost all types in Ada is
IN
. It can be used to determine whether a given value is a member
of a given type's set of values.
Example 5.8
Suppose Today
is of type Days
, and that we
have defined the two subtypes Weekdays
and Weekend
as
above. The following IF
statement serves as an example of the use
of IN
:
IF Today IN Weekdays THEN Ada.Text_IO.Put(Item => "Another day, another dollar..."); ELSE Ada.Text_IO.Put(Item => "We've worked hard, let's play hard!"); END IF;
Program 5.13 can be used to determine
whether we need to go to work tomorrow. It is
based on
Program
4.4. Notice the use of the IF
statement shown above.
Program 5.13
WITH Ada.Text_IO; PROCEDURE Work_Days IS ------------------------------------------------------------------------ --| Demonstrates the use of enumeration subtypes: --| prompts user for a day of the week and determines whether --| the following day is a weekday or weekend day. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun); SUBTYPE WeekDays IS Days RANGE Mon .. Fri; SUBTYPE WeekEnd IS Days RANGE Sat .. Sun; PACKAGE Day_IO IS NEW Ada.Text_IO.Enumeration_IO (Enum => Days); Today : Days; Tomorrow : Days; BEGIN -- Work_Days -- prompt user to enter a day abbreviation Ada.Text_IO.Put (Item => "Enter a 3-letter day of the week > "); Day_IO.Get (Item => Today); -- find tomorrow IF Today = Days'Last THEN Tomorrow := Days'First; ELSE Tomorrow := Days'Succ(Today); END IF; Ada.Text_IO.Put (Item => "Tomorrow is "); Day_IO.Put (Item => Tomorrow); Ada.Text_IO.New_Line; -- Is Tomorrow a week day or a weekend day? IF Tomorrow IN Weekdays THEN Ada.Text_IO.Put (Item => "Another day, another dollar..."); Ada.Text_IO.New_Line; ELSE Ada.Text_IO.Put (Item => "We've worked hard, let's play hard!"); Ada.Text_IO.New_Line; END IF; Ada.Text_IO.Put (Item => "Have a good day tomorrow."); Ada.Text_IO.New_Line; END Work_Days;Sample Run
Enter a 3-letter day of the week > fri Tomorrow is SAT We've worked hard, let's play hard! Have a good day tomorrow.
As
you have seen in this chapter, another use for IN
is in counting
loops. So far, you have seen only loops whose range is
1..repetitions
. Another useful form of the counting loops is to
give the name of a type or subtype as the range of the loop. Suppose that
SmallInt
is defined as above, with a range -50..50
,
then
FOR Counter IN SmallInt LOOP Ada.Integer_Text_IO.Put(Item => Counter); Ada.Text_IO.New_Line; END LOOP;
displays all the values in the type SmallInt
(-50,
-49, -48...), one at a time.
Example 5.9
Program
5.14 displays the addition table for integer values between 0 and 9 (type
SmallNat
). For example, the table line beginning with the digit 9
shows the result of adding to 9 each of the digits 0 through 9. The initial
FOR
loop prints the table heading, which is the operator + and the
list of digits from 0 through 9.
The nested FOR
loops are used to print the table body. The
outer FOR
loop (loop counter Left
) first prints the
current value of Left
. In the inner FOR
loop, each
value of Right
(0 through 9) is added to Left
and the
individual sums are printed. Each time the outer loop is repeated 10 additions
are performed; a total of 100 sums are printed.
Program 5.14
WITH Ada.Text_IO; WITH Ada.Integer_Text_IO; PROCEDURE Addition_Table IS ------------------------------------------------------------------------ --| Displays an addition table. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ MaxDigit : CONSTANT Natural := 9; -- largest digit SUBTYPE SmallNatural IS Natural RANGE 0 .. MaxDigit; BEGIN -- Addition_Table -- Display the table heading. Ada.Text_IO.Put(Item => "+"); FOR Right IN SmallNatural LOOP -- Display heading Ada.Integer_Text_IO.Put(Item => Right, Width => 3); END LOOP; Ada.Text_IO.New_Line; -- Terminate heading -- Display the table body. FOR Left IN SmallNatural LOOP -- Display each row of the table Ada.Integer_Text_IO.Put(Item => Left, Width => 1); FOR Right IN SmallNatural LOOP Ada.Integer_Text_IO.Put(Item => Left + Right, Width => 3); END LOOP; Ada.Text_IO.New_Line; -- Terminate table row END LOOP; END Addition_Table;Sample Run
+ 0 1 2 3 4 5 6 7 8 9 0 0 1 2 3 4 5 6 7 8 9 1 1 2 3 4 5 6 7 8 9 10 2 2 3 4 5 6 7 8 9 10 11 3 3 4 5 6 7 8 9 10 11 12 4 4 5 6 7 8 9 10 11 12 13 5 5 6 7 8 9 10 11 12 13 14 6 6 7 8 9 10 11 12 13 14 15 7 7 8 9 10 11 12 13 14 15 16 8 8 9 10 11 12 13 14 15 16 17 9 9 10 11 12 13 14 15 16 17 18
Example 5.10
Program
5.15 shows how this structure can be used to print all the days, weekdays,
and weekend days in the week. This program uses three FOR
loops,
one for the base type Days
and one for each of the two subtypes.
Program 5.15
WITH Ada.Text_IO; PROCEDURE Display_Days IS ------------------------------------------------------------------------ --| Display the days of the week, weekdays, weekend days; --| demonstrate enumeration subtypes and how they can be used --| to control a loop --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun); SUBTYPE WeekDays IS Days RANGE Mon .. Fri; SUBTYPE WeekEnd IS Days RANGE Sat .. Sun; PACKAGE Day_IO IS NEW Ada.Text_IO.Enumeration_IO (Enum => Days); BEGIN -- Display_Days Ada.Text_IO.Put (Item => "The days of the week are "); FOR Day IN Days LOOP Day_IO.Put (Item => Day, Width => 4); END LOOP; Ada.Text_IO.New_Line; Ada.Text_IO.Put (Item => "The weekdays are "); FOR Day IN WeekDays LOOP Day_IO.Put (Item => Day, Width => 4); END LOOP; Ada.Text_IO.New_Line; Ada.Text_IO.Put (Item => "The weekend days are "); FOR Day IN WeekEnd LOOP Day_IO.Put (Item => Day, Width => 4); END LOOP; Ada.Text_IO.New_Line; END Display_Days;Sample Run
The days of the week are MON TUE WED THU FRI SAT SUN The weekdays are MON TUE WED THU FRI The weekend days are SAT SUN
Program 5.16
is a modification of Program 5.15, using
REVERSE
in the FOR
loop statement to show how the
days can be displayed in reverse order.
Program 5.16
WITH Ada.Text_IO; PROCEDURE Reverse_Display_Days IS ------------------------------------------------------------------------ --| display the days of the week, weekdays, weekend days; --| demonstrate enumeration subtypes and how they can be used --| to control a loop running in reverse. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ TYPE Days IS (Mon, Tue, Wed, Thu, Fri, Sat, Sun); SUBTYPE WeekDays IS Days RANGE Mon .. Fri; SUBTYPE WeekEnd IS Days RANGE Sat .. Sun; PACKAGE Day_IO IS NEW Ada.Text_IO.Enumeration_IO (Enum => Days); BEGIN -- Reverse_Display_Days Ada.Text_IO.Put (Item => "The days of the week are "); FOR Day IN REVERSE Days LOOP Day_IO.Put (Item => Day, Width => 4); END LOOP; Ada.Text_IO.New_Line; Ada.Text_IO.Put (Item => "The weekdays are "); FOR Day IN REVERSE WeekDays LOOP Day_IO.Put (Item => Day, Width => 4); END LOOP; Ada.Text_IO.New_Line; Ada.Text_IO.Put (Item => "The weekend days are "); FOR Day IN REVERSE WeekEnd LOOP Day_IO.Put (Item => Day, Width => 4); END LOOP; Ada.Text_IO.New_Line; END Reverse_Display_Days;Sample Run
The days of the week are SUN SAT FRI THU WED TUE MON The weekdays are FRI THU WED TUE MON The weekend days are SUN SATExample 5.11
Program
5.17 uses the
Screen
package from Chapter 3 to draw vertical and
horizontal lines on the screen,
dividing the screen into four quadrants. We repeat the subtype and constant
declarations from Screen
here, just to remind you:
Screen_Depth : CONSTANT Integer := 24; Screen_Width : CONSTANT Integer := 80; SUBTYPE Depth IS Integer RANGE 1..Screen_Depth; SUBTYPE Width IS Integer RANGE 1..Screen_Width;
The loop
FOR Count IN Screen.Width LOOP Screen.MoveCursor (Row => 12, Column => Count); Ada.Text_IO.Put (Item => '-'); Screen.MoveCursor (Row => 13, Column => (Screen.Screen_Width - Count) + 1); Ada.Text_IO.Put (Item => '-'); END LOOP;
draws the horizontal separator consisting of two lines of
hyphen characters on rows 12 and 13 of the screen. The parameters to the first
call of Screen.MoveCursor
move the cursor one position to the
right in each loop iteration; just to make the program more interesting, the
second call moves the cursor one position to the left each time.
Program 5.17
WITH Ada.Text_IO; WITH Screen; PROCEDURE Four_Pieces IS ------------------------------------------------------------------------ --| This program divides the screen into four pieces by drawing --| horizontal and vertical lines. The Screen package is used to --| position the cursor. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: July 1995 ------------------------------------------------------------------------ BEGIN -- Four_Pieces Screen.ClearScreen; FOR Count IN Screen.Depth LOOP Screen.MoveCursor (Row => Count, Column => 41); Ada.Text_IO.Put (Item => '|'); Screen.MoveCursor (Row => (Screen.Screen_Depth - Count) + 1, Column => 42); Ada.Text_IO.Put (Item => '|'); END LOOP; FOR Count IN Screen.Width LOOP Screen.MoveCursor (Row => 12, Column => Count); Ada.Text_IO.Put (Item => '-'); Screen.MoveCursor (Row => 13, Column => (Screen.Screen_Width - Count) + 1); Ada.Text_IO.Put (Item => '-'); END LOOP; Screen.MoveCursor (Row => 24, Column => 1); END Four_Pieces;
SYNTAX DISPLAY
Counting Loops (Type-Name Form)
FOR counter IN type-name LOOP statement sequence END LOOP; FOR counter IN REVERSE type-name LOOP statement sequence END LOOP;
FOR WhichDay IN Weekdays LOOP Day_IO.Put (Item => WhichDay); Ada.Text_IO.New_Line; END LOOP;
type-name'First
before the
first execution of statement sequence; counter is incremented to its
successor value after each execution of statement sequence; the last
execution of statement sequence occurs when counter is equal to
type-name'Last
.The value of counter must not
be changed within statement sequence. The variable counter is not
declared separately and has no existence outside the loop.
If REVERSE
is present, counter is initialized to
type-name'Last
and the iteration is done backward,
decrementing counter to its predecessor value after each execution of
statement sequence.
The FOR
statement is very powerful and useful, but it
has one important limitation: the loop counter is always either
incremented (by taking the successor) or decremented (by taking the
predecessor). The FOR
statement can therefore be used only to loop
through all the values of a given range. There is no way to count by 2s,
for example.
Ada provides two other loop statements, which can be used with arbitrary
loop control conditions, not just counting straight through the values of a
range. Specifically, we can use either the WHILE
loop or the
general loop structure, which will be presented in Chapter 6.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.