Présentation du langage
Historique

- Fortran (1956)
- Cobol / Algol / PL1 (1960)
- Basic (1965)
- Simula (1967)
- Pascal (1969)
- Ada (1974)
- C (1978)
- C++ (1986)
En 1970, l'attention du département de la Défense des Etats-Unis fut attirée par la tendance à l'augmentation des coûts des logiciels développés dans ses services. Avant cette époque, les coûts de matériels dépassaient largement les coûts de logiciels.
De 1968 à 1973, le DoD (United States Department of Defense) enregistra un accroissement de 51% du coût direct de ses logiciels, alors même que les coûts des matériels diminuaient de façon spectaculaire. Après avoir étudié le problème, il apparut clairement :
- que de très nombreux langages de programmation différents étaient utilisés.
- que certaines applications étaient programmées dans des langages inadéquats.
- que certains langages ne supportaient pas les principes de la programmation moderne des logiciels.
- qu'il manquait d'environnements de développement de logiciels.
A cette époque il existait au moins 450 langages généraux pour les systèmes du DoD, bien que suivant la source citée, le nombre réel variait de 500 à 1500.
Nombreux étaient les projets liés à des vendeurs particuliers ou à des technologies obsolètes ; c'est pourquoi nous avons vu des projets utiliser COBOL pour des applications temps-réel, ou des langages d'assemblage pour des systèmes de gestion....
Comme la majorité des coûts de logiciels au DoD était associée à des systèmes informatiques embarqués, le DoD tourna son intérêt vers ce domaine d'application. Par définition, un système informatique embarqué est un système qui forme une partie d'un système plus vaste dont le but principal n'est pas d'effectuer des calculs. Les systèmes embarqués ont des exigences de programmation spécifiques :
- traitement parallèle,
- contrôle en temps réel,
- traitement d'exception,
- contrôle d'Entrées/Sorties spécialisées.
A l'époque il n'existait pas encore de langage de haut niveau correspondant aux exigences des systèmes embarqués. Le DoD demanda donc la définition du cahier des charges pour un langage de haut niveau capable de satisfaire toutes les exigences en matière de qualité des logiciels. Ce langage devait aussi être unique et à usage général.
- 1975 : Début de la définition du cahier des charges.
- 1977 : Appel d'offre international pour rechercher des spécifications applicables au nouveau langage.
- 1979 : Ada devient le nom officiel du langage de haut niveau du DoD.
- 1980 : Manuel de référence Ada qui marque la fin de l'effort de conception et de test du langage.
- 17/02/83 : Le manuel de référence du langage Ada est approuvé comme norme ANSI.
- 1995 : Suite à une révision du langage, un nouveau standard est créé.
Un aperçu du langage
Généralités
Un programme Ada se compose d'une ou plusieurs unités de programme dont la plupart peuvent être compilées séparément. Toutes ces unités contiennent une spécification (interface) et un corps (réalisation). Les unités de programme comprennent :
- les sous-programmes
- les paquetages
- les unités génériques
- les tâches
| Unité de programme | Caractéristiques | Applications |
| Sous-programme | Action Séquentielle |
Unité programme principal. Définition de contrôle fonctionnel. Définition d'opérations sur des types. |
| Paquetage | Ensemble de ressources |
Ensemble nommé de déclarations. Groupement d'unités de programme reliées. Type de données abstrait. Machines abstraites à états. |
| Unité Générique | Modèle | Composants logiciels réutilisables. |
| Tache | Action parallèle |
Actions concurrentes. Routage de messages. Contrôle de ressources. Interruptions. |
Résumé des unités de programme Ada
Eléments de base du langage
Jeu de caractères
On peut écrire toute construction du langage Ada au moyen d'un jeu de caractères constitué de :
- lettres majuscules :
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
- chiffres :
0 1 2 3 4 5 6 7 8 9
- caractères spéciaux de base :
" ' ( ) * + , - . / : ; < = > _ | &
- les caractères de formatage :
espace, tabulation, marques de fin de ligne, fin de page, fin de fichier
- lettres minuscules :
a b c d e f g h i j k l m n o p q r s t u v w x y z
- des caractères spéciaux ne rentrant pas dans les constructions du langage :
#! $ ? @ [ ] { } ~ ^ %
Unités lexicales
Les éléments de base du langage appelés unités lexicales sont construits à partir du jeu de caractères Ada. Les unités lexicales comprennent :
- les identificateurs,
- les littéraux numériques ou les nombres,
- les littéraux caractères ou les caractères,
- les littéraux chaînes ou les chaînes,
- les délimiteurs,
- les commentaires.
Les identificateurs sont formés d'une lettre suivie d'une série de lettres, de chiffres, et/ou de caractères "trait bas" (pour améliorer la lisibilité). Les mots réservés (Ada en possède 68) sont :
abort digits loop record when abs do mod rem while accept else new renames with access elsif not requeue xor aliased end null return all entry of reverse and exception or select array exit others separate at for out subtype begin function package tagged body generic pragma task case goto private terminate constant if procedure then declare in protected type delay is raise until delta limited range use
Mots réservés Ada
Ada n'impose aucune limitation à la longueur des noms d'identificateurs définis par l'utilisateur à condition qu'ils tiennent sur une seule ligne.
Exemple d'identificateurs valides
Couleurs_de_l_arc_en_ciel INTERROGER_TERMINAUX capteur_37
Ada ne fait aucune différence entre les noms écrits en majuscules ou en minuscules. Donc les identificateurs COMPTEUR_37, compteur_37, CompTeur_37 sont identiques.
Les littéraux numériques représentent des valeurs entières ou réelles. Les nombres peuvent être exprimés dans n'importe quelle base de 2 à 16. Les traits bas sont autorisés entre les chiffres d'un nombre pour améliorer la lisibilité et sont ignorés par le compilateur.
Exemples de littéraux numériques entiers légaux
7 1_000_000 --identique à 1000000 1_00_00_00 --même valeur 1e6 --même valeur 2#1100# --équivalent en base 2 de 12 en base 10 16#C# --équivalent en base 16 de 12 en base 10
Exemples de littéraux numériques réels légaux
0.125 3.141_592_65 --valeur de PI 2.78e-3 --équivalent à 0.00278 1.0e6 --équivalent à 1000000.0 16#F.0# --équivalent en base 16 de 15.0 en base 10.
Les nombres réels exigent au moins un chiffre de chaque côté du point décimal.
Un littéral caractère est constitué d'un caractère graphique encadré par des apostrophes simples.
Exemple
'A' '*'
Une chaîne est une séquence éventuellement vide de caractères encadrée par des apostrophes doubles.
Exemple
"" --chaîne vide "Bonjour" --chaîne de longueur 7 """" --chaîne dont la valeur est "
Les délimiteurs sont les caractères de formatage et les symboles suivants :
- délimiteurs simples : ' ( ) * + , - . / : ; < = > | &
- délimiteurs composés : => .. ** := /= >= <= << >> <>
Les délimiteurs ont une signification spéciale qui dépend de leur contexte.
Les commentaires commencent par un double tiret (--) et se terminent sur la fin de ligne.
Un nombre quelconque d'espaces peut séparer deux unités lexicales adjacentes. Chaque unité lexicale doit tenir sur une ligne mais peut être placée n'importe où sur la ligne à l'exception des commentaires qui ne peuvent pas être en début de ligne et suivi d'une instruction.
Concepts généraux
L'objectif de ce chapitre est de présenter les différentes structures du langage Ada pour vous permettre d'écrire vos premiers programmes Ada rapidement.
Types simples
Un type de donnée abstrait caractérise :
- un ensemble de valeurs : les valeurs du type T sont les valeurs admissibles pour les variables du type T.
- un ensemble d'opérations : les opérations du type T définissent les manipulations que l'on peut faire sur les variables du type T.
En Ada on dispose de plusieurs classes de type :
- les types de données scalaires (entier, réel, énumératif),
- les types de données composés (tableau, enregistrement...),
- les types accès de données,
- les types privés de données,
- les sous-types et types dérivés.
Dans ce chapitre nous ne parlerons que des trois premiers types. Les autres types seront présentés ultérieurement.
Les types de données scalaires
Les types scalaires (non décomposables) comprennent les types réels, et les types discrets entiers et énumérés.
Les types entiers
Les types entiers définissent des ensembles de nombres entiers c'est-à-dire sans partie fractionnaire. Ada fournit un type entier prédéfini et permet la création de nouveaux types entiers.
Exemple de déclaration de variables de type entier prédéfini INTEGER
A,B,VAR3 : INTEGER;
Les variables ainsi déclarées sont de type entier prédéfini. On peut donc leur appliquer toutes les opérations applicables à des entiers classiques.
On pourra écrire entre autre :
A:=5; --La variable A reçoit 5
B:=6;
VAR3 := (A+B-4)*2 --VAR3 prend comme valeur le résultat de
--l'évaluation de l'expression située à
--droite du signe d'affectation' :='
Exemple de déclaration d'un type entier
type INDICE is range 1..50;
I:INDICE; --I est de type INDICE et ne pourra donc prendre que
--des valeurs entières comprises entre 1 et 50 inclus.
Les types réels
Les types réels définissent des ensembles de nombres avec une partie fractionnaire (nombres en point flottant ou en point fixe). Ada fournit un type réel flottant prédéfini.
Exemple de déclaration de variables de type réel prédéfini FLOAT
A : float; -- A est de type réel prédéfini
Exemple de déclaration de type réel
type MASS is digits 10; --type point flottant
type VOLTAGE is delta 0.01 range -12.0..+24.0; --type point fixe
A : VOLTAGE; --A est une variable réelle qui pourra prendre des
--valeurs comprises entre -12 et 24 avec une
--précision de 10-2.
Types énumératifs
Une définition de type énuméré déclare en les énumérant toutes les valeurs du type. Les types énumérés BOOLEAN et CHARACTER sont prédéfinis.
Exemple de déclaration d'un type énuméré
type COULEUR is (VERT, ROUGE, ORANGE); UNE_COULEUR : COULEUR;
Les types de données composés
Le type tableau
Les tableaux sont des ensembles d'éléments de même type. On peut déclarer des types tableau à une ou plusieurs dimensions.
Exemple de déclaration de types tableau
type ECHIQUIER is array (1..8, 1..8) of INTEGER; type PIXEL is array (INDEX range 5..10) of VOLTAGE; E:ECHIQUIER; P:PIXEL;
L'accès à un composant d'un tableau se fait en utilisant le nom de la variable suivi des indices entre parenthèses ; par exemple E(4,7), P(10).
Les types articles
Les articles sont des ensembles d'éléments de types identiques ou différents.
Exemple de déclaration de types article
type DATE is record
JOUR : INTEGER range 1..31;
MOIS : INTEGER range 1..12;
ANNEE : NATURAL;
end record;
AUJOURDHUI: DATE;
On peut renseigner les différents champs de la structure de la manière suivante :
AUJOURDHUI.JOUR := 12; AUJOURDHUI.MOIS := 4;
Les types accès de données
Les types accès de données correspondent aux notions de pointeurs que l'on retrouve en PASCAL ou C. Le langage Ada offre la possibilité d'allouer dynamiquement de la mémoire via le type accès.
Exemple de déclaration de types access
type POINTEUR is access DATE; --Chaque variable de type POINTEUR
--désignera un élément de type --DATE défini plus haut. POINTEUR1 : POINTEUR; --Déclaration d'une variable de type POINTEUR.
L'allocation de mémoire se fera de la manière suivante :
POINTEUR1 := new DATE; --Allocation dynamique de mémoire pour un élément de type DATE.
On peut ensuite renseigner les différents champs :
POINTEUR1.JOUR := 5; POINTEUR1.MOIS := 12;
Remarques
- les variables de type ACCESS sont les seules variables initialisées par défaut en Ada au moment de la compilation. La valeur par défaut est NULL qui signifie que le pointeur ne désigne aucune zone mémoire.
- contrairement au PASCAL ou au C, le langage Ada ne possède aucune instruction permettant de restituer une zone mémoire à l'espace libre. Dès qu'une zone mémoire allouée dynamiquement n'est plus désignée par aucun pointeur, la zone est automatiquement rendue à l'espace libre.
Les structures de contrôle
Les structures de contrôle permettent de modifier le déroulement séquentiel du programme.
Contrôle conditionnel
Instruction CASE
Syntaxe
case <expression> is --<expression> est de type discret entier ou énuméré when <choix1> => <suite d'instructions> when <choix2> => <suite d'instructions> when <choix3> => <suite d'instructions> ... when others => <suite d'instructions> end case;
Exemples
case I is case UNE_COULEUR is
when 1 => J := J + 2; when VERT => PASSER;
when 2|3 => J := -1; when ORANGE => RALENTIR;
when others => j := 5; ARRETER;
end case; when ROUGE => ATTENDRE;
end case;
Instruction IF..THEN..ELSE
Syntaxe
if <condition> then <suite d'instructions>;
if <condition> then
if <condition1> then
<suite d'instructions1>;
<suite d'instructions1>;
else
elsif <condition2> then
<suite d'instructions2>;
<suite d'instructions2>;
end if;
elsif
...
else
...
end if;
Exemples
if I = 1 then if I = 1 then
J := J + 2; J := J + 2;
else elsif (J = 2) or (J = 3) then
if (J = 2) or (J = 3) then J := -1;
J := -1; else
else J := 5;
J := 5; end if;
end if;
end if;
Remarque
Le ELSE se rapporte toujours au dernier IF.
Contrôle itératif
Forme de base LOOP..END LOOP
Syntaxe
<nom de la boucle> --facultatif loop <suite d'instructions> end loop;
Sous cette forme, la boucle s'exécute indéfiniment. On peut utiliser une instruction EXIT, provocant la sortie de la boucle.
Instruction EXIT
L'instruction exit permet de quitter une boucle. Le contrôle reprend juste après la fin de la boucle. Cette instruction a plusieurs formes.
Syntaxe
EXIT;
--Quitter la boucle englobante
EXIT <nom de la boucle>; --Quitter la boucle nommée
EXIT when <condition>; --Quitter la boucle englobante si la condition est vraie.
EXIT <nom de la boucle> when <condition>;
--Quitter la boucle nommée si la condition est vraie.
Exemple1
loop I := I + 4; exit when I > 50; end loop;
Exemple2
Avec plusieurs niveaux de boucles, il est possible de quitter un niveau de boucle en nommant explicitement la boucle.
BOUCLE_EXTERNE:
loop
<suite d'instructions> -- POINT A BOUCLE_INTERNE:
loop
<suite d'instructions> -- POINT B
end loop BOUCLE_INTERNE;
<suite d'instructions> -- POINT C
end loop BOUCLE_EXTERNE;
Avec exit ou exit BOUCLE_EXTERNE aux points A ou C, le contrôle est transféré à la fin de BOUCLE_EXTERNE.
Avec exit ou exit BOUCLE_INTERNE au point B le contrôle est transféré à la fin de BOUCLE_INTERNE, l'exécution reste sous le contrôle de BOUCLE_EXTERNE.
Avec exit BOUCLE_EXTERNE au point B le contrôle est transféré immédiatement à la fin de BOUCLE_EXTERNE.
Instruction WHILE..END LOOP
Syntaxe
while <condition> loop <suite d'instructions> end loop;
Exemple
I := j; while I < K loop I := I + 4; L := I * K; end loop;
Instruction FOR
Syntaxe
for <variable> in [reverse] <intervalle> loop <suite d'instructions> end loop;
Exemple1
for I in 10..20 loop -- schéma de boucle de pas 1 J := K + I; end loop;
Exemple2
for I in reverse debut..fin loop --schéma de boucle de pas -1 T(I) := I; end loop;
Remarques
- On doit toujours utiliser une boucle FOR lorsqu'on connaît le nombre d'itérations à exécuter au moment de l'écriture du programme.
- Les bornes de l'intervalle sont de type discret (entier ou énuméré).
- L'indice utilisé dans la boucle n'a pas à être déclaré. Il est déclaré de manière implicite lors de l'entrée dans la boucle.
- L'indice utilisé dans la boucle ne peut pas être manipulé en dehors de la boucle, ni modifié à l'intérieur de la boucle.
Le bloc
Un bloc est une structure permettant de déclarer des objets en cours d'exécution et de travailler sur ces objets.
La forme générale d'un bloc est la suivante :
<Nom de bloc> : --facultatif declare -- déclarations begin -- corps du bloc end <nom de bloc>;
Les blocs sont utilisés dans la gestion des erreurs. Ils permettent également de limiter la portée des variables.
Lorsque la fin du bloc est atteinte, les éléments qu'il déclare sont détruits.
Les sous-programmes
Les sous-programmes comme toutes les autres unités de programme Ada se composent de deux parties, une spécification et un corps. La spécification identifie le nom du sous-programme ainsi que les paramètres formels et le type du résultat pour les fonctions. Le corps encapsule une séquence d'instructions qui définit l'algorithme proprement dit. La notion de sous programme Ada correspond aux notions de procédures et de fonctions que l'on retrouve en Pascal ou en C.
Le mode de passage des paramètres
Il y a 3 modes de passage de paramètres :
- IN : le paramètre formel n'autorise que la lecture du paramètre effectif correspondant.
- OUT : le paramètre formel ne permet que la modification du paramètre effectif correspondant.
- IN OUT : le paramètre formel permet la lecture et la modification du paramètre effectif correspondant.
Les procédures :
Déclaration d'une spécification de procédure
procedure <nom de la procédure> (<liste des paramètres formels>) ;
L'utilisation de ce type de déclaration est présentée plus loin.
Déclaration du corps d'une procédure
procedure <nom de procédure> (<liste des paramètres formels>) is < types et variables locales> begin <suite d'instructions> end <nom de la procédure>;
Dans <liste de paramètres formels>, chaque paramètre est spécifié par :
<indentificateur> : <mode> <type> [:=valeur initiale pour les paramètre en entrée];
Exemple1
procedure EMPILER (ELEMENT: in INTEGER; SUR : in out PILE);
Exemple2
procedure SOMME (A, B : in INTEGER; C : out INTEGER) is begin C := A + B end SOMME;
Les fonctions
Déclaration d'une spécification de fonction
function <nom de la fonction> (<liste des paramètres formels>) return <type du résultat>;
Déclaration du corps d'une fonction
function <nom de la fonction> (<liste des paramètres formels>) return <type du résultat> is < types et variables locales> begin <suite d'instructions> + <1 instruction return> end <nom de la fonction>;
La spécification des paramètres formels est la même que pour une procédure sauf qu'ils sont ici tous en mode in.
Exemple1
function EST_VIDE (MAPILE : in PILE) return BOOLEAN;
Exemple2
function SOMME (A, B : in INTEGER) return INTEGER is begin return A + B; end SOMME;
Remarques sur le mode de passage des paramètres
Un paramètre in a une valeur donnée par l'unité appelante, et se comporte comme une constante, impossible à modifier.
Dans la procédure SOMME, les variables A et B ne doivent pas être modifiées (ne doivent pas être à gauche d'une affectation). Par contre, leurs valeurs peuvent être testées ou utilisées comme bornes d'intervalle d'une boucle.
Un paramètre out n'est pas défini lors de l'entrée dans une procédure, mais reçoit une valeur dans le corps de la procédure, (il peut donc être à gauche d'une affectation). Par contre, il ne peut pas être utilisé pour initialiser une autre variable (ne peut être à droite d'une affectation), ni même être testé ou utilisé comme borne d'intevalle d'une boucle.
Dans la procédure SOMME :
C := A + B; --est légale A := C; --est illégale C := C + 1; --est illégale.
Un paramètre in out a une valeur au moment de l'appel de la procédure, et est modifiable dans le corps de la procédure.
L'appel des sous-programmes
Un appel de procédure est une instruction.
Un appel de fonction est placé dans une expression.
L'association des paramètres effectifs aux paramètres formels peut se faire par position ou par nom.
Les appels possibles de la procédure SOMME sont les suivants :
SOMME(10, 15, R); --association par position SOMME(A => 10, B => 15, C => R); --association par nom SOMME(10, 15, C => R); --association mixte (déconseillée)
Pour un sous-programme ayant des paramètres d'entrée définis avec valeur par défaut, les paramètres effectifs correspondant peuvent être omis lors de l'appel ou leur valeur peut être modifiée.
Par exemple, pour le sous-programme défini par :
SP (X : in INTEGER := 4 ; Y : in FLOAT := 2.5; Z : out FLOAT);
les appels possibles sont :
SP (ZZ); --X = 4, Y = 2.5 SP (2, 4.5, ZZ); --les valeurs de X et Y sont changées SP (Y => 5.0, Z => ZZ); --seule la valeur de Y est changée
La surcharge
La surcharge consiste à utiliser le même nom pour représenter des sous-programmes différents. Un sous-programme surchargé est ambigu s'il est impossible au compilateur de savoir quel sous-programme utiliser. Pour résoudre ce problème, le compilateur considère les types et l'ordre des paramètres formels.
En Ada tout sous-programme peut être surchargé y compris les fonctions et procédures prédéfinies. On peut par exemple surcharger les opérateurs arithmétiques standards comme le "+", le "-" etc...
Exemple
procedure LIRE( I : out INTEGER); procedure LIRE( F : out FLOAT); procedure LIRE( V : out VECTEUR); --VECTEUR est un type tableau.
Exemple de surcharge d'un opérateur prédéfini
function "+"(X, Y : in NOMBRE) return NOMBRE; function "+"(V1, V2 : in VECTEUR) return VECTEUR;
La surchage des opérateurs prédéfinis n'est possible que par l'utilisation des packages.
Règles de portée et de visibilité
Dans une unité de programme, la portée d'une entité commence à sa déclaration et disparaît à la fin de l'unité. Les portées d'entités portant le même nom peuvent se chevaucher en particulier lors de surcharge de sous-programmes.
La visibilité d'une entité se trouve à l'intérieur de la portée et peut être directe en l'absence de chevauchement ou étendue sinon.
Exemple
procedure PORTEE_VISIBLE is
X: INTEGER;
Y: INTEGER;
function F1(X:in FLOAT) return FLOAT is
begin
return Y*X;
end F1;
...
procedure P1(..........) is
X:FLOAT;
I:INTEGER;
begin
...
end P1
begin
...
end PORTEE_VISIBLE;
|
Portée de X Y | | | | | | | | | | | | | | | | | | | | I | | | | | | | | I | | | | X Y |
Visibilité
directe de
X
| Y
| |
| |
| |
| |
| |
| |
| |
| | I
| | |
| | |
| | I
| |
| |
X Y |
Pour utiliser une entité cachée par une autre entité de même nom, on utilise la notation pointée : nom_de_l'unite.nom_de_l'entité.
Par exemple, dans la procédure P1, pour utiliser la variable X définie dans PORTEE_VISIBILITE, on note PORTEE_VISIBILITE.X ce qui étend la visibilité de X.
La notion de package
Un package Ada est un ensemble d'entités ou de ressources de traitement logiquement reliées entre elles. Il permet d'encapsuler les données c'est-à-dire de contrôler exactement la manière dont elles seront manipulées.
Un package Ada comprend :
- un partie visible appelée INTERFACE,
- une partie cachée qui est le CORPS du package.
Syntaxe
spécification corps
package <nom du package> is package body <nom du package> is <déclarations> <déclarations> private begin <déclarations d'objets privés> <rien ou initialisation> end <nom du package> end <nom du package>
La partie spécification (interface)
Cette interface spécifie quelles parties du package peuvent être utilisées et comment. Il est sans importance pour l'utilisateur du package de comprendre la façon dont les opérations sont effectivement implémentées. L'utilisateur d'un package ne peut se référer qu'à ces entités visibles. Par exemple, l'interface entre un homme et une voiture comprend le volant, les freins et l'accélérateur : ce sont les ressources visibles. Le conducteur n'a pas besoin de connaître comment elles marchent, c'est un détail d'implémentation!
La partie spécification d'un package comprend deux parties : la partie visible et la partie privée.
La partie visible déclare les ressources qui peuvent être utilisées à l'extérieur du package. On dit que le package exporte ces entités. Un package peut exporter un nombre quelconques d'éléments qui peuvent être des types, des sous-programmes...La partie privée ne peut apparaître qu'à la fin de la spécification, et elle commence avec le mot réservé private. La partie privée ne peut pas être référencée en dehors du package. Elle existe dans la partie spécification pour permettre le mécanisme de la compilation séparée des unités.
La partie implémentation (le corps du package)
On doit faire correspondre un corps à toute spécification de package, sauf si la spécification ne contient que des types et des objets, auquel cas le corps est optionnel. Les éléments du corps du package ne sont pas accessibles à l'extérieur du package. On respecte ainsi le principe de dissimulation de l'information.
Exemple
package COMPLEXE is
type NOMBRE is private;
procedure AFFECTER (X: out NOMBRE; GAUCHE : in Float; DROITE : in Float);
function "+" (X, Y: in NOMBRE) return NOMBRE;
function "-" (X, Y: in NOMBRE) return NOMBRE;
function ACCES_PR (X: in NOMBRE) return Float;
function ACCES_PI (X: in NOMBRE) return Float;
private
type NOMBRE is record
PARTIE_REELLE, PARTIE_IMAGINAIRE : Float;
end record;
end COMPLEXE;
package body COMPLEXE is
procedure AFFECTER (X: out NOMBRE; GAUCHE in Float; DROITE : in Float) is
begin
X:=(GAUCHE, DROITE);
end AFFECTER;
function "+" (X, Y: in NOMBRE) return NOMBRE is
begin
return (X.PARTIE_REELLE + Y.PARTIE_REELLE, X.PARTIE_IMAGINAIRE + Y.PARTIE_IMAGINAIRE);
end "+";
...
begin
end COMPLEXE;
Remarque
Les packages ne doivent pas être soumis à l'éditeur de lien. Ils doivent juste être compilés. Une fois compilés, ils peuvent être utilisés à partir d'un autre programme.
Utilisation d'un package
Un programme peut utiliser un package en utilisant les clauses with et use.
Syntaxe
with <nom du package>; use <nom du package>;
Exemple
with COMPLEXE; use COMPLEXE; procedure ESSAI is C1,C2,C3:NOMBRE; begin C3:=C1+C2; end;
Remarque
Un type private autorise les opérations d'affectation et de comparaison entre des variables de ce type. Ainsi si on reprend l'exemple précédent, on peut écrire C1 := C2 ce qui ne fait appel à aucun sous-programme de COMPLEXE. En utilisant un type limited private, l'affectation et les tests sont interdits. L'utilisateur du package ne pourra plus écrire C1 := C2. Il devra utiliser une procédure (ou fonction) du package COMPLEXE.
Les entrées/sorties
Les entrées/sorties de textes, de caractères, de nombres etc..., se font en utilisant les packages prédéfinis suivants :
ADA.TEXT_IO : permet les entrées/sorties de chaînes de caractères ou de caractères.
ADA.INTEGER_TEXT_IO : permet les entrées/sorties pour des entités de type prédéfini INTEGER.
ADA.FLOAT_TEXT_IO : permet les entrées/sorties pour des entités de type prédéfini FLOAT.
Exemple d'utilisation
with ada.text_io; use ada.text_io;
with ada.integer_text_io; use ada.integer_text_io;
with ada.float_text_io; use ada.float_text_io;
procedure ENTREE_SORTIE is
I : INTEGER;
R : FLOAT;
begin
put_line("Donner une valeur entiere :"); --Ecriture d'un message et saut de ligne
get(I); --Lecture au clavier de la variable I
put(" La valeur saisie est : ); --Ecriture d'un message sans saut
put(I); --Ecriture de la variable I sans saut
put_line("Donner une valeur reelle :"); --Ecriture d'un message et saut de ligne
get(R); --Lecture au clavier de la variable R
put(" La valeur saisie est : ); --Ecriture d'un message sans saut
put(R); --Ecriture de la variable R sans saut
end ENTREE_SORTIE;
Remarque
Les opérations de lecture (GET) et d'écriture (PUT) sont surchagées.
Pour réaliser des entrées/sorties de type entier, réel ou énuméré construits,
il faut utiliser des packages génériques prédéfinis.
Exemple d'utilisation
with ada.text_io; use ada.text_io;
procedure ENTREE_SORTIE is
type ENTIER is range 1..100; --Déclaration d'un type entier
type REEL is digits 6; --Déclaration d'un type réel
type JOUR is (LUN,MAR,MER,JEU,VEN); --Déclaration d'un type énuméré
--Déclaration des instances des packages génériques
package ES_ENTIER is new INTEGER_IO ( ENTIER); use ES_ENTIER;
package ES_REEL is new FLOAT_IO(REEL); use ES_REEL;
package ES_JOUR is new ENUMERATION_IO(JOUR); use ES_JOUR;
I : ENTIER;
R : REEL;
AUJOURDHUI : JOUR;
begin
put_line("Donner une valeur entiere:"); --Ecriture d'un message et saut de ligne
get(I); --Lecture au clavier de la variable I
put(" La valeur saisie est : ); --Ecriture d'un message sans saut
put(I); --Ecritue de la variable I sans saut
put_line("Donner une valeur reelle :"); --Ecriture d'un message et saut de ligne
get(R); --Lecture au clavier de la variable R
put(" La valeur saisie est : ); --Ecriture d'un message sans saut
put(R); --Ecriture de la variable R sans saut
get(AUJOURDHUI); --Lecture de la variable AUJOURDHUI
put(AUJOURDHUI); --Ecriture de la variable AUJOURDHUI
end ENTREE_SORTIE;
Les types
Ada est un langage fortement typé. Chaque variable et expression possèdent un type explicite déterminé lors de la compilation. Un type est un ensemble de valeurs, et un ensemble d'opérations applicables aux objets de ce type.
Un objet est créé et son type précisé au moyen d'une déclaration. Tous les objets doivent être explicitement déclarés. Deux objets sont de même type si leur déclaration se réfère au même nom de type.
Il y a deux sortes d'objets : les constantes non modifiables et les variables.
Déclaration de constantes
<liste_d'identificateurs> : constant [ <type> ] := expression;
Déclaration de variables
<liste_d'identificateurs> : <type> [ := expression];
L'initialisation est obligatoire pour une constante, facultative pour une variable.
Exemples:
TAILLE : constante INTEGER :=100; PI : constant := 3.14; --il s'agit ici d'un nombre nommé sans précision du type. TRUC : FLOAT; X : INTEGER := 1; ENTIER : INTEGER range 1..10 := 5; --Déclaration avec intervalle de valeurs.
Nous pouvons résumer les types Ada selon la classification suivante :
- Composés élémentaires privés
- tableaux accès scalaires
- articles - sur sous-programmes réels discrets
- tâches - sur bojets fixes flottants entiers énumérés
- étiquetés - binaires - signés
- protégés - décimaux - modulaires
- numériques
Types de données scalaires
Types entiers
Une définition de type entier introduit un ensemble d'entiers consécutifs comme valeur du type. Tous les littéraux entiers sont des valeurs d'un type anonyme infini, appelé entier_universel, que nous pouvons imaginer comme représentant tous les entiers du monde mathématique. Une implémentation donnée définira les types entiers comme des types dérivés d'entier_universel.
Les types entiers prédéfinis sont :
- INTEGER (une variable de type INTEGER aura un intervalle de variation de -32_768 à 32_767 sur une machine 16 bits),
- LONG_INTEGER,
- SHORT_INTEGER.
Remarque
Ada doit supporter obligatoirement le type INTEGER, et peut, mais ce n'est pas obligatoire supporter des intervalles d'entiers plus grands (LONG_INTEGER) ou plus court (SHORT_INTEGER).
L'utilisation des types entiers prédéfinis est déconseillée car les valeurs extrêmes dépendent de la machine. Afin de travailler indépendamment de la machine, il faut construire ses propres types.
Constructeur de types entiers avec intervalle de valeurs explicites
Le compilateur choisit la représentation interne la plus appropriée rendant ainsi le code portable.
Syntaxe
type <nom du type> is range Borne1..Borne2;
Exemple
type COMPTEUR_LIGNE is range 0..66; type BRASSE is range -5_000..0;
On peut ensuite déclarer des variables sur ces types de la manière suivante:
LIGNES : COMPTEUR_LIGNE;
Remarque
Si au cours de la manipulation d'une variable nous tentions de lui affecter une valeur externe à son intervalle de variation, l'exception CONSTRAINT_ERROR serait levée.
Ensemble de valeurs
Ensemble d'entiers consécutifs.
borne1 et borne2 sont des expressions entières représentant respectivement les bornes inférieure et supérieure de l'intervalle.
Ensemble d'opérations
- additives : + -
- affectation : :=
- appartenance : in not in
- exponentiation : * *
- multiplicatives : * / mod rem
- relationnelles : = /= < <= > >=
- unaires : + - abs
Attributs
ADDRESS PRED BASE SIZE FIRST SUCC IMAGE VAL LAST VALUE POS WIDTH
Types prédéfinis:
INTEGER LONG_INTEGER NATURAL POSITIVE SHORT_INTEGER
L'utilisation des attributs permet de paramétrer les programmes. Par exemple, une boucle sur le domaine de valeurs du type COMPTEUR_LIGNE défini plus haut peut s'écrire:
for I in COMPTEUR_LIGNE'range loop for I in COMPTEUR_LIGNE'first..COMPTEUR_LIGNE'last loop
L'attribut IMAGE peut être utilisé pour réaliser rapidement des impressions de variables entières en utilisant seulement Ada.text_io (sans passer par une instance de INTEGER_IO). Cet attribut fournit une chaîne de caractères correspondant à la valeur d'une variable de type entier.
Exemple
put(COMPTEUR_LIGNE'image(LIGNE)); --LIGNE étant une variable de type COMPTEUR_LIGNE.
Types réels
Un type réel définit un ensemble de valeurs qui sont des approximations des nombres réels. On considère que les littéraux réels sont du type réel_universel. avec une précision infinie.
Les types réels prédéfinis sont:
FLOAT, LONG_FLOAT, SHORT_FLOAT.
Comme pour les entiers, il est déconseillé d'utiliser ces types prédéfinis.
Constructeur de types point flottant
Les types point flottant permettent de décrire des valeurs avec une précision relative.
Syntaxe
type <nom du type> is digits <nombre>; type <nom du type> is digits <nombre> range Borne1..Borne2;
Exemples
type MESURE_PLANETAIRE is digits 15; type VALEUR_DE_MASSE is digits 7 range 0.0..3.0;
Constructeur de types point fixe
Les types point fixe permettent de décrire des valeurs avec une précision absolue.
Syntaxe
type <nom du type> is delta <nombre> range Borne1..Borne2;
Exemples
type fixe is delta 0.01 range -12.0..+24.0;
Constructeur de types point fixe décimaux
type <nom du type> is delta <nombre> digits <nombre> [range Borne1..Borne2];
Exemple
type FD is delta 0.01 digits 5; --Une précision de 0.01 et un domaine de valeur compris entre -999.99 et +999.99.
Ensemble de valeurs
Approximation des nombres réels.
Structure
- digits N range I..S : spécifie une incertitude relative, où N est une valeur entière statique qui représente le nombre de chiffres significatifs et I et S sont des valeurs statiques représentant les bornes inférieure et supérieure de l'intervalle.
- delta D range I..S : spécifie une incertitude absolue, où D est une valeur réelle statique qui représente le delta et I et S sont des valeurs statiques représentant les bornes inférieure et supérieure de l'intervalle.
- delta D digits N : spécifie une incertitude absolue, où D est une valeur réelle statique qui représente le delta, N le nombre de chiffres, en base 10, de la représentation.
Ensemble d'opérations
- additives : + -
- affectation : :=
- exponentiation : **
- multiplicatives : * /
- relationnelles : = /= < <= > >=
- unaires : + - abs
Attributs
Points fixes
ADDRESS MACHINE_OVERFLOWS AFT MACHINE_ROUNDS BASE MANTISSA DELTA SIZE FIRST SAFE_LARGE FORE SAFE_SMALL LARGE SMALL LAST
Points flottants
ADDRESS MACHINE_MANTISSA BASE MACHINE_OVERFLOWS DIGITS MACHINE_RADIX EMAX MACHINE_ROUNDS EPSILON MANTISSA FIRST SAFE_EMAX LARGE SAFE_LARGE LAST SAFE_SMALL MACHINE_EMAX SIZE MACHINE_EMIN SMALL
Types prédéfinis
FLOAT LONG_FLOAT SHORT_FLOAT DURATION
Types énumératifs
Un type énumératif Ada permet au programmeur de déclarer explicitement les valeurs possibles pour ce type.
Syntaxe
type <nom du type> is (<liste des valeurs>);
Exemples
type COULEURS is (rouge, bleu, marron, violet);
type CHIFFRE_HEXA is ('A', 'B', 'C', 'D', 'E', 'F');
type POSITION is (Haute, Basse);
On peut ensuite déclarer des variables par :
MA_COULEUR : COULEURS; TRAIN_D_ATTERRISSAGE : POSITION;
L'utilisation de ces variables peut se faire comme suit :
MA_COULEUR := COULEURS'FIRST; --Donne la couleur rouge TRAIN_D_ATTERRISSAGE := Haute;
Remarques
- La liste de valeurs fournit entre parenthèses ne doit pas comporter de mots réservés Ada.
Ainsi la déclaration:
type GAMME is ( Do, Re, Mi, Fa, Sol, La, Si,);
est illégale car Do est un mot réservé.
- Si nous tentons d'introduire une valeur en dehors de l'intervalle énumératif, l'exception CONSTRAINT_ERROR est levée.
Ensemble de valeurs
Ensemble des valeurs ordonnées distinctes.
Structure
(E0, E1, ...,En) où Ei est un littéral énumératif .
Ensemble d'opérations
- affectation : :=
- appartenance : in not in
- relationnelle : = /= < <= > >=
Attributs
ADDRESS PRED BASE SIZE FIRST SUCC IMAGE VAL LAST VALUE POS WIDTH
Types prédéfinis
BOOLEAN CHARACTER
Types de données composites
Types tableaux
Un nom de tableau dénote un groupe de composants de même type. Un tableau peut comporter plusieurs dimensions et Ada n'impose aucune limitation au nombre maximal de dimensions des tableaux.
Constructeur de types tableaux contraints
Tous les objets construit sur le type ont les mêmes bornes.
Syntaxe
type <nom du type> is array(<contraintes d'indice>) of <nom de type>;
Les contraintes d'indice sont précisées avec ou sans type. Dans ce dernier cas, les bornes sont des entiers. Le type des indices est discret. Le type des éléments du tableau est quelconque.
Exemple1
type TAB is array (1..30) of FLOAT;
Exemple2
type INDICE is range 0..1_000; type GRAND_TABLEAU is array(INDICE) of float; type PETIT_TABLEAU is array(INDICE range 10..49) of float;
Exemple3
type JOUR is (lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche); type HEURES is delta 0.1 range 0.0..24.0; type FICHE_DE_TRAVAIL is array (JOUR range LUNDI..VENDREDI) of HEURES; type HEURES_SUP is array (JOUR range SAMEDI..DIMANCHE) of HEURES; type SEMAINE is array (JOUR) of HEURES; DEMAIN : constant array (JOUR) of JOUR := (mardi, mercredi, jeudi, vendredi, samedi, dimanche, lundi);
Constructeur de types tableaux non contraints
Un type tableau non contraint doit être considéré comme un modèle de tableau dans lequel un paramètre reste à définir : le domaine des valeurs des indices. Le type tableau non contraint autorise la création de plusieurs objets du même type avec des bornes d'indices différentes. De plus les bornes (appelées contraintes d'indice) ne sont pas nécessairement statiques; elles peuvent être déterminées à l'exécution.
Syntaxe
type <nom du type> is array ( <non de type> range <> ) of < nom de type>;
Exemple1
type VECTEUR is array ( INTEGER range <> ) of float; V1 : VECTEUR (1..10); V2 : VECTEUR (1..100);
Ensemble de valeurs
Ensemble indicé de valeurs de type identique.
Ensemble d'opérations
- concaténation : & (tableau mono-dimensionnel)
- affectation : :=
- relationnelles : = /= < <= > >=
- agrégats et tranches
Attributs
ADDRESS RANGE BASE RANGE(J) FIRST LENGTH FIRST(J) LENGTH(J) LAST SIZE LAST(J)
Types prédéfinis
STRING
Exemple d'utilisation des attributs
with Ada.FLOAT_TEXT_IO; use ADA.FLOAT_TEXT_IO;
procedure ATTRIBUTS_TABLEAUX is
type MATRICE is array(INTEGER range 1..100, INTEGER range 1..100 ) of FLOAT;
type VECTEUR is array (INTEGER range 1..50) of FLOAT;
M1 : MATRICE; V1 : VECTEUR;
begin
--Lecture du vecteur V1
for I in V1'first..V1'last loop
get(V1(I));
end loop;
--Lecture de la matrice M1
for I in M1'range (1) loop
for J in M1'range(2) loop
get (M1(I,J));
end loop;
end loop;
end ATTRIBUTS_TABLEAUX;
Les agrégats et les tranches
L'agrégat est une opération spécifique aux types composés. Elle permet de construire une valeur d'un type composé en donnant une valeur à chaque composant.
Les associations de composants peuvent être données soit par leur position (association par position) ; soit en nommant les composants choisis (association nominative) ; soit en mélangeant les deux méthodes.
Association par position sur un tableau :
tab := (1, 3, 7, 5, 9); --tab étant d'un type tableau de 5 éléments tab2 := ((1, 2, 3), (4, 5, 6), (7, 8, 9)); --tab2 est d'un type tableau de dimension (3,3)
Association par nom sur un tableau :
tab := (1..4 => 45, 6..8 => 34); -- 45 de la position 1 à la position 4
-- 34 de la position 6 à la position 8
tab2(5..8) := ( 5|8 => 0, 6|7 => 1); --utilisation d'une tranche de tableau
--0 en position 5 et 8
--1 en position 6 et 7
Une façon élégante de nommer tous les éléments restants s'ils doivent avoir la même valeur est d'utiliser le choix OTHERS. Ce choix ne peut apparaître qu'en fin d'agrégat.
tab1 := (OTHERS => 0); --tous les éléments à 0 tab2 := (4|3|2 => 1, OTHERS => 0);
Association mixte : la notation par position ne peut apparaître qu'en début d'agrégat.
Une tranche est un sous-tableau. On peut faire des affectations par tranche.
tab(I + 1..J + 1) := tab(I..J); --Décalage de 1 vers la droite de la tranche entre les positions I et J.
Type tableau prédéfini STRING
Ada fournit deux types tableaux prédéfinis appelés STRING et WIDE_STRING, qui sont des types tableaux mono-dimensionnels dont les composants sont de type CHARACTER ou WIDE_CHARACTER.
Exemples d'utilisation
NOM : STRING(1..18); QUESTION : constant STRING := "Entrez vos données : "; NOM := "Reservoir" & " numéro 7"; --concaténation de deux chaînes
Types articles
Les articles sont des ensembles d'éléments de types identiques ou différents.
Comme pour le type tableau, on distingue le type article contraint (ou simple) et le type article non contraint ( ou avec discriminant).
Déclaration d'un type article simple
Syntaxe
type <nom du type> is record
<liste des champs>
end record;
Exemples
type PERSONNE is record
NOM,PRENOM : string (1..10);
AGE : POSITIVE range 0..60;
end record;
PAUL, PIERRE : PERSONNE;
Ensemble de valeurs
Ensemble de composants de types différents.
Ensemble d'opérations
- affectation : :=
- relationnelles : = /=
- agrégat
Attributs
Type article
ADDRESS BASE CONSTRAINED SIZE
Composant d'article
FIRST_BIT LAST_BIT POSITION
Les agrégats
Association par position sur un article :
PIERRE := ("DUPUIS ", " PIERRE ", 30); --le champ NOM prend la valeur DUPUIS
--le champ PRENOM prend la valeur PIERRE
--le champ AGE prend la valeur 30
Association par nom sur un article :
PAUL: = (NOM => "DUPONT ", PRENOM => "PAUL ", AGE => 30);
Association mixte sur un type article :
ANDRE : = ("DUPONT ", PRENOM => "PAUL ", AGE => 30);
Le choix OTHERS peut être utilisé à condition que les champs soient tous de même type.
Déclaration d'un type article avec discriminant
Syntaxe
type <nom de type> (<nom discriminant> : type [:= expression]) is record
<liste de champ> -- le discriminant est utilisé ici
end record;
Il peut existe un ou plusieurs discriminants. Un discriminant est de type discret et peut avoir ou non une valeur initiale.
La technique du discriminant permet de créer des article avec partie variante ou des articles mutables.
Exemple d'article avec partie variante
type SEXE is (MASCULIN, FEMININ);
type PERSONNE (DE_SEXE : SEXE) is record
NOM,PRENOM : STRING (1..8);
AGE : POSITIVE;
case DE_SEXE is
when MASCULIN => SITUATION_MILITAIRE : BOOLEAN;
when FEMININ => NOM_DE_JEUNE_FILLE : STRING(1..8);
NB_ENFANTS : NATURAL;
EN_ACTIVITE : BOOLEAN;
end case;
end record;
Dans cet exemple, le discriminant n'a pas de valeur par défaut. Toute création d'objet sur ce type doit donner une valeur au discriminant :
LUI : PERSONNE(MASCULIN); -- article avec 4 champs ELLE : PERSONNE(DE_SEXE => FEMININ); -- artcicle avec 6 champs
ou
LUI : PERSONNE(MASCULIN, "DUPONT ", "PIERRE ", 30, TRUE);
L'accès au discriminant est possible avec LUI.DE_SEXE, il est toutefois impossible de le modifier.
Les objets du type n'ont pas tous la même forme. L'affectation entre LUI et ELLE est interdite !
Exemple de type article mutable
Un type article avec discriminant peut être utilisé pour gérer des tableaux de taille variable.
type indice is range 1..1000; type TABLEAU is array (INDICE range <>) of INTEGER; type T_variable(TAILLE : INDICE) is record TAB : TABLEAU(1..TAILLE); end record;
La déclaration d'un objet doit être faite avec une valeur pour le discriminant :
T1 : T_VARIABLE(10); --T1 est un objet contraint et T1.TAB aura toujours 10 éléments (le discriminant n'a pas de valeur par défaut).
Avec la déclaration suivante :
type indice is range 1..1000;
type TABLEAU is array(INDICE range <>) of INTEGER;
type T_variable ( TAILLE : INDICE := 1) is record
TAB : TABLEAU (1 .. TAILLE);
end record;
T1 : T_VARIABLE; --T1 est un objet non contraint, T1.TAB est un tableau de 1 élément pour le moment,
--mais sa taille pourra être modifiée en cours d'exécution.
Autre exemple sur une chaîne de caractères :
type INDICE is range 1..255;
type CHAINE (TAILLE : POSITIVE :=1) is record
CH : STRING(1..TAILLE);
end record;
NOM, PRENOM : CHAINE;
NOM := (5, "BYRON");
PRENOM := (8, "ADELAIDE");
NOM.CH := NOM.CH&PRENOM.CH;
Le type ACCESS
Ada fournit le type accès pour permettre de manipuler les situations suivantes:
- des objets peuvent être créés et détruits de façon imprévisible,
- plusieurs noms peuvent se référer au même objets,
- les relations entre objets peuvent changer au cours du temps.
Syntaxe
type <nom de type> is access <nom de type>;
Exemple
type TAMPON is record
MESSAGE : string (1..10);
PRIORITE : POSITIVE;
end record;
type POINTEUR_TAMPON is access TAMPON;
On peut ensuite déclarer des variables de ce type:
PAQUET1, PAQUET2, PAQUET3 : POINTEUR_TAMPON;
Allocation de mémoire
L'allocation de mémoire en cours d'exécution se fait grâce à l'instruction new. On peut travailler sur l'espace alloué dès que l'instruction new a été exécutée.
Exemple1
PAQUET1 := new TAMPON; PAQUET1.PRIORITE := 5;
Exemple 2
PAQUET2 := new TAMPON'(MESSAGE => "**********", PRIORITE => 1);
Exemple 3
PAQUET3 := new TAMPON'("----------", 2);
Durée de vie des objets dynamiques
Les objets dynamiques sont détruits lorsque nous quittons la portée de leurs objets accès respectifs (en supposant que nous ne gardons pas de pointeurs globaux sur l'objet désigné).
Exemple
declare POINTEUR : POINTEUR_TAMPON; begin ... POINTEUR := new TAMPON; -- crée un objet TAMPON ... end; --L'objet TAMPON est détruit
Remarques
- Dès qu'une zone de mémoire allouée dynamiquement n'est plus désignée par aucun pointeur, cette zone est restituée à l'espace libre.
- Les variables de type access sont les seules variables initialiser par défaut à la compilation (valeur NULL).
- Ne pas confondre l'objet access (le pointeur) et l'objet désigné.
Exemple:
PAQUET1.all -- se réfère à tout l'objet PAQUET2 -- l'objet de type access
Ensemble de valeurs : valeurs d'accès sur des objets désignés
Ensemble d'opérations
- affectation : :=
- relationnelles : = /=
Attributs
ADDRESS BASE SIZE STORAGE_SIZE
Résumé des types accès
On peut utiliser des valeurs accès pour décrire des relations entre objets, par exemple pour représenter des structures de données comme les listes chaînées, les arbres,...
Exemple de définition d'une liste chaînée
type CELLULE; --déclaration incomplète
type LIEN is access CELLULE;
type CELLULE is record
VALEUR : INTEGER;
SUIVANT : LIEN;
end record;
TETE, COUR : LIEN;
TETE := new CELLULE; -- création d'une tête fictive
COUR := new CELLULE'(VALEUR => 10, SUIVANT => NULL);
TETE.SUIVANT := COUR;
L'accès au champ de la cellule qui vient d'être créée se fait par :
COUR.VALEUR ou TETE.SUIVANT.VALEUR.
Le type ACCESS généralisé
Ce type permet de créer des pointeurs sur des variables ou des constantes, ou sur des sous-programmes.
Création de pointeurs sur des variables ou des constantes
Syntaxe
type <nom de type> is access <all ou constant> <nom de type>;
Avec all, une valeur du type accès généralisé peut désigner seulement une variable et peut être utilisée pour mettre à jour cette variable.
Avec constant, une valeur du type accès généralisé peut désigner seulement une constante et ne peut être utilisée pour mettre à jour cette constante. Par contre, on peut lui donner une valeur initiale.
Exemple
type PTR1 is access all INTEGER; type PTR2 is access constant INTEGER; P1 : PTR1; --Déclaration de deux variables P2 : PTR2; --sur les deux types accès
Les objets définis sur le type pointés (ici INTEGER) doivent être déclarés aliased pour permettre l'accès à leurs adresses.
ENTIER1, ENTIER2 : aliased INTEGER;
Ces deux variables sont de types INTEGER et on va pouvoir récupérer les adresses en utilisant l'attribut access :
P1 := ENTIER1'access; --P1 donne accès à ENTIER1 P2 := ENTIER2'access; --P2 donne accès à ENTIER2
L'initialisation des deux objets ENTIER1 et ENTIER2 peut se faire de deux façons :
ENTIER1:=500; ou P1.all:=500; --la modification de ENTIER1 reste possible ENTIER2:=200; ou P2.all:=200; --la modification de ENTIER2 est impossible
Création de pointeur sur des sous-programme
Syntaxe
type <nom de type> is access function (profil des paramètres) return <profil du résultat>; type <nom de type> is access procedure (profil des paramètres);
Exemple
procedure EXEMPLE is
type PTR_F is access function (P : FLOAT) return FLOAT; --ce type accès est défini pour une fonction ayant un seul
--paramètre de type FLOAT
function INTEGRER ( LA_FONCTION PTR_F; MIN,MAX : FLOAT) return FLOAT is
begin
...
end INTEGRER;
RES : FLOAT; ou T := PTR_F := log'access;
begin
RES := INTEGRER (log'access, 1.0, 2.0);
ou RES := INTGERER(T, 1.0, 2.0);
end EXEMPLE;
Les types privés
Les types de données privés apparaissent dans les spécifications de packages sous la forme d'un nom de type suivi des mots réservés private ou limited private.
Sous-types et types dérivés
Les sous-types
Un type décrit les propriétés générales d'une entité du monde réel. Un sous-type permet d'exprimer des restrictions applicables seulement à certains objets en introduisant des contraintes.
Un sous-type ne définit pas de nouveau type, il caractérise un sous-ensemble des valeurs d' un type donné, dit type de base. A l'exception des contraintes de précision des types point flottant et point fixe, les contraintes d'un sous-type ne sont pas obligatoirement statiques, mais peuvent être évaluées à l'exécution. Un sous-type est compatible avec son type de base.
Syntaxe
subtype <nom du sous-type> is <nom du type> <contrainte>;
Exemple1
type NOM_DE_MOIS is (janvier, fevrier, mars, avril, mai, juin, juillet, aôut, septembre, octobre, novembre, décembre); subtype ETE is NOM_DE_MOIS range juin..aôut; MOIS_COURANT : NOM_DE_MOIS; VACANCES : ETE;
Exemple2
type NON_NEGATIF is range 0..INTEGER'LAST; subtype INDEX is NON_NEGATIF range 0..10;
Exemple3
type VECTEUR is array (NATURAL range <>) of float; subtype VECTEUR_3D is VECTEUR(1..3);
Exemple4
subtype GRAND is NON_NEGATIF range 0..1_000_000; subtype PETIT is GRAND range 0..10;
Les types dérivés
Contrairement aux sous-types, les types dérivés définissent des types distincts. Ils sont nécessaires lorsque notre abstraction de l'espace du problème contient des objets distincts mais dont la structure est similaire.
Syntaxe
type <nom du type dérivé> is new <nom du type parent>; type <nom du type dérivé> is new <nom du type parent> <contraintes>;
Exemple1
type MASSE is new FLOAT; type POIDS is new FLOAT;
MASSE et POIDS sont dérivés du type FLOAT qui est appelé type parent.
On peut ensuite déclarer des variables comme suit:
M : MASSE; P : POID;
Mais l'addition M + P est illégale car M et P ne sont pas du même type.
Exemple2
type BUDGET is new FLOAT range 0.0..12_000.0;
Remarque
Un type dérivé hérite des opérations applicables au type parent.
Explication et exemple
Reprenons le type DATE.
type DATE is record
jour : INTEGER range 1..31;
MOIS : INTEGER range 1..12;
ANNEE : NATURAL;
end record;
Si nous surchargeons l'opérateur "+" comme suit :
function "+"(D1 : in DATE ; D2 : in DATE) return DATE is ...
Soit le type dérivé suivant :
type MA_DATE is new DATE;
le sous-programme "+" est également dérivé. Toute variable de type MA_DATE pourra utiliser ce sous-programme. Cependant, si nous déclarons d'autres opérations sur DATE après la déclaration de MA_DATE, ces sous-programmes ne seront pas dérivés.
Les exceptions
Une exception désigne un événement qui provoque la suspension de l'exécution normale du programme. Cet événement peut être la violation d'une contrainte ou un état qui demande un traitement spécial.
Dans le premier cas l'exception est prédéfinie (exemple : CONSTRAINT_ERROR), dans le second cas, elle est définie par le programmeur.
Déclarer, lever, traiter une exception
Déclaration d'exceptions
Une déclaration d'exception est similaire dans sa forme à une déclaration d'objet. Elle consiste en une liste d'identificateurs, un signe deux-points, et le mot réservé exception. Elle se trouve dans toute partie déclarative.
Syntaxe
<nom de l'exception> : exception;
Exemple
MATRICE_SINGULIERE : exception; ERREUR_DE_PARITE : exception;
Remarque
Le nom d'une exception utilisateur a la même portée qu'une déclaration d'objet, bien que l'effet d'une exception puisse s'étendre au delà de sa portée.
Lever d'une exception
Lever une exception avec l'instruction RAISE, consiste à signaler qu'une situation anormale est survenue.
Syntaxe
raise <nom de l'exception définie>; raise;
Exemple
raise MATRICE_SINGULIERE;
L'instruction raise suspend le traitement séquentiel normal et propage l'exception jusqu'à ce qu'elle soit traitée.
Remarque
Le mot réservé raise peut apparaître partout où une levée d'exception est autorisée : dans la partie exécutable d'un bloc, d'un sous-programme. Ces unités de traitement peuvent être dans un package.
Traiter une exception
Lorsqu'il se produit une exception comme une division par zéro, dans la plupart des langages on suspend le traitement et le système d'exploitation reprend le contrôle. Dans un système temps réel fiable, nous ne pouvons pas permettre au programme de se terminer anormalement; nous devons pouvoir intercepter l'exception. C'est pourquoi Ada nous permet d'écrire des traites-exceptions pour rattraper aussi bien les exceptions prédéfinies que celles définies par l'utilisateur.
Lorsqu'une exception est levée dans une unité, le traitement de cette unité est abandonné et le contrôle est donné au traite-exception, s'il existe. Un traite-exception peut apparaître à la fin d'un bloc ou d'un corps de sous-programme .
Syntaxe
exception when <nom de l'exception> => <traitement à réaliser> when <nom de l'exception> => <traitement à réaliser> ... when others => <traitement à réaliser> end;
Exemple
procedure P is
-- déclarations
ERREUR, NE_MARCHE_PAS : exception;
begin
-- instructions
if INCIDENT then
raise ERREUR;
end if;
--instructions
if TOUT_VA_MAL then
raise NE_MARCHE_PAS;
end if;
--instructions
EXCEPTION
when ERREUR => faire _le_nécessaire;
when NE_MARCHE_PAS => remettre_un_peu_d'ordre;
when CONSTRAINT_ERROR => faire_autre_chose;
end P;
Les exceptions prédéfinies
Les exceptions prédéfinies sont levées implicitement. La liste des exceptions prédéfinies est la suivante :
CONSTRAINT_ERROR : levée lors de la violation d'une contrainte d'intervalle, d'indice ou de discriminant.
PROGRAM_ERROR :
- levée si l'on tente d'accéder à un sous-programme, à un package non encore élaborés.
- levée si une fonction atteint le end sans avoir exécuté d'instruction return.
Remarque:
L'implémentation peut également lever cette exception si elle détecte un comportement erroné.
STORAGE_ERROR : levée lorsque l'espace dynamique alloué à une entité est épuisé.
DATA_ERROR : levée lorsque ce que l'on a lu ne peut être interprété selon le type désiré.
Propagation des exceptions
Lorsqu'une exception est levée dans une suite d'instructions, l'exécution de la suite est abandonnée : s'il y a un traite-exception, l'exécution se poursuit avec les instructions du traite-exception.
Règle
Si une exception levée n'est pas traitée localement, elle se propage au niveau de la structure englobante jusqu'à ce qu'elle soit traitée, ou qu'elle parviennent à l'environnement.
- une exception dans une partie déclarative est toujours propagée à l'unité englobante.
- une exception peut-être propagée au delà de sa portée. Dans ce cas, elle ne peut-être traitée qu'avec la clause when others.
Utilisation des exceptions
On ne doit pas utiliser les exceptions pour simuler des goto implicites. Nous devons essayer d'identifier les conditions d'erreur possibles et nous n'utilisons les exceptions que pour prévoir leur traitement.
Plusieurs types d'actions sont possibles lorsqu'on lève une exception:
- abandonner l'exécution de l'unité,
- essayer l'opération à nouveau,
- utiliser une méthode différente,
- réparer la cause de l'erreur.
Remarque
Il est généralement déconseillé d'ignorer l'exception.
Exemple 1
Une procédure PROC appelle une fonction qui peut (de temps en temps) faire une division par zéro (qui lève CONSTRAINT_ERROR).
Solution 1
function FONC(.....) return TRUC is
begin
return C/D;
end;
procedure PROC is
T: TRUC;
begin
T := FONC;
exception
when CONSTRAINT_ERROR => ...;
end PROC;
Solutions 2
Traiter l'exception au niveau de l'appel, FONC reste inchangée.
procedure PROC is
T: TRUC;
begin
...
begin
T: = FONC;
exception
when CONSTRAINT_ERROR => ...;
end;
...
end PROC;
Solutions 3
Traiter l'exception dans FONC
function FONC(.....) return TRUC is
begin
...
begin
return C / D;
exception
when CONSTRAINT_ERROR => ...;
end;
end FONC;
procedure PROC is
T : TRUC;
begin
T := FONC;
end PROC;
Exemple2
Lecture contrôlée d'un entier.
procedure LIRE (ENTREE: out INTEGER) is
begin
loop
begin
put(">");
get(ENTREE); SKIP_LINE;
exit;
exception
when OTHERS => SKIP_LINE;
PUT_LINE("Reponse invalide, entrez une valeur entière");
end;
end loop;
end LIRE;
Si l'entrée est valide, nous passons à l'instruction suivante et nous sortons de la boucle.
Si l'utilisateur saisit une donnée qui n'est pas entière, l'exception DATA_ERROR est levée. Comme le bloc est à l'intérieur d'une boucle, après écriture du message, le processus est réitéré jusqu'à ce que l'utilisateur entre une réponse valide .
Exemple 3
function MULT (U,V ; FLOAT) return FLOAT is
begin
return U*V;
exception
when CONSTRAINT_ERROR => return FLOAT'LARGE;
end MULT;
Exemple 4
procedure TRUC is
type VECTEUR is array (POSITIVE range <>) of FLOAT;
...
function MAX return INTEGER is
begin
...
end MAX;
procedure SOLUTION is
TAB : VECTEUR(1..MAX);
...
begin
...
end SOLUTION;
...
begin
...
begin SOLUTION; ou SOLUTION;
...
exception
when OTHERS => REPARER;
end;
exception
when OTHERS => REPARER;
end TRUC;
Exemple 5
procedure INVERSION_MATRICE is
SINGULARITE : EXCEPTION;
type MATRICE is array(INTEGER range <> ; INTEGER range <>) of FLOAT;
NB_MATRICE : POSITIVE;
procedure INVERSER(M : in out MATRICE ) is
DETERMINANT : FLOAT;
EPSILON : constant FLOAT := 1.0E-10;
begin
--calcul du déterminant de la matrice
if ABS(DETERMINANT) < EPSILON then
raise SINGULARITE;
end if;
--fin du calcul de l'inverse
end INVERSER;
procedure TRAITER_UNE_MATRICE is
M : MATRICE;
begin
LIRE(M);
INVERSER(M);
ECRIRE(M);
exception
when SINGULARITE => put ( "matrice singulière");
end TRAITER_UNE_MATRICE;
begin
--lire NB_MATRICE
for i in 1..NB_MATRICE loop
TRAITER_UNE_MATRICE;
end loop;
end INVERSION_MATRICE;
Les packages
Un package Ada est un ensemble d'entités ou de ressources de traitement logiquement reliées entre elles. Il permet d'encapsuler ces ressources c'est-à- dire de contrôler exactement la manière dont elles seront manipulées.
Forme des packages
Un package Ada comprend :
- une partie visible appelée INTERFACE,
- une partie cachée qui est le CORPS du package,
qui peuvent être compilées séparément.
Syntaxe
package <nom du package> is <déclarations> private <déclarations d'objets privés> end <nom du package>; Package body <nom du package> is <déclarations des types et variables locales> <sous-programmes> begin <rien ou initialisation> end <nom du package>;
La partie spécification (interface)
Cette spécification regroupe toute les entités que le package exporte, c'est-à-dire toutes les informations nécessaires à la bonne utilisation des entités exportées.
La partie spécification d'un package comprend deux parties :
- la partie visible accessible à l'utilisateur,
- éventuellement, une partie privée , nécessaire au compilateur et non accessible à l'utilisateur (mais utilisée dans le corps du package).
Le corps du package (la partie implémentation)
Le corps comporte deux parties :
- une partie déclarative qui regroupe les objets nécessaires au fonctionnement interne du package, ainsi que la réalisation concrète des unités de programmes déclarées dans le spécification du package (corps des sous-programmes).
- une partie initialisation qui est exécutée au moment de l'élaboration du package. La plupart du temps, cette partie est vide.
Remarque
La partie spécification est obligatoire. Par contre, le corps peut être absent si le package exporte seulement des types ou des variables.
Un exemple simple
L'exemple suivant est une ébauche de boîte à outils pour la gestion de matrices et de vecteurs de réels.
package MATRICES is type VECTEUR is array(POSITIVE range <>) of FLOAT; type MATRICE is array(POSITIVE range <>, POSITIVE range <>) of FLOAT; --Définition des opérations d'entrée/sortie; procedure LIRE (V : out VECTEUR); procedure LIRE (M : out MATRICE); procedure ECRIRE (V : in VECTEUR); procedure ECRIRE (M : in MATRICE); --Opérations de manipulation des vecteurs et des matrices; function IDENTITE(TAILLE : in positive) return MATRICE; function "+"(V1, V2 : in VECTEUR) return VECTEUR; function "+"(M1, M2 : in MATRICE) return MATRICE; function "*"(M1, M2 : in MATRICE) return MATRICE; etc... end MATRICES;
L'utilisation de ce package est faite sans connaître le corps.
with MATRICES; use MATRICES; procedure UTILISATION is -- Déclaration de 2 matrices; M1 : MATRICE(1..10, 1..5) := (OTHERS => (others => 0.0)); M2 : MATRICE(1..5, 1..10) := (OTHERS => (others => 0.0)); --Création d'une matrice identité d'ordre 10; MI : MATRICE(1..10, 1..10) := IDENTITE(10); --Déclaration de 3 vecteurs; V1, V2, V3 : VECTEUR(1..10); -- Déclaration d'une matrice 10e10; MRES : MATRICE(1..10, 1..10); begin LIRE(M1); LIRE(M2); MRES := M1 + M2; ECRIRE(MRES); LIRE(V1); LIRE(V2); V3 := V1 + V2; ECRIRE(V3); ... end UTILISATION;
Le corps du package est le suivant :
with text_io; use text_io;
with ADA.FLOAT_TEXT_IO; use ADA.FLOAT_TEXT_IO;
package body MATRICES is
--Définition d'une opération de lecture contrôlée d'un réel, interne au package
procedure LIRE(R : out FLOAT) is
begin
loop
begin
put (">"); get (R);
SKIP_LINE;
exit;
exception
when OTHERS => SKIP_LINE;
PUT_LINE("Réponse invalide, entrez une valeur réelle");
end;
end loop;
end LIRE;
--Corps des opérations d'entrée/sortie;
procedure LIRE(V : out VECTEUR) is -- lire un vecteur;
begin
for I in V'range loop
LIRE(V(I));
end loop;
end LIRE;
procedure LIRE(M : out MATRICE) is --lire une matrice;
begin
for I in M'range(1) loop
for J in M'range(2) loop
LIRE(M(I,J));
end loop;
end loop;
end LIRE;
procedure ECRIRE(V : in VECTEUR) is --écrire un vecteur;
begin
for I in V'range loop
PUT(V(I));
end loop;
end ECRIRE;
procedure ECRIRE(M : in MATRICE) is --écrire une matrice;
begin
for I in M'range(1) loop
for J in M ' range (2) loop
PUT(M(I, J));
end loop;
end loop;
end ECRIRE;
--Corps des opérations de manipulation des vecteurs et des matrices
function IDENTITE(TAILLE : in positive) return MATRICE is
RESULTAT : MATRICE(1..TAILLE, 1..TAILLE) := (others => (others => 0.0));
begin
for I in 1..TAILLE loop
RESULTAT(I, I) := 1.0;
end loop;
return RESULTAT;
end IDENTITE;
function "+"(V1, V2 : in VECTEUR) return VECTEUR is --addition de 2 vecteurs
begin
declare
R : VECTEUR (1..V1'LENGTH);
begin
for I in R'range loop
R(I) := V1(I) + V2(I);
end loop;
return R;
end;
end "+";
function "*"(M1, M2 : in MATRICE) return MATRICE is --produit de 2 matrices
begin
declare
R : MATRICE(1..M1'LENGTH(1), 1..M2'LENGTH(2));
CUMUL : FLOAT;
begin
for I in R'range (1) loop
for J in R'range (2) loop
CUMUL := 0.0;
for k in M1'range (2) loop
CUMUL := CUMUL + M1(I, K) * M2(K, J);
end loop;
R(I, J) := CUMUL;
end loop;
end loop;
return R;
end;
end "*";
--et la fonction "+" sur les matrices.
end MATRICES;
Packages et types privés
Nous avons mentionné que les packages permettent de respecter le principe d'abstraction. Un type privé va permettre de protéger certains objets en rendant leur manipulation impossible en dehors des procédures et des fonctions prévues à cet effet. La structure interne d'un type privé est inaccessible de l'extérieur du package.
Il existe deux classes de types privés :
- les types privés simples,
- les types privés limités.
Les types privés simples : private
La seule information disponible à l'extérieur du package est celle donnée par la partie visible du package. Le nom du type est disponible, mais l'ensemble des valeurs ou la structure du type sont cachés.
Les seules opérations que l'on peut appliquer aux objets d'un type privé sont les sous-programmes déclarés dans la partie visible, ainsi que l'opérateur d'affectation et les tests d'inégalité et d'égalité.
A l'intérieur d'une spécification de package, nous pouvons non seulement définir des types privés mais également déclarer des constantes du type privé (mais nous ne pouvons pas définir de variables du type privé avant l'élaboration de la partie privée).
Exemple1
package GESTIONNAIRE is type MOT_DE_PASSE is private; PASSE_NUL : constant MOT_DE_PASSE; function OBTENIR return MOT_DE_PASSE; function EST_VALIDE (M: in MOT_DE_PASSE) return BOOLEAN; private type MOT_DE_PASSE is range 0..7_000; PASSE_NUL : constant MOT_DE_PASSE := 0; end GESTIONNAIRE;
MOT_DE_PASSE est un type privé. Un utilisateur peut définir son mot de passe en utilisant la fonction OBTENIR. Par contre, il lui est impossible de donner une autre valeur à son mot de passe. Nous pouvons utiliser librement PASSE_NUL à l'extérieur du package de la manière suivante:
MON_MOT : MOT_DE_PASSE := PASSE_NUL;
Les types limités privés : limited private
Les seules opérations que l'on peut appliquer à des objets d'un type limité privé sont les sous-programmes déclarés dans la partie visible du package.
Applications des packages
Collection nommée de déclarations
Le package exporte des objets et des types, n'exporte pas d'autres unités de programmes. Ce type de package n'a pas de corps (car il n'y a pas de déclaration de sous-programme dans l'interface).
Exemple
package INFO_DATE is type NOM_JOUR is (lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche); type NOM_MOIS is (janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre); type NUM_JOUR is range 1..31; type NUM_MOIS is range 1..12; type NUM_ANNEE is range 1900..3000; end INFO_DATE;
Une utilisation
with INFO_DATE; use INFO_DATE;
procedure UTIL is
type DATE is record
JOUR : NUM_JOUR;
MOIS : NOM_MOIS;
ANNEE : NUM_ANNEE;
end record;
begin
...
end UTIL;
Groupement d'unités de programme reliées
Le package n'exporte pas de type ni d'objet, il n'exporte que des unités de programmes. On retrouve ici la notion habituelle de bibliothèque (mathématique, statistique...). Les sous-programmes définis dans l'interface sont indépendants les uns des autres et sans mémoire. L'ordre des appels est quelconque.
Exemple
package FONC_TRIG is function COS(ANGLE : in FLOAT) return FLOAT; function SIN(ANGLE : in FLOAT) return FLOAT; function TAN(ANGLE : in FLOAT) return FLOAT; ... end FONC_TRIG; Package body FONC_TRIG is --Corps des sous-programmes définis dans l'interface end FONC_TRIG;
Type de données abstraits
Un type de données abstrait (TDA) est une structure de donnée et les différentes opérations définies sur cette structure. En Ada cela se traduit par un package exportant un seul type ( de préférence privé ou limité privé) et des sous-programmes agissant sur ce type.
Les opérations définies sur un TDA sont de trois types :
- l'itérateur qui réalise le parcours de la structure,
- le sélecteur qui interroge l'état de l'objet courant,
- le constructeur qui modifie l'état de l'objet courant.
L'itérateur et le constructeur sont exprimés sous forme de procédures, le sélecteur sous forme de fonction.
A partir d'un TDA, il est possible de créer plusieurs objets similaires.
Parmi les TDA les plus connus, on trouve les types PILE, FILE, LISTE CONTIGUE, LISTE CHAINEE, ARBRE, ... Ces structures n'ont aucune utilité par elles-mêmes, elles ne servent qu'à organiser, stocker, gérer d'autres entités.
Exemple
Package de gestion d'une file circulaire d'entiers.
package TDA_FILE is
type TYPE_FILE (TAILLE: POSITIVE) is limited private;
FILE_PLEINE, FILE_VIDE : exception;
--Sélecteurs;
function EST_VIDE (LA_FILE : in TYPE_FILE) return BOOLEAN;
function EST_PLEINE (LA_FILE : in TYPE_FILE) return BOOLEAN;
--Constructeurs;
procedure VIDER (LA_FILE : in out TYPE_FILE);
procedure AJOUTER(A_LA_FILE : in out TYPE_FILE; V : in INTEGER);
procedure RETIRER(DE_LA_FILE : in out TYPE_FILE; V : out INTEGER);
private
type TAB is array(POSITIVE range <>) of INTEGER;
type TYPE_FILE(TAILLE : POSITIVE) is record
NB_ELEMENT : NATURAL :=0;
PREMIER : POSITIVE := 1;
DERNIER : POSITIVE := TAILLE;
TABLE : TAB(1..TAILLE);
end record;
end TDA_FILE;
package body TDA_FILE is
procedure VIDER (LA_FILE : in out TYPE_FILE) is
begin
LA_FILE.NB_ELEMENT := 0;
LA_FILE.PREMIER := 1;
LA_FILE.DERNIER := LA_FILE.TAILLE;
end VIDER;
function EST_VIDE (LA_FILE : in TYPE_FILE) return BOOLEAN is
begin
return LA_FILE.NB_ELEMENT = 0;
end EST_VIDE;
function EST_PLEINE (LA_FILE : in TYPE_FILE) return BOOLEAN is
begin
return LA_FILE.NB_ELEMENT = LA_FILE.TAILLE;
end EST_PLEINE;
procedure AJOUTER(A_LA_FILE : in out TYPE_FILE; V : in INTEGER) is
N : POSITIVE renames A_LA_FILE.TAILLE;
begin
if EST_PLEINE (A_LA_FILE) then
raise FILE_PLEINE;
end if;
A_LA_FILE.DERNIER := A_LA_FILE.DERNIER rem N + 1;
A_LA_FILE.TABLE (A_LA_FILE.DERNIER) := V;
A_LA_FILE.NB_ELEMENT := A_LA_FILE.NB_ELEMENT + 1;
end AJOUTER;
procedure RETIRER(DE_LA_FILE : in out TYPE_FILE ; V : out INTEGER) is
N : POSITIVE renames DE_LA_FILE.TAILLE;
begin
if EST_VIDE(DE_LA_FILE) then
raise FILE_VIDE;
end if;
V := DE_LE_FILE.TABLE(DE_LA_FILE.PREMIER);
DE_LA_FILE.PREMIER := DE_LA_FILE.PREMIER rem N + 1;
DE_LA_FILE.NB_ELEMENT := DE_LA_FILE.NB_ELEMENT - 1;
end RETIRER;
end TDA_FILE;
TYPE_FILE a été déclaré limité privé pour interdir l'affectation et les comparaisons d'objets de ce type entre eux.
Exemple d'utilisation de ce package
with TDA_FILE; use TDA_FILE;
procedure UTIL is
F1 : TYPE_FILE(TAILLE => 100);
F2 : TYPE_FILE(TAILLE => 200);
...
begin
AJOUTER(A_LA_FILE => F1, V => 40);
AJOUTER(A_LA_FILE => F2, V => 50);
...
exception
when FILE_PLEINE => ...;
when FILE_VIDE => ...;
end UTIL;
Les machines abstraites à états
Une machine abstraite est une structure de données cachée et les opérations définies sur cette structure. Le package traduisant une machine abstraite présente une spécification ne contenant que des sous-programmes (et éventuellement des types annexes pour la définition des paramètres).
La structure de données est cachée dans le corps du package qui garde une trace de l'état courant de la structure. Ce sont les sous-programmes de l'interface qui permettent de modifier cet état.
Avec une machine abstraite, un seul objet est créé (sauf si elle est générique).
Exemple
package MACHINE_FILE is
FILE_PLEINE, FILE_VIDE : exception;
procedure VIDER ;
function EST_VIDE return BOOLEAN;
function EST_PLEINE return BOOLEAN;
procedure AJOUTER(V : in INTEGER);
function RETIRER return INTEGER;
end MACHINE_FILE;
with ADA.TEXT_IO; use ADA.TEXT_IO;
with ADA.INTEGER_TEXT_IO; use ADA.INTEGER_TEXT_IO;
package body MACHINE_FILE is
function TAILLE return POSITIVE is
N : POSITIVE;
begin
PUT("taille de la file : ");
GET(N);
return N;
end TAILLE;
--Définition interne de la file;
NB_ELEMENT : POSITIVE := TAILLE;
subtype INDICE is INTEGER range 1..NB_ELEMENT;
TABLE : array(INDICE) of INTEGER;
PREMIER, DERNIER : INDICE := 1;
procedure VIDER is
begin
PREMIER := 1;
DERNIER := 1;
end VIDER;
function EST_PLEINE return BOOLEAN is
begin
return ((DERNIER mod NB_ELEMENT) + 1) = PREMIER;
end EST_PLEINE;
function EST_VIDE return BOOLEAN is
begin
return PREMIER = DERNIER;
end EST_VIDE;
procedure AJOUTER (V : in INTEGER) is
SUIVANT : INDICE := (DERNIER mod NB_ELEMENT) + 1;
begin
if SUIVANT = PREMIER then
raise FILE_PLEINE;
end if;
TABLE (DERNIER) := V;
DERNIER := SUIVANT;
end AJOUTER;
function RETIRER return INTEGER is
V : INTEGER;
begin
if EST_VIDE then
raise FILE_VIDE;
end if;
V := TABLE(PREMIER);
PREMIER := (PREMIER mod NB_ELEMENT) + 1;
return V;
end RETIRER;
end MACHINE_FILE;
Utilisation de ce package:
with MACHINE_FILE; use MACHINE_FILE; -- Le file existe; procedure UTIL is E : INTEGER; begin AJOUTER(V => 10); -- Ajoute un élément à la file; E := RETIRER; --Sortie d'un élément; end UTIL;
Compléments
Retour sur les types privés
L'utilisation d'un type privé permet de sauvegarder l'intégrité des objets du type, en préservant la confidentialité de la structure du type.
Dans certains cas, les interdictions induites par un type privé peuvent sembler trop strictes. Le concepteur d'un package peut autoriser certaines opérations. Ces autorisations sont données en redéfinissant les opérateurs interdits, dans l'interface du package.
Exemple
package P1 is
type T1 is private;
a : constant T1;
-- Définition d'opérations sur le type T1;
function "<" (x, y : T1) return BOOLEAN;
private
type T1 is range 1..100;
a : constant := 2;
end P1;
package body P1 is
--Corps des opérations sur T1;
function "<" (x, y : T1) return BOOLEAN is
begin
return INTEGER(x) < INTEGER(y);
end "<";
...
end P1;
Un peu d'héritage
Soient les structures de package suivantes :
Package PAC1 is type T1 is range 1..100; procedure O1 (P1 : in T1 ; P2 : out T1); function F1 (P : in T1) return STRING; end PAC1; with PAC1; use PAC1; package PAC2 is subtype S1 is T1 range 1..10; procedure O2 (P2 : in S1 ; R : out BOOLEAN); end PAC2; with PAC2; use PAC2; procedure UTIL is V1 : S1; R1 : BOOLEAN; V2 : T1; --Impossible, PAC1 n'est pas dans le contexte de UTIL; begin O2(V1, R1); --Les opérations de PAC1 ne sont pas accessibles; ... end UTIL;
Solution 1
La solution la plus directe est de rajouter PAC1 dans le contexte de UTIL.
Solution 2
Une autre solution consiste à renommer dans PAC2 les éléments de PAC1 susceptibles d'être utilisés. Après ce « renommage », ces éléments sont considérés comme appartenant à PAC1.
PAC1 reste inchangé et PAC2 devient :
with PAC1; use PAC1; package PAC2 is subtype S1 is T1 range 1..10; procedure O1(P1 : in S1 ; P2 : out S1) renames PAC1.O1; procedure O2(P2 : in S1 ; R : out BOOLEAN); end PAC2; with PAC2; use PAC2; procedure UTIL is V1, V2 : S1; R1 : BOOLEAN; Begin V1 := 1; O1(V1, V2); O2(V2, R1); end UTIL;
Solution 3
Utiliser la dérivation de type.
PAC1 reste inchangé et PAC2 devient :
with PAC1; use PAC1; package PAC2 is type D1 is new T1; subtype S1 is T1 range 1..10; procedure O2(P2 : in S1 ; R : out BOOLEAN); end PAC2; with PAC2; use PAC2; procedure UTIL is V1, V2 : D1; R1 : BOOLEAN; V3 : S1; begin V1 := 2; V3 := 3; O1(V1, V2); O2(V3, R1); end UTIL;
La généricité
Introduction
Jusqu'ici, nous avons vu comment réaliser des packages et des procédures en donnant seulement leurs spécifications, tout en gardant secret leur réalisation. Ada permet de faire mieux encore.
Prenons un exemple : soient un programme de traitement des graphes des stations de métro et un programme de gestion des graphes de connections électroniques. Ces deux programmes n'ont à priori rien à voir entre eux, et pourtant !. Le programme de gestion des graphes est commun aux deux.
Cet exemple est caractéristique de la généricité : programmer générique, c'est programmer en "blanc" en pensant à la réutilisabilité.
Quelles sont les raisons qui peuvent amener à concevoir générique:
- il existe des algorithmes "naturellement" génériques pour certains traitements : tris, gestion de listes, gestion de graphes, gestion de piles, . Cela couvre aussi la notion de type abstrait;
- pour paramétrer un traitement par des procédures ou des fonctions différentes.
Pour notre exemple de gestion de graphes, il faut construire un modèle générique sur les nouds et les arcs du graphe. Le type du contenu des nouds sera le paramètre générique. Les opérations à fournir dans les spécifications sont:
- créer un noud avec telle valeur,
- créer un arc entre deux nouds,
- attribuer un coût ( ou un poids) à un arc,
- donner les nouds voisins d'un noud,
- etc.
L'une des difficultés, comme pour un package non générique, est de clore la liste des opérations.
Qui peut-être générique ? Toutes les unités de compilation : procédure, fonction, package.
La généricité est donc la propriété qui permet d'engendrer différentes versions d'un modèle, dit modèle générique. La fabrication d'une version s'appelle une instanciation et une version, une instance.
L'instanciation consiste à:
- fournir un identificateur qui désignera l'unité de programme ( la version),
- donner une liste de paramètres effectifs correspondant aux paramètres formels génériques.
Définition d'une unité générique
Une unité générique est composée de trois parties:
- la déclaration des paramètres génériques introduite par le mot clé generic,
- la spécification de l'unité générique,
- le corps de l'unité générique.
Définition d'un sous-programme générique
generic <partie générique> procédure <nom> (paramètres); procédure <nom> (paramètres) is <déclarations> begin <instructions> end <nom>;
Définition d'un package générique
generic <partie générique> package <nom > is <déclarations> private <déclarations d'objets privés> end <nom>; package body <nom> is <déclarations des types et variables locales> <corps des sous-programmes> begin <rien ou initialisation> end <nom >;
La création d'une instance d'un package est définie par:
package <identificateur> is new <nom du package générique>(<paramètres effectifs>);
La création d'une instance d'un sous-programme est définie par :
Procedure <identificateur> is new <nom de la procedure générique>(<paramètres effectifs>); function <identificateur> is new <nom de la fonction générique>(<paramètres effectifs>);
Paramètres génériques
Les paramètres de la généricité sont classés en trois types. Ils peuvent être:
- des types,
- des constantes ou des variables,
- des sous-programmes.
Les types
La Figure VI-1 résume les formes des paramètres formels de la généricité et définit quels types effectifs sont associables.
Formes des types de la généricité et des types effectifs compatibles
type DISCRET is (<>); --à associer à tout type discret (types entier et énumération)
type ENTIER is range <>; --à associer à tout type entier
type MODULAIRE is mod <>; --à associer à tout type modulaire
type FIXE is delta <>; --à associer à tout type point fixe
type FIXE_DECIMAL is delta <> digits <>; --à associer à tout type point fixe décimal
type FLOTTANT is digits <>; --à associer à tout type point flottant
type TABLEAU_CONTRAINT is array (INDEX) of ENTIER; --à associer à tout type tableau contraint de mêmes dimensions,
--types d'index, et type de composant
type TABLEAU_NON_CONTRAINT is array (INDEX range <>) of ENTIER; --à associer à tout type tableau non contraint de mêmes
--dimensions, types d'index et types de composants.
type USAGE_GENERAL is limited private; --à associer à n'importe quel type
type ELEMENT is private; --à associer à n'importe quel type autorisant l'affectation et le test d'(in)égalité
type PTR is access ELEMENT; --à associer à un type accès
EXEMPLE
Réalisation d'une procédure générique de recherche dans un tableau d'éléments de type discret.
generic
type ELEMENT is (<>); --le type des éléments du tableau
type INDEX is (<>); --le type de l'indice du tableau
type VECTEUR is array(INDEX range <>) of ELEMENT; --le type du tableau
procedure RECHERCHE(VEC : in VECTEUR ; VALEUR : in ELEMENT; OK : out BOOLEAN ; IND : out INDEX );
--la convention d'appel de la procédure
procedure RECHERCHE(VEC : in VECTEUR ; VALEUR : in ELEMENT; OK : out BOOLEAN ; IND : out INDEX ) is
TROUVE : BOOLEAN := FALSE;
begin
for I in VEC'range loop
TROUVE := VEC(I) = VALEUR;
if TROUVE then IND := I;
EXIT;
end if;
end loop;
OK := TROUVE;
end RECHERCHE;
Une utilisation possible :
with RECHERCHE; procedure UTIL is type ENTIER is range 50 ..100; type JOUR is (LUN, MAR, MER, JEU, VEN, SAM, DIM); type LISTE is array(1..100) of JOUR; type TAB is array(POSITIVE range 1..50) of ENTIER; --création d'une version pour un tableau de jours procedure RECH is new RECHERCHE(ELEMENT => JOUR, INDEX => INTEGER, VECTEUR => LISTE); --création d'une version pour un tableau d'entiers procedure RECH is new RECHERCHE(ELEMENT => ENTIER, INDEX => POSITIVE, VECTEUR => TAB); L : LISTE (LUN, MER, VEN, DIM); --Une liste de jours DAY : JOUR := VEN; RES : boolean; POSITION : INTEGER; T : TAB(1..5) := (2, 4, 6, 8, 10); -- Un tableau d'entiers INDICE : POSITIVE; begin RECH(VEC => L, VALEUR => DAY, OK => RES, IND => POSITION); RECH(VEC => T, VALEUR => 8, OK => RES, IND => INDICE); ... end UTIL;
Pour généraliser complètement cette procédure, il suffit de déclarer ELEMENT en private ou limited private. Dans ce dernier cas, le test d'égalité doit être redéfini.
On peut alors faire une recherche dans un tableau d'enregistrements (cas ou ELEMENT est privé) :
with RECHERCHE;
procedure UTIL is
type PERSONNE is record
AGE : POSITIVE;
NOM : STRING(1..5);
end record;
type TAB is array(POSITIVE range 1..50) of PERSONNE;
--création d'une version pour un tableau de personnes
procedure RECH is new RECHERCHE(ELEMENT => PERSONNE, INDEX => POSITIVE, ..., VECTEUR => TAB);
begin
...
end UTIL;
Constantes et variables
Ada permet de définir des constantes et des variables paramètres de la généricité. La déclaration de tels paramètres prend la forme d'une déclaration de constante, en ajoutant le mot clé in, ou d'une variable avec in out. Le mode out n'est pas possible.
Lorsqu'on utilise de tels paramètres, on doit faire correspondre au paramètre formel une constante ou une variable du même type.
L'avantage de cette classe de paramètres génériques est de permettre d'instancier une unité de programme en fournissant des limites constantes aux algorithmes ou aux objets abstraits.
Exemple
generic LIGNE : in INTEGER := 24; COLONNE : in INTEGER := 80; package TERMINAL is ...
Des instances possibles :
package MINITEL is new TERMINAL(24, 40); package IMPRIMANTE is new TERMINAL(66, 132); package CONSOLE is new TERMINAL;
Paramètres sous-programmes
La notion de sous-programmes en paramètres génériques est nécessaire pour deux raisons :
- en Ada, on ne peut utiliser des sous-programmes en tant que paramètres d'autres sous-programmes. Cette contrainte peut être contournée en Ada95 en utilisant des pointeurs sur les sous-programmes. Mais on peut passer des sous-programmes comme paramètres de la généricité.
- un type formel générique privé ou limité privé, n'autorise que l'utilisation des opérations :=, /=, = pour le type privé, et interdit toute opération pour le type limité privé, dans le corps de l'unité générique. Pour réintroduire des opérations interdites, on définit ces opérations sous la forme de sous-programmes paramètres génériques.
Les sous-programmes formels génériques sont décrits dans une partie générique comme une déclaration de sous-programme précédée de la clause with. Trois formulations sont possibles :
1ère forme:
generic ... with <nom de sous-programme>; package <nom du package> ...
Avec cette définition, il n'existe pas de valeur par défaut pour ce sous-programme. Lors de l'instanciation, le nom d'un sous-programme effectif est obligatoire.
2ème forme:
Un sous-programme de même nom que le paramètre formel peut être pris par défaut dans l'environnement d'instanciation .
generic ... with <définition d'une procédure ou fonction générique> is <>; package <nom du package> ...
3ème forme:
Un sous-programme de nom nom prédéfini ou redéfini est pris par défaut.
generic ... with <nom de sous-programme> is <nom>; package <nom du package> ...
Utilisation pour généraliser l'emploi de sous-programmes
Dans l'exemple suivant, la fonction INTEGRER calcule l'intégrale d'une fonction entre deux valeurs MIN et MAX. La fonction à intégrer constitue un paramètre générique. De plus, INTEGRER doit pouvoir être utilisée pour tout type flottant.
Spécification de la fonction générique INTEGRER
generic type FLOTTANT is digits <>; with function F ( X : FLOTTANT ) return FLOTTANT; function INTEGRER (MIN,MAX : FLOTTANT) return FLOTTANT;
Remarque
le sous-programme paramètre générique est une fonction à une variable, à associer à une fonction effective à une variable lors de l'instanciation.
Corps de la fonction générique INTEGRER
function INTEGRER(MIN, MAX : FLOTTANT) return FLOTTANT is RESULTAT : FLOTTANT; begin --tout ce qu'il faut pour calculer l'intégrale return RESULTAT; end INTEGRER;
Utilisation de la fonction générique
with INTEGRER;
procedure UTIL is
type REEL is digits 6;
--Définition de la fonction à intégrer
function A_INTEGRER ( V : REEL) return REEL is
begin
return X * X + 1.0;
end A_INTEGRER;
--Création d'une instance de INTEGRER
function INTEGRER_F is new INTEGRER(FLOTTANT => REEL; F => A_INTEGRER);
R : REEL;
begin
R := INTEGRER_F(MIN => 0.0, MAX : 0.1); --Appel de l'instance;
...
end UTIL;
Utilisation liée au type privé
Pour réaliser une procédure de tri d'un tableau d'éléments de type quelconque, il est nécessaire de définir quels sont les éléments à placer dans la partie générique.
La définition du tableau doit être suffisamment général : l'indice doit être de type discret et le type des éléments quelconque (entier, réel, article, ...).
Il est donc nécessaire d'utiliser un type privé pour définir le type des éléments du tableau. Or, pour réaliser un algorithme de tri, un opérateurs de comparaison (autre que /= et =) est indispensable.
Spécification d'un sous-programme de tri générique
generic
type OBJET is private; --tout type avec /=, =, :=
type INDICE is (<>); --tout type discret
type TABLEAU is array(INDICE) of OBJET; --tout tableau contraint
with function "<="(OP1, OP2 : in OBJET) return BOOLEAN is <>; --une fonction de comparaison
procedure TRI_TOUT(TAB : in out TABLEAU);
Corps du sous-programme de tri générique (méthode de sélection simple)
procedure TRI_TOUT(TAB : in out TABLEAU) is
J : INDICE; U : OBJET;
begin
for I in TAB'first..INDICE'pred(TAB'last) loop
J := I;
for K in INDICE'succ(I)..TAB'last loop
if TAB(K) <= TAB(J) then
J := K;
end if;
end loop;
U := TAB(J); TAB(J) := TAB(I);
TAB(I) := U;
end loop;
end TRI_TOUT;
Utilisation
with TRI_TOUT;
procedure ESSAI is
type NATIONALITE is (ITALIEN, FRANCAIS, ALLEMAND, BELGE);
type TAB_REEL is array(NATIONALITE) of FLOAT;
--une instance pour un tri par ordre croissant
procedure TRI_CROISSANT is new TRI_TOUT(OBJET => FLOAT, INDICE => NATIONALITE, TABLEAU => TAB_REEL);
--une instance pour un tri décroissant
procedure TRI_DECROISSANT is new TRI_TOUT(OBJET => FLOAT, INDICE => NATIONALITE, TABLEAU => TAB_REEL, "<=" => ">=");
T1 : TAB_REEL(1.0, 6.7, 4.5, 7.3); --un tableau de réels
begin
TRI_CROISSANT(TAB => T1);
TRI_DECROISSANT(TAB => T1);
...
end ESSAI;
Utilisation pour un tableau d'enregistrement
Pour des éléments de type enregistrement, il n'existe pas de fonction de comparaison <= prédéfinie. Cet opérateur doit être créé dans l'environnement de l'instanciation.
with TRI_TOUT;
procedure TRI_RECORD is
type personne is record
nom : STRING(1..4);
TAILLE : POSITIVE;
end record;
type TAB_PERS is array(1..3) of PERSONNE;
function "<="(P1, P2 : PERSONNE) return BOOLEAN is
begin
return P1.TAILLE <= P2.TAILLE ;
end "<=";
--lors del'instanciation, la fonction est facultative
procedure TRIER is new TRI_TOUT(OBJET => PERSONNE, INDICE => INTEGER, TAB => TAB_PERS);
T : TAB_PERS := (("TITI, 183), ("TOTO", 157), ("LULU", 120);
begin
TRIER(TAB => T);
...
end TRI_RECORD;
Utilisation d'une unité générique dans une autre unité générique
Dans la procédure TRI_TOUT, l'échange de deux variables est utilisée. Cette opération est souvent utilisée dans d'autres contextes et peu donc être générique.
Procédure d'échange générique
Generic
type ELEMENT is private;
procedure ECHANGE(A, B : in out ELEMENT);
procedure ECHANGE(A, B : in out ELEMENT) is
TEMP : ELEMENT;
begin
TEMP := A; A := B; B := TEMP;
end ECHANGE;
La procédure TRI_TOUT peut alors s'écrire :
with ECHANGE;
generic
type OBJET is private; --tout type avec /=, =, :=
type INDICE is (<>); --tout type discret
type TABLEAU is array(INDICE) of OBJET; --tout tableau contraint
with function "<="(OP1, OP2 : OBJET) return BOOLEAN is <>; --une fonction de comparaison
procedure TRI_TOUT(TAB : in out TABLEAU);
procedure TRI_TOUT(TAB : in out TABLEAU) is
procedure SWAP is new ECHANGE(OBJET);
J : INDICE; U : OBJET;
begin
for I in TAB'first..INDICE'pred(TAB'last) loop
J := I;
for K in INDICE'succ(I)..TAB'last loop
if TAB(K) <= TAB(J) then
J:= K;
end if;
end loop;
SWAP(TAB(I), TAB(J));
end loop;
end TRI_TOUT;
Les packages génériques
Les packages définissant des TDA ou des machines abstraites sont en générale indépendants des objets qu'ils manipulent. Ils doivent donc être génériques.
Les deux packages de définition d'une file s'écrivent alors :
generic
type ELEMENT is private;
package TDA_FILE is
type TYPE_FILE(TAILLE : POSITIVE) is limited private;
FILE_PLEINE, FILE_VIDE : exception;
--Sélecteurs;
function EST_VIDE(LA_FILE : in TYPE_FILE) return BOOLEAN;
function EST_PLEINE(LA_FILE : in TYPE_FILE) return BOOLEAN;
--Constructeurs;
procedure VIDER(LA_FILE : in out TYPE_FILE);
procedure AJOUTER(A_LA_FILE : in out TYPE_FILE ; V : in ELEMENT);
procedure RETIRER (DE_LA_FILE : in out TYPE_FILE ; V : out ELEMENT);
private
type TAB is array (POSITIVE range <>) of ELEMENT;
type TYPE_FILE(TAILLE : POSITIVE) is record
NB_ELEMENT : NATURAL :=0;
PREMIER : POSITIVE := 1;
DERNIER : POSITIVE := TAILLE;
TABLE : TAB(1..TAILLE);
end record;
end TDA_FILE;
package body TDA_FILE is
procedure VIDER(LA_FILE : in out TYPE_FILE) is
begin
LA_FILE.NB_ELEMENT := 0;
LA_FILE.PREMIER := 1;
LA_FILE.DERNIER := LA_FILE.TAILLE;
end VIDER;
function EST_VIDE(LA_FILE : in TYPE_FILE) return BOOLEAN is
begin
return LA_FILE.NB_ELEMENT = 0;
end EST_VIDE;
function EST_PLEINE(LA_FILE : in TYPE_FILE) return BOOLEAN is
begin
return LA_FILE.NB_ELEMENT = LA_FILE.TAILLE;
end EST_PLEINE;
procedure AJOUTER(A_LA_FILE : in out TYPE_FILE; V : in ELEMENT) is
N : POSITIVE renames A_LA_FILE.TAILLE;
begin
if EST_PLEINE (A_LA_FILE) then
raise FILE_PLEINE;
end if;
A_LA_FILE.DERNIER := A_LA_FILE.DERNIER rem N + 1;
A_LA_FILE.TABLE(A_LA_FILE.DERNIER) := V;
A_LA_FILE.NB_ELEMENT := A_LA_FILE.NB_ELEMENT + 1;
end AJOUTER;
procedure RETIRER(DE_LA_FILE : in out TYPE_FILE ; V: out ELEMENT) is
N : POSITIVE renames DE_LA_FILE.TAILLE;
begin
if EST_VIDE(DE_LA_FILE) then
raise FILE_VIDE;
end if;
V := DE_LE_FILE.TABLE(DE_LA_FILE.PREMIER);
DE_LA_FILE.PREMIER := DE_LA_FILE.PREMIER rem N + 1;
DE_LA_FILE.NB_ELEMENT := DE_LA_FILE.NB_ELEMENT - 1;
end RETIRER;
end TDA_FILE;
Exemple d'utilisation de ce package
with TDA_FILE; --Pas de clause use ici!!!
procedure UTIL is
package FILE_ENTIER is new TDA_FILE(ELEMENT => INTEGER);
use FILE_ENTIER;
F1 : TYPE_FILE(TAILLE => 100);
F2 : TYPE_FILE(TAILLE => 200);
...
begin
AJOUTER(A_LA_FILE => F1, V => 40);
AJOUTER(A_LA_FILE => F2, V => 50);
...
exception
when FILE_PLEINE => ...;
when FILE_VIDE => ...;
end UTIL;
Redéfinition de la machine abstraite
generic
TAILLE : POSITIVE :=100;
type ELEMENT is private;
package MACHINE_FILE is
FILE_PLEINE, FILE_VIDE : exception;
procedure VIDER ;
function EST_VIDE return BOOLEAN;
function EST_PLEINE return BOOLEAN;
procedure AJOUTER (V : in ELEMENT);
function RETIRER return ELEMENT;
end MACHINE_FILE;
package body MACHINE_FILE is
TABLE : array(TAILLE) of ELEMENT;
PREMIER, DERNIER : INDICE := 1;
procedure VIDER is
begin
PREMIER := 1;
DERNIER := 1;
end VIDER;
function EST_PLEINE return BOOLEAN is
begin
return ((DERNIER mod NB_ELEMENT) + 1) = PREMIER;
end EST_PLEINE;
function EST_VIDE return BOOLEAN is
begin
return PREMIER = DERNIER;
end EST_VIDE;
procedure AJOUTER (V : in ELEMENT) is
SUIVANT : INDICE := (DERNIER mod TAILLE) + 1;
begin
if SUIVANT = PREMIER then
raise FILE_PLEINE;
end if;
TABLE (DERNIER) := V;
DERNIER := SUIVANT;
end AJOUTER;
function RETIRER return ELEMENT is
v : ELEMENT;
begin
if EST_VIDE then
raise FILE_VIDE;
end if;
V := TABLE(PREMIER);
PREMIER := (PREMIER mod TAILLE) + 1;
return V;
end RETIRER;
end MACHINE_FILE;
Utilisation de ce package:
with MACHINE_FILE; procedure UTIL is package FILE_REEL is new MACHINE_FILE(200, FLOAT); package FILE_CHARACTER is new MACHINE_FILE(300, CHARACTER); begin FILE_REEL.AJOUTER(V => 10.0); --Ajoute un élément à la file de réels FILE_CHARACTER.AJOUTER(V => 'C'); --Ajoute un élément à la file de caractères ... end UTIL;
Dans l'environnement du langage, un certains nombres de package génériques sont disponibles. Leur définition est donnée en annexe.
Compilation séparée et composition incrémentale
Dans ce chapitre, nous étudions les aspects organisationnels. Les sous-programmes, les packages sont des unités permettant de décomposer logiquement un programme (en modules logiques). Ada met à la disposition du programmeur un ensemble d'outils permettant l'organisation physique du programme (en modules physiques).
La compilation séparée
Les sous-programmes, les packages sont des modules de compilation. S'ils sont définis dans un autre sous-programme ou un package, il peuvent être séparés physiquement de l'unité englobante en utilisant l'option separate. Cette option signale que le sous-programme se trouve physiquement dans un autre fichier.
Reprenons l'exemple du package MATRICES :
(La spécification de ce package reste inchangée.)
package MATRICES is type VECTEUR is array (POSITIVE range <>) of FLOAT; type MATRICE is array (POSITIVE range <>, POSITIVE range <>) of FLOAT; --Définition des opérations d'entrée sortie procedure LIRE(V : out VECTEUR); procedure LIRE(M : out MATRICE); procedure ECRIRE(V : in VECTEUR); procedure ECRIRE(M : in MATRICE); --Opérations de manipulation des vecteurs et des matrices function IDENTITE (TAILLE : in positive) return MATRICE; function "+"(V1, V2 : in VECTEUR) return VECTEUR; function "+"(M1, M2 : in MATRICE) return MATRICE; function "*"(M1, M2 : in MATRICE) return MATRICE; ... end MATRICES;
Dans le corps du package, on peut décider de séparer les opérations de manipulation des vecteurs et des matrices et de garder seulement les opérations d'entrée/sortie.
with text_io; use text_io;
with ADA.FLOAT_TEXT_IO; use ADA.FLOAT_TEXT_IO;
package body MATRICES is
--Définition d'une opération de lecture contrôlée d'un réel, interne au package;
procedure LIRE(R : out FLOAT) is
begin
loop
begin
put (">"); get (R);
SKIP_LINE;
exit;
exception
when OTHERS => SKIP_LINE;
PUT_LINE("Réponse invalide, entrez une valeur réelle");
end;
end loop;
end LIRE;
--Corps des opérations d'entrée sortie;
procedure LIRE(V : out VECTEUR) --lire un vecteur
begin
for I in V ' range loop
LIRE(V(I));
end loop;
end LIRE;
procedure LIRE(M : out MATRICE) is --lire une matrice
begin
for I in M'range (1) loop
for J in M'range (2) loop
LIRE(M(I, J));
end loop;
end loop;
end LIRE;
procedure ECRIRE(V : in VECTEUR) is --écrire un vecteur
begin
for I in V ' range loop
PUT(V(I));
end loop;
end ECRIRE;
procedure ECRIRE ( M : in MATRICE) is --écrire une matrice
begin
for I in M'range (1) loop
for J in M'range (2) loop
PUT(M(I, J));
end loop;
end loop;
end ECRIRE;
--Corps des opérations de manipulation définis séparemment
function IDENTITE(TAILLE : in positive) return MATRICE is separate;
function "+" (V1,V2 : in VECTEUR) return VECTEUR is separate;
function "*" (M1,M2 : in MATRICE) return MATRICE is separate;
--et la fonction "+" sur les matrices
end MATRICES;
Les corps des sous-programmes sont soit dans le même fichier, soit chacun dans un fichier. Il est bien sûr nécessaire de préciser à quelle unité un sous-programme séparé appartient à l'aide d'une clause separate(nom de l'unité) :
separate (MATRICES)
function IDENTITE ( TAILLE : in positive) return MATRICE is
RESULTAT : MATRICE(1..TAILLE, 1..TAILLE) := (others => (others => 0.0));
begin
for I in 1..TAILLE loop
RESULTAT(I, I) := 1.0;
end loop;
return RESULTAT;
end IDENTITE;
separate (MATRICES)
function "+"(V1, V2 : in VECTEUR) return VECTEUR is
begin
declare
R : VECTEUR (1..V1'LENGTH);
begin
for I in R'range loop
R(I) := V1(I) + V2(I);
end loop;
return R;
end;
end "+";
separate (MATRICES)
function "*"(M1, M2 : in MATRICE) return MATRICE is
begin
declare
R : MATRICE (1..M1'LENGTH(1), 1..M2'LENGTH(2));
CUMUL : FLOAT;
begin
for I in R'range (1) loop
for J in R'range (2) loop
CUMUL := 0.0;
for k in M1'range (2) loop
CUMUL := CUMUL + M1(I, K) * M2(K, J);
end loop;
R(I,J) := CUMUL;
end loop;
end loop;
return R;
end;
end "*";
Composition incrémentale : unité parent, unité enfant
Afin de faciliter la mise au point de très grosses applications et surtout leur évolution, la notion de packages hiérarchiques à été introduite. Il est possible de rajouter des fonctionnalités à un package, sans avoir à recompiler le package.
Reprenons l'exemple du package MATRICES : ce package contient des opérations d'entrées/sorties, des opérations de manipulation de matrices, des opérations de manipulation de vecteurs et naturellement, la définition des différents types nécessaires. On va donc définir un package parent pour la définition des types, puis des packages enfants pour chaque ensembles d'opérations. La définition d'un package enfant est donnée par : nom du package parent . nom du package enfant.
Définition du package parent :
package MATRICES is type VECTEUR is array (POSITIVE range <>) of FLOAT; type MATRICE is array(POSITIVE range <>, POSITIVE range <>) of FLOAT; end matrices;
Définition d'un package enfant pour les entrées /sorties :
package MATRICES.ES is procedure LIRE(V : out VECTEUR); procedure LIRE(M : out MATRICE); procedure ECRIRE(V : in VECTEUR); procedure ECRIRE(M : in MATRICE); end MATRICES.ES;
Définition d'un package enfant pour la manipulation des vecteurs :
package MATRICES.VECTEURS is function "+"(V1, V2 : in VECTEUR) return VECTEUR; end MATRICES.VECTEURS;
Définition d'un package enfant pour la manipulation des matrices :
packages MATRICES.MATRICES is function IDENTITE(TAILLE : in positive) return MATRICE; function "+"(M1, M2 : in MATRICE) return MATRICE; function "*"(M1, M2 : in MATRICE) return MATRICE; end MATRICES.MATRICES;
Chacun des ces packages enfants a un corps défini avec les mêmes algorithmes que précédemment. Ils ne sont donc pas rappelés.
L'utilisation de cette hiérarchie peut être faite de différentes façons. Si l'utilisateur n'a besoin que de la définition des types tableaux, il précisera seulement with MATRICES dans la clause de contexte de son unité. S'il souhaitent utiliser la définition des types tableaux et les opérations d'entrées/sorties, il précisera with MATRICES, MATRICES.ES ; etc...
Pour rajouter des opérations diverses sur les matrices, par exemple, il n'est pas nécessaire de modifier l'existant. On peut créer un package enfant au package MATRICES.MATRICES qui peut s'appeler MATRICES.MATRICES.DIVERS !
Approche objet
Concepts fondamentaux de la programmation orientée objet
La programmation structurée est basée sur une décomposition en actions.
La programmation orientée objet travaille avec une décomposition en objet.
les instructions élémentaires sont les mêmes, mais leur regroupement est différent.
On trouve dans l'approche objet trois principes fondamentaux :
- l'encapsulation : un objet regroupe à la fois se attributs et ses opérations associées,
- l'indépendance temporelle : le comportement d'un objet est indépendant du contexte dans lequel il est appelé,
- l'indépendance spatiale : les informations relatives à une même entité sont physiquement dans le même module.
- La programmation orientée objet apporte de nouvelles notions :
- notion de classe : une classe regroupe des objets ayant des propriétés communes (factorisation des propriétés),
- notion d'héritage : une sous-classe est définie à partie d'une classe avec des propriétés supplémentaires. La sous-classe hérite des propriétés et des opérations de la classe parente,
- notion de polymorphisme : permet d'écrire des programmes de même but, donc de même nom, mais avec des implémentations différentes selon la nature des objets sur lesquels ils portent,
- notion de liaison dynamique : qui est la capacité à associer le service surchargé correct en fonction de la référence de la classe,
- notion de généricité : pour décrire des données et des opérations indépendantes déléments de la classe.
Traduction en ADA
- une classe : un package contenant une définition d'un type étiqueté (tagged) sur la base d'un type enregistrement, et les opérations portant sur ce type,
- l'héritage : obtenu par dérivation du type étiqueté avec des attributs et des opérations supplémentaires, des méthodes de même spécification que dans la classe parente, mais adaptées au type dérivé,
- la liaison dynamique : une opération dans une classe est définie à l'échelle de la classe en utilisant l'attribut 'class dans la définition des paramètres.
Exemple
Définition de la classe ARTICLE
package CLASSE_ARTICLE is
type PRIX is delta 0.01 digits 9;
type QUANTITE is range 0..10000;
type ARTICLE is tagged record
REFERENCE : STRING(1..4);
DESIGNATION : STRING(1..10);
PRIX_HT : PRIX;
QUANTITE_STOCK : QUANTITE;
end record;
procedure GET (UN_ARTICLE : out ARTICLE);
procedure PUT (UN_ARTICLE : in ARTICLE);
function PRIX_TTC (UN_ARTICLE : in ARTICLE) return PRIX;
function VALORISER_STOCK(UN_ARTICLE : in ARTICLE'CLASS) return PRIX;
end CLASSE_ARTICLE;
with ADA.TEXT_IO; use ADA.TEXT_IO;
package body CLASSE_ARTICLE is
package ES_PRIX is new DECIMAL_IO(PRIX); use ES_PRIX;
package ES_QUANTITE is new INTEGER_IO(QUANTITE); use ES_QUANTITE;
procedure GET (UN_ARTICLE : out ARTICLE) is
begin
PUT("Donnez la reference sur 4 caracteres : ");
GET(UN_ARTICLE.REFERENCE); NEW_LINE;
PUT("Donnez la designation sur 10 caracteres : ");
GET(UN_ARTICLE.DESIGNATION);NEW_LINE;
PUT("Donnez le prix hors taxe : ");
GET(UN_ARTICLE.PRIX_HT); NEW_LINE;
PUT(" Donnez le stock : ");
GET(UN_ARTICLE.QUANTITE_STOCK); NEW_LINE;
end GET;
procedure PUT(UN_ARTICLE : in ARTICLE) is
begin
PUT("Reference : ");
PUT(UN_ARTICLE.REFERENCE); NEW_LINE;
PUT("Designation: ");
PUT(UN_ARTICLE.DESIGNATION); NEW_LINE;
PUT("Prix hors taxe : ");
PUT(UN_ARTICLE.PRIX_HT); NEW_LINE;
PUT("Stock :" );
PUT(UN_ARTICLE.QUANTITE_STOCK); NEW_LINE;
end PUT;
function PRIX_TTC(UN_ARTICLE : in ARTICLE) return PRIX is
begin
PUT_LINE("Application methode PRIX_TTC de CLASSE_ARTICLE");
return UN_ARTICLE.PRIX_HT * 1.0;
end PRIX_TTC;
function VALORISER_STOCK(UN_ARTICLE : in ARTICLE'CLASS) return PRIX is
begin
return PRIX_TTC(UN_ARTICLE) * INTEGER(UN_ARTICLE.QUANTITE_STOCK);
end VALORISER_STOCK;
end CLASSE_ARTICLE;
Définition de la sous-classe ARTICLE_LUXE
with CLASSE_ARTICLE; use CLASSE_ARTICLE;
package CLASSE_ARTICLE_LUXE is
type ARTICLE_LUXE is new ARTICLE with record
MARQUE : STRING(1..10);
end record;
procedure GET(UN_ARTICLE_LUXE : out ARTICLE_LUXE);
procedure PUT(UN_ARTICLE_LUXE : in ARTICLE_LUXE);
function PRIX_TTC(UN_ARTICLE_LUXE : in ARTICLE_LUXE) return PRIX;
end CLASSE_ARTICLE_LUXE;
with ADA.TEXT_IO;use ADA.TEXT_IO;
package body CLASSE_ARTICLE_LUXE is
procedure GET (UN_ARTICLE_LUXE : out ARTICLE_LUXE) is
begin
GET(ARTICLE(UN_ARTICLE_LUXE));
PUT("Donner la marque sur 10 caracteres : ");
GET(UN_ARTICLE_LUXE.MARQUE);NEW_LINE;
end GET;
procedure PUT(UN_ARTICLE_LUXE : in ARTICLE_LUXE) is
begin
PUT(ARTICLE(UN_ARTICLE_LUXE));
PUT("De marque : ");
PUT(UN_ARTICLE_LUXE.MARQUE);NEW_LINE;
end PUT;
function PRIX_TTC(UN_ARTICLE_LUXE : in ARTICLE_LUXE) return PRIX is
begin
PUT_LINE("Application methode PRIX_TTC de CLASSE_ARTICLE_LUXE");
return UN_ARTICLE_LUXE.PRIX_HT * 1.330;
end PRIX_TTC;
end CLASSE_ARTICLE_LUXE;
Définition de la sous-classe VETEMENT
with CLASSE_ARTICLE; use CLASSE_ARTICLE; package CLASSE_VETEMENT is type VETEMENT is new ARTICLE with null record; end CLASSE_VETEMENT;
Définition de la sous-classe ADULTE
with CLASSE_ARTICLE; use CLASSE_ARTICLE;
with CLASSE_VETEMENT; use CLASSE_VETEMENT;
package CLASSE_ADULTE is
type TAILLE is range 34..56;
type VETEMENT_ADULTE is new ARTICLE with record
TAILLE_ADULTE : TAILLE;
end record;
procedure GET(UN_VETEMENT_ADULTE : out VETEMENT_ADULTE);
procedure PUT(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE);
function PRIX_TTC(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE) return PRIX;
end CLASSE_ADULTE;
with ADA.TEXT_IO;use ADA.TEXT_IO;
package body CLASSE_ADULTE is
package ES_ENT is new INTEGER_IO(TAILLE);use ES_ENT;
procedure GET(UN_VETEMENT_ADULTE : out VETEMENT_ADULTE) is
begin
GET(ARTICLE(UN_VETEMENT_ADULTE));
PUT("Donner la taille : ");
GET(UN_VETEMENT_ADULTE.TAILLE_ADULTE);NEW_LINE;
end GET;
procedure PUT(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE) is
begin
PUT(ARTICLE(UN_VETEMENT_ADULTE));
PUT("De taille : ");
PUT(UN_VETEMENT_ADULTE.TAILLE_ADULTE); NEW_LINE;
end PUT;
function PRIX_TTC(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE) return PRIX is
begin
PUT_LINE("Application methode PRIX_TTC deCLASSE_ADULTE");
return UN_VETEMENT_ADULTE.PRIX_HT * 1.206;
end PRIX_TTC;
end CLASSE_ADULTE;
Définition de la sous-classe ENFANT
with CLASSE_ARTICLE; use CLASSE_ARTICLE;
with CLASSE_VETEMENT; use CLASSE_VETEMENT;
package CLASSE_ENFANT is
type AGE is range 0..16;
type VETEMENT_ENFANT is new VETEMENT with record
TAILLE_AGE : AGE;
end record ;
procedure GET(UN_VETEMENT_ENFANT : out VETEMENT_ENFANT);
procedure PUT(UN_VETEMENT_ENFANT : in VETEMENT_ENFANT);
function PRIX_TTC(UN_VETEMENT_ENFANT : in VETEMENT_ENFANT) return PRIX;
end CLASSE_ENFANT;
with ADA.TEXT_IO;use ADA.TEXT_IO;
package body CLASSE_ENFANT is
package ES_ENT is new INTEGER_IO(AGE);use ES_ENT;
procedure GET(UN_VETEMENT_ENFANT : out VETEMENT_ENFANT) is
begin
GET(ARTICLE(UN_VETEMENT_ENFANT));
PUT("Donner la taille en age : ");
GET(UN_VETEMENT_ENFANT.TAILLE_AGE); NEW_LINE;
end GET;
procedure PUT(UN_VETEMENT_ENFANT : in VETEMENT_ENFANT) is
begin
PUT(ARTICLE(UN_VETEMENT_ENFANT));
PUT("De taille : ");
PUT(UN_VETEMENT_ENFANT.TAILLE_AGE);
end PUT;
function PRIX_TTC (UN_VETEMENT_ENFANT : in VETEMENT_ENFANT) return PRIX is
begin
PUT_LINE("Application methode PRIX_TTC de CLASSE_ENFANT");
return UN_VETEMENT_ENFANT.PRIX_HT * 1.005;
end PRIX_TTC;
end CLASSE_ENFANT;
Programme de test
with CLASSE_ARTICLE, CLASSE_ARTICLE_LUXE, CLASSE_VETEMENT, CLASSE_ENFANT, CLASSE_ADULTE;
use CLASSE_ARTICLE, CLASSE_ARTICLE_LUXE, CLASSE_VETEMENT, CLASSE_ENFANT, CLASSE_ADULTE;
with ADA.TEXT_IO; use ADA.TEXT_IO;
procedure TEST1 is
package ES_FIXE is new DECIMAL_IO(PRIX); use ES_FIXE;
ARTICLE1 : ARTICLE_LUXE;
ARTICLE2 : VETEMENT_ADULTE;
ARTICLE3 : VETEMENT_ENFANT;
PRIX_DE_L_ARTICLE, VALEUR_DU_STOCK : PRIX;
begin
PUT_LINE("Traitement d'un article de luxe : ");
GET(ARTICLE1);
PUT(ARTICLE1);
PRIX_DE_L_ARTICLE := PRIX_TTC(ARTICLE1);
PUT(" Prix TTC : "); PUT(PRIX_DE_L_ARTICLE); NEW_LINE;
PUT_LINE(" Valeur du stock TTC : ");
VALEUR_DU_STOCK := VALORISER_STOCK(ARTICLE1);
PUT(VALEUR_DU_STOCK); NEW_LINE(2);
PUT_LINE("Traitement d'un vetement adulte :");
GET(ARTICLE2);
PUT(ARTICLE2);
PRIX_DE_L_ARTICLE := PRIX_TTC(ARTICLE2);
PUT("Prix TTC : "); PUT(PRIX_DE_L_ARTICLE); NEW_LINE;
PUT_LINE("Valeur du stock TTC : ");
VALEUR_DU_STOCK := VALORISER_STOCK(ARTICLE2);
PUT(VALEUR_DU_STOCK); NEW_LINE(2);
PUT_LINE("Traitement d'un vetement enfant : ");
GET(ARTICLE3);
PUT(ARTICLE3);
PRIX_DE_L_ARTICLE := PRIX_TTC(ARTICLE3);
PUT("Prix TTC : "); PUT(PRIX_DE_L_ARTICLE); NEW_LINE;
PUT_LINE("Valeur du stock TTC : ");
VALEUR_DU_STOCK := VALORISER_STOCK(ARTICLE3);
PUT(VALEUR_DU_STOCK); NEW_LINE(2);
end TEST1;