Ada Programming/Types/array
An array is a collection of elements which can be accessed by one or more index values. In Ada any definite type is allowed as element and any discrete type, i.e. Range, Modular or Enumeration, can be used as an index.
Declaring arrays
[edit | edit source]Ada's arrays are quite powerful and so there are quite a few syntax variations, which are presented below.
Basic syntax
[edit | edit source]The basic form of an Ada array is:
array
(Index_Range)of
Element_Type
where Index_Range is a range of values within a discrete index type, and Element_Type is a definite subtype. The array consists of one element of "Element_Type" for each possible value in the given range. If you for example want to count how often a specific letter appears inside a text, you could use:
type
Character_Counteris
array
(Character)of
Natural;
Very often, the index does not have semantic contents by itself, it is just used a means to identify elements for instance in a list. Thus, as a general advice, do not use negative indices in these cases. It is also a good style when using numeric indices, to define them starting in 1 instead of 0, since it is more intuitive for humans and avoids off-by-one errors.
There are, however, cases, where negative indices make sense. So use indices adapted to the problem at hand. Imagine you are a chemist doing some experiments depending on the temperature:
type
Temperatureis
range
-10 .. +40; -- Celsiustype
Experimentis
array
(Temperature )of
Something;
With known subrange
[edit | edit source]Often you don't need an array of all possible values of the index type. In this case you can subtype
your index type to the actually needed range.
subtype
Index_Sub_Typeis
Index_Typerange
First .. Lastarray
(Index_Sub_Type)of
Element_Type
Since this may involve a lot of typing and you may also run out of useful names for new subtypes, the array declaration allows for a shortcut:
array
(Index_Typerange
First .. Last)of
Element_Type
Since First and Last are expressions of Index_Type, a simpler form of the above is:
array
(First .. Last)of
Element_Type
Note that if First and Last are numeric literals, this implies the index type Integer.
If in the example above the character counter should only count upper case characters and discard all other characters, you can use the following array type:
type
Character_Counteris
array
(Characterrange
'A' .. 'Z')of
Natural;
With unknown subrange
[edit | edit source]Sometimes the range actually needed is not known until runtime or you need objects of different lengths. In some languages you would resort to pointers to element types. Not with Ada. Here we have the box '<>', which allows us to declare indefinite arrays:
array
(Index_Typerange
<>)of
Element_Type;
When you declare objects of such a type, the bounds must of course be given and the object is constrained to them.
The predefined type String is such a type. It is defined as
type
Stringis
array
(Positiverange
<>)of
Character;
You define objects of such an unconstrained type in several ways (the extrapolation to other arrays than String should be obvious):
Text : String (10 .. 20); Input: String := Read_from_some_file;
(These declarations additionally define anonymous subtypes of String.) In the first example, the range of indices is explicitly given. In the second example, the range is implicitly defined from the initial expression, which here could be via a function reading data from some file. Both objects are constrained to their ranges, i.e. they cannot grow nor shrink.
With aliased elements
[edit | edit source]If you come from C/C++, you are probably used to the fact that every element of an array has an address. The C/C++ standards actually demand that.
In Ada, this is not true. Consider the following array:
type
Day_Of_Monthis
range
1 .. 31;type
Day_Has_Appointmentis
array
(Day_Of_Month)of
Boolean;pragma
Pack (Day_Has_Appointment);
Since we have packed the array, the compiler will use as little storage as possible. And in most cases this will mean that 8 boolean values will fit into one byte.
So Ada knows about arrays where more than one element shares one address. So what if you need to address each single element. Just not using pragma Pack is not enough. If the CPU has very fast bit access, the compiler might pack the array without being told. You need to tell the compiler that you need to address each element via an access.
type
Day_Of_Monthis
range
1 .. 31;type
Day_Has_Appointmentis
array
(Day_Of_Month)of
aliased
Boolean;
Arrays with more than one dimension
[edit | edit source]Arrays can have more than one index. Consider the following 2-dimensional array:
type
Character_Displayis
array
(Positiverange
<>, Positiverange
<>)of
Character;
This type permits declaring rectangular arrays of characters. Example:
Magic_Square: constant
Character_Display :=
(('S', 'A', 'T', 'O', 'R'),
('A', 'R', 'E', 'P', 'O'),
('T', 'E', 'N', 'E', 'T'),
('O', 'P', 'E', 'R', 'A'),
('R', 'O', 'T', 'A', 'S'));
Or, stating some index values explicitly,
Magic_Square: constant
Character_Display(1 .. 5, 1 .. 5) :=
(1 => ('S', 'A', 'T', 'O', 'R'),
2 => ('A', 'R', 'E', 'P', 'O'),
3 => ('T', 'E', 'N', 'E', 'T'),
4 => ('O', 'P', 'E', 'R', 'A'),
5 => ('R', 'O', 'T', 'A', 'S'));
The index values of the second dimension, those indexing the characters in each row, are in 1 .. 5 here. By choosing a different second range, we could change these to be in 11 .. 15:
Magic_Square: constant
Character_Display(1 .. 5, 11 .. 15) :=
(1 => ('S', 'A', 'T', 'O', 'R'),
...
By adding more dimensions to an array type, we could have squares, cubes (or « bricks »), etc., of homogenous data items.
Finally, an array of characters is a string (see Ada Programming/Strings). Therefore, Magic_Square may simply be declared like this:
Magic_Square: constant
Character_Display :=
("SATOR",
"AREPO",
"TENET",
"OPERA",
"ROTAS");
Using arrays
[edit | edit source]Assignment
[edit | edit source]When accessing elements, the index is specified in parentheses. It is also possible to access slices in this way:
Vector_A (1 .. 3) := Vector_B (3 .. 5);
Note that the index range slides in this example: After the assignment, Vector_A (1) = Vector_B (3) and similarly for the other indices.
Also note that slice assignments are done in one go, not in a loop character by character, so that assignments with overlapping ranges work as expected:
Name: String (1 .. 13) := "Lady Ada "; Name (6 .. 13) := Name (1 .. 8);
The result is "Lady Lady Ada" (and not "Lady Lady Lad").
Slicing
[edit | edit source]As has been shown above, in slice assignments index ranges slide. Also subtype conversions make index ranges slide:
subtype
Str_1_8is
String (1 .. 8);
The result of Str_1_8 (Name (6 .. 13)) has the new bounds 1 and 8 and contents "Lady Ada" and is not a copy. This is the best way to change the bounds of an array or of parts thereof.
Concatenate
[edit | edit source]The operator "&" can be used to concatenate arrays:
Name := First_Name & ' ' & Last_Name;
In both cases, if the resulting array does not fit in the destination array, Constraint_Error is raised.
If you try to access an existing element by indexing outside the array bounds, Constraint_Error is raised (unless checks are suppressed).
Array Attributes
[edit | edit source]There are four Attributes which are important for arrays: 'First, 'Last, 'Length and 'Range. Lets look at them with an example. Say we have the following three strings:
Hello_World :constant
String := "Hello World!"; World :constant
String := Hello_World (7 .. 11); Empty_String :constant
String := "";
Then the four attributes will have the following values:
Array | 'First | 'Last | 'Length | 'Range |
---|---|---|---|---|
Hello_World | 1 | 12 | 12 | 1 .. 12 |
World | 7 | 11 | 5 | 7 .. 11 |
Empty_String | 1 | 0 | 0 | 1 .. 0 |
The example was chosen to show a few common beginner's mistakes:
- The assumption that strings begin with the index value 1 is wrong (cf. World'First = 7 on the second line).
- The assumption (which follows from the first one) that X'Length = X'Last is wrong.
- The assumption that X'Last >= X'First; this is not true for empty strings.
The index subtype of predefined type String is Positive, therefore excluding 0 or -17 etc. from the set of possible index values, by subtype constraint (of Positive). Also, 'A' or 2.17e+4 are excluded, since they are not of type Positive.
The attribute 'Range is a little special as it does not return a discrete value but an abstract description of the array. One might wonder what it is good for. The most common use is in the for loop on arrays. When looping over all elements of an array, you need not know the actual index range; by using the attribute, one of the most frequent errors, accessing elements out of the index range, can never occur:
for
Iin
World'Rangeloop
... World (I)...end
loop
;
'Range can also be used in declaring a name for the index subtype:
subtype
Hello_World_Indexis
Integerrange
Hello_World'Range;
The Range attribute can be convenient when programming index checks:
if
Kin
World'Rangethen
return
World(K);else
return
Substitute;end
if
;
Empty or Null Arrays
[edit | edit source]As you have seen in the section above, Ada allows for empty arrays. Any array whose last index value is lower than the first index value is empty. And — of course — you can have empty arrays of all sorts, not just String:
type
Some_Arrayis
array
(Positive range <>)of
Boolean; Empty_Some_Array :constant
Some_Array (1 .. 0) := (others
=> False); Also_Empty: Some_Array (42 .. 10);
Note: If you give an initial expression to an empty array (which is a must for a constant), the expression in the aggregate will of course not be evaluated since there are no elements actually stored.