Saturday, August 18, 2012

AdaTutor - Records and Arrays (1)

Records

A record lets us group several declarations together and refer to them as one:

   type Month_Type is (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug,
                       Sep, Oct, Nov, Dec);
   subtype Day_Subtype is Integer range 1 .. 31;
   type Date is
    record
      Day   : Day_Subtype;   -- Naturally, Day_Subtype and
      Month : Month_Type;    -- Month_Type must be defined
      Year  : Integer;       -- before type Date is defined.
    end record;
   USA : Date;

In this example, USA has three parts (called "fields"): USA.Day, USA.Month, and USA.Year.  The fields of a record can be of any type, including other records.  Here USA.Day is of type Integer, USA.Month is of type Month_Type, and USA.Year is of type Integer.  The fields of USA may be referenced separately:

      USA.Day   := 4;
      USA.Month := Jul;
      USA.Year  := 1776;

However, USA can also be referenced as one object.  For example, we could have assigned all three fields in one statement: USA := (4, Jul, 1776);.  Here the object on the left is of type Date, and the object on the right is called an aggregate.  The aggregate fits the type Date because it contains an Integer, a Month_Type, and another Integer.  This aggregate is said to use positional notation because its three parts appear in the same order as the three parts of the record definition: first Day, then Month, then Year.

We can specify the parts of an aggregate in any order if we use named notation:USA := (Month => Jul, Day => 4, Year => 1776);.  (The symbol => is read "arrow" and may not contain a space.)  Using named notation often improves program readability, especially if the names of the fields are well chosen.

We can switch from positional to named notation in an aggregate.  But once we use named notation, the compiler loses track of position, so we can't switch back to positional.  For example, the following is legal:

      USA := (4, Year => 1776, Month => Jul);

But the following is illegal because positional notation can't follow named:

      USA := (4, Year => 1776, Jul); -- illegal

Record discriminants, record variants, and tagged records will be discussed in the section on More Records and Types.

Question

  procedure Record_Exercise is
    type Month_Type is (Jan, Feb, Mar, Apr, May, Jun,
                        Jul, Aug, Sep, Oct, Nov, Dec);
    subtype Day_Subtype is Integer range 1 .. 31;
    type Date is
      record
        Day   : Day_Subtype;
        Month : Month_Type;
        Year  : Integer;
      end record;
    D1, D2, D3 : Date;  -- 1
  begin
    D1       := (Month => Jan, Day => 22, Year => 1983);  -- 2
    D2.Day   := 22;
    D2.Month := 1;  -- 3
    D2.Year  := 1983;
    D3       := D2;  -- 4
  end Record_Exercise;
Which commented line in the above program is illegal?
1
2
3
4

Arrays

To declare an array in Ada, we specify the type and range of the subscript, followed by the type of the elements of the array.  The subscript can have any discrete type (integer or enumeration), and the elements of the array can be of any type at all, including records and other arrays.  There are three ways to declare an array in Ada.  Here are three examples of the most direct, but least flexible, way (types Rainbow_Color and Date must be defined earlier):

 A : array(Rainbow_Color range Orange .. Blue) of Date;
    -- A four-element array, each element of which is a record
    -- with three parts.  The allowable subscripts are Orange,
    -- Yellow, Green, and Blue.  Here A(Yellow) is of type Date,
    -- and A(Yellow).Year is of type Integer.
 B : array(Integer range -10 .. 10) of Integer;
    -- An array of 21 Integers, with Integer subscripts.
 C : array(0 .. 30) of Float;
    -- Here (0 .. 30) is understood to mean
    -- (Integer range 0 .. 30), and we have an array of 31
    -- Floats, with Integer subscripts.

A subscript can be an expression; if I is an Integer, we can write C(2*I). If a subscript is out-of-range (for example, A(Red) or C(-32)), the program will raise a Constraint_Error.

This direct method of declaring arrays is usually used to create single arrays for table lookup, etc., where there's no need to have several arrays of the same type.  A better way to declare an array is to specify a type name for the array itself. Then several objects can be declared to have that same type.  For example,

      type Vector100 is array(1 .. 100) of Float;
      type Vector300 is array(1 .. 300) of Float;
      D, E, F : Vector100;
      G, H    : Vector300;

Here D, E, and F are all of type Vector100, so we can write D := E; and assign the entire array with one statement.  Similarly, we can write G := H;, but not G := F;, because G and F have different types.

An even more flexible way to declare arrays is to leave the range of the subscript unspecified with the box symbol, <>, specifying the range when declaring the objects.  For example,

      type Vector is array(Integer range <>) of Float;
      D, E, F : Vector(1 .. 100);
      G, H    : Vector(1 .. 300);

There are two errors to avoid when declaring arrays in this way.  One is to declare a type with the box symbol for the range of the subscript, and then fail to specify the range when declaring a variable:

      type Vector is array(Integer range <>) of Float;
      D1 : Vector; -- illegal

This error is called unconstrained array.  Unconstrained arrays are legal in formal parameters ("dummy arguments") of procedures and functions, and a function can return an unconstrained array type.  (We'll learn about these things later.)  But an unconstrained array is illegal when declaring a variable, because the compiler needs to know the range of the subscript.

In Ada 95, we may constrain the variable array by initializing it:

      D1 : Vector := (2.3, 4.5, 4.0); -- legal in Ada 95 only

Here the compiler assumes the range of the subscript to be 1 .. 3. In Ada 83, however, we must specify the subscript range of a variable explicitly:

      D1 : Vector(1 .. 3) := (2.3, 4.5, 4.0); -- legal

In both Ada 83 and Ada 95, a constant array, which must be initialized, is automatically constrained.  Therefore, the following is legal in both Ada 83 and Ada 95, and the compiler will assume the subscript range to be 1 .. 3:

     D1 : constant Vector := (2.3, 4.5, 4.0); -- legal 

The other error to avoid when declaring arrays in this way is to specify the range of the subscript twice: once when declaring the type and once when declaring the object:

      type Vector100 is array(1 .. 100) of Float;
      D2 : Vector100(1 .. 100);  -- illegal

Even if the two ranges agree, this is illegal and is called doubly constrained array.

Arrays may be initialized and assigned with aggregates, and both positional and named notation may be used.  For example, arrays A and B are equal here:

    type Vector5 is array(1 .. 5) of Float;
    A : constant Vector5 := (2.0, 4.0, 8.0, 16.0, 32.0);
    B : constant Vector5 := (1 => 2.0, 2 => 4.0, 3 => 8.0,
                             4 => 16.0, 5 => 32.0);

The aggregate must fill the whole array, but the reserved word others is available.  Here's an array of 500 Float variables, all initialized to 0.0:

    type Vector500 is array(1 .. 500) of Float;
    V1 : Vector500 := (others => 0.0);

If others follows named notation, it's best to qualify the aggregate with the type name.  Here W(10) = 1.3, W(15) = -30.7, and the rest of the array is 0.0:

    W : Vector500 := Vector500'(10 => 1.3,  15 => -30.7,
                                others => 0.0);

Sometimes we must qualify an aggregate when others is used with named notation; at other times it's optional.  The rules ( Ada 95 RM section 4.3.3, paragraphs 10-15) are complicated.  It's easiest always to qualify an aggregate when others follows named notation, as shown above.

In array aggregates, multiple choices can be denoted with the vertical bar (|), shift-backslash on PC keyboards.  In this array, the elements with odd subscripts are True, while the elements with even subscripts are False:

    type Decade is array(1 .. 10) of Boolean;
    D1 : Decade;
    ...
    D1 := Decade'(1 | 3 | 5 | 7 | 9 => True,  others => False);

Here we assigned to D1 with an executable statement for variety; we could also have initialized D1 in the declarative region with the same aggregate.  Some people read the vertical bar as "and," others as "or."  One can say, "Elements 1, and 3, and 5, and 7, and 9 are True," or one can say, "If the subscript is 1, or 3, or 5, or 7, or 9, the array element is True."


Question

 with Ada.Text_IO, Ada.Integer_Text_IO;
 use Ada.Text_IO, Ada.Integer_Text_IO;
 procedure Array_Quiz is
   subtype Unaccented_Cap_Letter is Character range 'A' .. 'Z';
   type Set_Of_Unaccented_Letters is
      array(Unaccented_Cap_Letter) of Boolean;
   Vowels : Set_Of_Unaccented_Letters :=
       Set_Of_Unaccented_Letters'('A'|'E'|'I'|'O'|'U' => True,
                                  others => False);
   Letter : Unaccented_Cap_Letter;
 begin
   Letter := 'E';
   Put(Boolean'Pos(Vowels(Letter)));
 end Array_Quiz;
What will this program display?
  1. The program will display 1.
  2. The program will display 2.
  3. The program will display TRUE.
  4. The program will display E.

In an array declaration or an array type declaration, we may totally omit the range of the subscript (not even supplying a box), if the type or subtype of the subscript has a reasonable number of possible values.  (We did that in the declaration of type Set_Of_Unaccented_Letters in the last question.)  For example, in

  type Rainbow_Color is (Red, Orange, Yellow, Green, Blue, Indigo,
                         Violet);
  R : array(Rainbow_Color) of Float;

we have an array of seven Floats, because there are seven possible values of type Rainbow_Color.  Similarly,

    V : array(Character) of Boolean;
creates an array of 256 Booleans (128 in Ada 83).  However, J : array(Integer) of Float; is an attempt to declare a very large array indeed!  In a case like this, we can use a subtype in the subscript declaration.  The following creates an array of 31 Floats:
    subtype Day_Subtype is Integer range 1 .. 31;
    J : array(Day_Subtype) of Float;

The attributes 'First and 'Last, which we've seen with discrete types, can also be used with array types and with the array names themselves.  For example,

    type My_Vector is array(30 .. 33) of Integer;
    Mv : My_Vector;
    ...
    Mv(30) := 1000;
    Mv(31) := 20;
    Mv(32) := -70;
    Mv(33) := -500;

Here Mv'First and My_Vector'First are both 30.  Mv'Last and My_Vector'Last are both 33.  Note that 'First and 'Last refer to the subscripts, not to the values of the array elements.  Thus Mv'First is not 1000.  To obtain the value of the first element, we would use Mv'First as a subscriptMv(Mv'First) is 1000.

The attribute 'Range is an abbreviation for 'First .. 'Last.  It can be used with array types and array names, and (in Ada 95 only), with scalar types.  Thus Mv'Range and My_Vector'Range both mean 30 .. 33.  In Ada 95, we can write Integer'Range as an abbreviation for Integer'First .. Integer'Last.  The attribute 'Length is also available: Mv'Length and My_Vector'Length are both 4.

< prev   next >

No comments: