|
by Jim Rogers
This is part four (of five) of this article. Click here
to return to part three, or click here to return to the start of the article.
Ada provides three different looping constructs.
Simple Loop
The simplest loop in Ada is an unconditional loop.
loop
-- Loop body goes here
end loop;
This loop will continue forever, or until you
execute an exit command within the loop. In Ada, the exit
command terminates a loop. It does not terminate a program.
loop
if condition then
exit;
end if;
end loop;
This construct is so common that a shortened
version is available.
loop
exit when condition;
end loop;
The simple loop combined with an exit
statement is the most general form of looping in Ada, allowing
you to perform your test at the top, bottom, or middle of each
iteration.
While loop
The Ada while loop is like the while loop
in most languages. It is a conditional loop that performs the
condition test at the top of the loop.
while condition loop
-- Loop body goes here
end loop;
For Loop
The Ada for loop iterates through an
explicitly stated range of discrete values. As we discussed earlier,
the concept of a range is very important in Ada. A range is specified by the
notation lowest_value .. highest_value.
for variable in low_value .. high_value loop
-- Loop body goes here
end loop;
The name of a discrete type can be used in place
of the value. In that case the loop will iterate over all values
in the type, starting at the lowest value. Ranges always must
be expressed as low_value..high_value. They can never be
expressed in reverse order. You can iterate backwards through a
range by adding the reserved word reverse.
for variable in reverse low_value .. high_value loop
-- Loop body goes here
end loop;
The control variable used in a for loop is
only visible within the loop body. It is a read-only variable
within the loop body, meaning that you cannot explicitly assign a
value to the control variable. You do not declare the
control variable before you use it. The compiler determines the
data type for the range and creates the control variable to be an
instance of that type.
If Statements
The Ada if statement is fully blocked.
if condition then
-- statement(s)
end if;
if condition then
-- statement(s)
else
-- statement(s)
end if;
The condition must always evaluate to a Boolean
value (False or True).
I will compare this with the C if
statement.
In C the if statement controls a
conditional jump to the statement immediately following the if
statement.
if (condition)
/* statement */
In C the statement following the if may be
a simple statement or a compound statement. The condition must
evaluate to an integer. C has no boolean data type. C considers 0
to be false and any non-zero value to be true. The C if
statement presents several opportunities for coding traps. The
simplest problem occurs when you place a semicolon directly after
the condition:
if (condition);
/* statement */
This is always erroneous, but always legal C
code. The semicolon acts as a null statement for the if
condition. Execution will always flow to the statement following
the semicolon because that statement is not controlled by the if
statement. This problem never happens in Ada. If you put the
extra semicolon in Ada code the compiler will produce an error
message indicating incorrect syntax.
Another problem with the C if statement is
how it handles nested if statement with else
alternatives.
if (condition)
if(condition 2)
/* statement */
else
/*statement */
In this example the else is associated
with the nearest if , not with the outer if. This
mistake cannot be made with Ada.
Case Statements
The Ada case statement is used to make one
of many choices based upon an expression that evaluates to a
discrete type. The Ada case statement must either
explicitly deal with all possible values of the discrete type or
it must contain an Others clause.
case expression is
when choice list =>
sequence-of-statements
when choice list =>
sequence-of-statements
when others =>
sequence-of-statements
end case;
The Others clause corresponds to the default
option in a C switch statement. The Ada case statement does not
suffer from fall through like the C switch statement. There is no
need to explicitly break at the end of each case as must
be done in C to prevent fall through.
type Directions is (North, South, East, West);
Heading : Directions;
case Heading is
when North =>
Y := Y + 1;
when South =>
Y := Y - 1;
when East =>
X := X + 1;
when West =>
X := X - 1;
end case;
In this example the Others clause is not
used because all possible values of the Directions type
are explicitly handled.
Ada provides two kinds of subprograms, and makes
a strong distinction between the two.
Procedures never return a value through a
return statement, but may pass parameters out through
their parameter list. Procedure parameters must have a passing
mode. There are three passing modes: IN, OUT, and IN
OUT. IN parameters are treated as constants within a
subprogram. They may be read from but not written to. OUT
parameters have no reliable initial value upon entering the
procedure, but are expected to be written to. IN OUT
parameters have an initial value and may be written to. If no
mode is specified, the default mode is IN.
procedure Swap(Left, Right : IN OUT Integer) is
Temp : Integer := Left;
begin
Left := Right;
Right := Temp;
end Swap;
Functions always return a value (except
when the function raises an exception). Function parameters can
only have the IN mode.
function Count_Letters(Item : String) return Natural is
Count : Natural := 0;
begin
for I in Item'Range loop
if Is_Letter(Item(I)) then
Count := Count + 1;
end if;
end loop;
return Count;
end Count_Letters;
This function will count all the letters in any
size string passed to it. The for loop determines the
range to iterate through by inspecting the Range attribute
of the string. Remember that a string is merely an array of
characters.
Procedure parameter modes tell the compiler what
you want to do with the parameters. They also document for the
programmer what your intent is. Any parameter passed with an IN
mode can cause no side effects in the calling code block. Any
parameter passed with an OUT or IN OUT mode
can be expected to change.
Actual parameters to a subprogram may be passed
by position, as in C, or using named notation. It is even
possible to mix the notations, with some restrictions. In
general, most Ada programmers prefer to use named notation when
there are multiple parameters. The Put procedure that writes an
unsigned integer type to standard output is defined as:
procedure Put(Item : in Num;
Width : in Field := Default_Width;
Base : in Number_Base := Default_Base);
This procedure declares three formal parameters.
The last two have default values. This means that you do not need
to provide values for those parameters if you are happy with the
defaults. When calling this procedure using all defaults you
would use one of the following formats:
Put(Count);
Put(Item => Count);
Both procedure calls above accomplish the same
thing. The first one uses positional notation. The actual
parameter Count is passed into the formal parameter Item. This is
made explicit with the second call. Clearly, there is no great
advantage to using named notation in this instance. If, however,
you want to output your integer value in base 2 you must pass the
value 2 to the Base formal parameter. In this case named notation
is very useful.
Put(Item => Count, Base => 2);
Put(Base => 2, Item => Count);
Put(Count, Base => 2);
All three forms are allowed. The first form is
generally preferred. Note that the use of named notation allows
you to specify parameters in any order you choose. The last
example shows a mixture of named and positional notation. This
form has an important rule. All the positional parameters must be
passed before any named parameter. Named notation is useful for
code maintenance. It documents exactly what the original
programmer meant to do without requiring the maintenance
programmer to look up the subprogram definition to understand
which parameters were receiving which value.
The primary structure used for encapsulation in
Ada is called a package . Packages are used to group data
and subprograms. Packages can be hierarchical, allowing a
structured and extensible relationship for data and code. All the
standard Ada libraries are defined within packages.
Package definitions usually consist of two parts,
a package specification and a package body. The package
specification declares all the public and private components in a
package including data types, constants, functions, and
procedures. Ada's notion of private is actually closer to the C++
or Java notion of protected. Most package specifications require
a corresponding package body. The exception is a package defining
only constants. Package bodies contain the implementation of all
the procedures, functions, protected types, and task types
defined in a package specification. Package bodies can also
declare and define data types, variables, constants, functions,
and procedures not declared in the package specification.
Anything declared withing a package body is visible only to
functions, procedures, protected types, and task types defined in
the same package body. Variables and constants declared in a
package body correspond to private members of C++ or Java
classes.
I will repeat the code shown earlier about
operators. This code clearly demonstrates the use of a user
defined package.
The first part is the package specification for a
package named Operator_Example . This package defines two
data types and the procedures and functions associated with those
data types. No private data is defined in this package.
---------------------------------------------------------------
-- Ada operator examples
---------------------------------------------------------------
package Operator_Examples is
type Days is (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday);
procedure Print_Message(For_Day : in Days);
type Daily_Sales is array(Days) of Float;
function Total(Sales : in Daily_Sales) return Float;
function Geometric_Mean(Sales : in Daily_Sales)
return Float;
end Operator_Examples;
The second part is the package body for Operator_Example.
The functions and procedures defined within this package need to
call functions and procedures from other packages. The with
clause is used to identify which packages are needed. The use
clause is used to simplify the naming of those functions and
procedures from other packages. For instance, without the clause
use Ada.Text_Io;
The Print_Message procedure below would
need to be written as follows:
procedure Print_Message(For_Day : in Days) is
begin
if For_Day in Saturday..Sunday then
Ada.Text_Io.Put_Line("Go Fishing!");
else
Ada.Text_Io.Put_Line("Go To Work!");
end if;
end Print_Message;
With clauses must be placed before the
beginning of the package body or specification. They cannot be
scattered throughout the code. A use clause may be placed
in the same area as the with clause, or it may be placed
in the declarative region of a code block. If it is placed in the
declarative region of a code block its influence is restricted to
that code block.
with Ada.Text_Io; use Ada.Text_Io;
with Ada.Numerics.Elementary_Functions;
use Ada.Numerics.Elementary_Functions;
package body Operator_Examples is
procedure Print_Message(For_Day : in Days) is
begin
if For_Day in Saturday..Sunday then
Put_Line("Go Fishing!");
else
Put_Line("Go To Work!");
end if;
end Print_Message;
function Total(Sales : in Daily_Sales) return Float is
Sum : Float := 0.0;
begin
for I in Sales'range loop
Sum := Sum + Sales(I);
end loop;
return Sum;
end Total;
function Geometric_Mean(Sales : in Daily_Sales)
return Float is
Product : Float := 1.0;
begin
for I in Sales'range loop
Product := Product * Sales(I);
end loop;
return Product**(1.0 / Float(Sales'Length));
end Geometric_Mean;
end Operator_Examples;
The last part is not a package at all, but a
stand-alone procedure with no parameters. Ada requires you to
create a stand-alone procedure with no parameters as the starting
point of your program. This procedure needs to use types and
subprograms from several packages. The appropriate with
clauses are placed before the beginning of the procedure. Note
that the with clause does not work like the
preprocessor for C or C++. The contents of a package
specification are not copied into the code at the point of a with
clause. The C and C++ preprocessor will generate duplicate symbol
names if a file is included into the same source file multiple
times. Ada avoids that problem altogether.
---------------------------------------------------------------
-- The driver procedure to exercise the
-- Operator_Examples package.
---------------------------------------------------------------
with Ada.Text_Io; use Ada.Text_Io;
with Ada.Float_Text_Io; use Ada.Float_Text_Io;
with Operator_Examples; use Operator_Examples;
procedure Operator_Exerciser is
My_Sales : Daily_Sales;
begin
My_Sales(Monday) := 1234.56;
My_Sales(Tuesday) := 1342.65;
My_Sales(Wednesday) := 1432.55;
My_Sales(Thursday) := 1332.44;
My_Sales(Friday) := 2345.67;
My_Sales(Saturday) := 2222.00;
My_Sales(Sunday) := 1232.33;
for Day in Days loop
Put(Days'Image(Day) & ": ");
Print_Message(Day);
end loop;
Put("Total sales: ");
Put(Item => Total(My_Sales), Aft => 2, Exp => 0);
New_Line;
Put("Mean Sales: ");
Put(Item => Geometric_Mean(My_Sales), Aft => 3, Exp => 0);
New_Line;
end Operator_Exerciser;
You can create a composite type containing multiple fields, each
with possibly a different type, using a record. A record
is very much like a struct in C.
type Address is record
Street : Unbounded_String;
City : Unbounded_String;
Postal_Code : String(1..10);
end record;
My_Address : Address;
The Address type shown above has three
fields. The variable My_Address is an instance of the Address
type. Record fields are accessed using a simple dot (.) notation.
A.Street := To_Unbounded_String("1700 Pennsylvania Avenue");
A.City := To_Unbounded_String("Washington, D.C.");
A.Postal_Code := "00000-0000";
An access type corresponds to a Java
reference.
type Address_Ref is access Address;
A_Ref := new Address;
A_Ref.Street := To_Unbounded_String("17 Raven Road");
A_Ref.City := To_Unbounded_String("San Anselmo");
A_Ref.Postal_Code := "94960-1234";
Unlike C, there is no notational difference for
accessing a record field directly or through an access value. To
refer to the entire record accessed by an access value use the
following notation:
Print(A_Ref.all);
What? Ada Has No Class?
Ada provides tools and constructs for extending
types through inheritence. The most unusual feature about Ada's
approach to constructing classes and objects is that Ada has no class
reserved word. In many object oriented languages the concept of a
class is overloaded. It is both the unit of encapsulation and a
type definition. Ada separates these two concepts. Packages are
used for encapsulation. Tagged types are used to define
extensible types. This allows some interesting flexibilities. It
is entirely legal to define more than one tagged type in a
package. This ability is most useful when you need to define
mutually referencing (or circular referencing) types.
Defining a Tagged Type
A tagged type definition is syntactically simply
an extension of record type definition.
type Person is tagged record
Name : String(1..20);
Age : Natural;
end record;
Such a type definition is performed in a package.
Immediately following the type definition must be the procedures
and functions comprising the primitive operations defined
for this type. Primitive operations must have one of
its parameters be of the tagged type, or for functions the return
value can be an instance of the tagged type, allowing you to
overload functions based upon their return type. This is a stark
contrast to C++ and Java which do not allow you to overload based
upon the return type of a function. The tagged type is extended
by making a new tagged record based upon the original
type.
type Employee is new Person with record
Employee_Id : Positive;
Salary : Float;
end record;
Type Employee inherits all the fields and
primitive operations of type Person.
This ends part four of this article. Click here
to continue with part five, Concurrent Programming.
|