II. Procédures▲
Une procédure est un sous-programme. Écrire des procédures permet de découper
un programme en plusieurs morceaux.
Chaque procédure définit une nouvelle instruction, que l'on peut appeler en tout
endroit du programme. On peut ainsi réutiliser le code d'un sous-programme.
Lorsqu'on découpe un problème en terme de procédures, puis qu'on implémente
ces procédures, on fait ce qu'on appelle une analyse descendante : on va du plus
général au détail.
II-1. Procédure sans paramètre▲
II-1-1. Principe▲
Il s'agit simplement de donner un nom à un groupe d'instructions.
Ensuite, l'appel de ce nom à divers endroits du programme provoque à chaque
fois l'exécution de ce groupe d'instructions.
Exemple
PROGRAM
exemple1;
VAR
x, y, t : integer
;
{ Declaration de la procedure Echange_xy }
PROCEDURE
Echange_xy;
BEGIN
{ Corps de la procedure }
t := x; x := y; y := t;
END
;
BEGIN
{ Programme principal }
x := 3
; y := 4
;
writeln (x, ' '
, y);
Echange_xy; { 1er appel de la procedure }
writeln (x, ' '
, y);
Echange_xy; { 2eme appel de la procedure }
writeln (x, ' '
, y);
END
.
Ce programme affiche :
3 4
4 3
3 4
Remarques
- Le nom de la procédure est un indentificateur.
- On déclare toute procédure avant le BEGIN du programme principal.
II-1-2. Appels▲
On peut très bien appeler une procédure P1 depuis une procédure P2, mais il faut
que la procédure P1 aie été déclarée avant la procédure P2.
Exemple donnant le même résultat.
PROGRAM
exemple2;
VAR
x, y, t : integer
;
PROCEDURE
Affiche_xy;
BEGIN
writeln (x, ' '
, y);
END
;
PROCEDURE
Echange_xy;
BEGIN
t := x; x := y; y := t;
Affiche_xy;
END
;
BEGIN
x := 3
; y := 4
;
Affiche_xy;
Echange_xy;
Echange_xy;
END
.
Remarque
On peut aussi appeler une procédure depuis elle-même : c'est la récursivité, que
l'on n'étudiera pas dans ce module.
II-1-3. Variables locales▲
Les objets du programme qui ne sont utiles que dans la procédure peuvent être
définis dans les déclarations locales de la procédure.
Exemple Reprenons exemple1 et changeons t :
PROGRAM
exemple3;
VAR
x, y : integer
;
PROCEDURE
Echange_xy;
VAR
t : integer
; { Declaration locale }
BEGIN
t := x; x := y; y := t;
END
;
BEGIN
{ ... }
END
.
- Une variable déclarée localement n'existe que pendant l'exécution de la procédure, et ne sert que à cette procédure.
- Le programme principal n'a jamais accès à une variable locale de procédure.
- Une procédure n'a jamais accès à une variable locale d'une autre procédure.
Améliore la lisibilité du programme.
II-1-4. Portée des variables▲
Les variables déclarées dans le VAR du programme principal sont appelées variables globales. Elles existent pendant toute la durée du programme et sont accessible de partout.
Une variable locale à une procédure P, portant le même nom x qu'une variable
globale, masque la variable globale pendant l'exécution de P.
Exemple
PROGRAM
exemple4;
VAR
x : integer
;
PROCEDURE
Toto;
VAR
x : integer
;
BEGIN
x := 4
;
writeln ('toto x = '
, x);
END
;
BEGIN
x := 2
;
writeln ('glob x = '
, x);
Toto;
writeln ('glob x = '
, x);
END
.
Ce programme affiche :
glob x = 2
toto x = 4
glob x = 2
II-1-5. Effet de bord▲
Voici le scénario catastrophe :
- On est dans une procédure P et on veut modifier une variable x locale à P.
- Il existe déjà une variable globale ayant le même nom x.
- On oublie de déclarer la variable locale x au niveau de P.
- A la compilation tout va bien !
- A l'exécution, P modifié le x global alors que le programmeur ne l'avait pas voulu.
- Conséquence : le programme ne fait pas ce qu'on voulait, le x global a l'air de changer de valeur tout seul !
Erreur très difficile à détecter ; être très rigoureux et prudent !
II-2. Procédure paramètre▲
II-2-1. Pseudo-passage de paramètres▲
Ecrivons une procédure Produit qui calcule z = xy.
PROGRAM
exemple5;
VAR
x, y, z, a, b, c, d : real
;
PROCEDURE
Produit;
BEGIN
z := x * y;
END
;
On
veut se servir de Produit pour calculer c = ab et d = (a - 1
)(b + 1
).
BEGIN
write
('a b ? '
); readln (a, b);
x := a; y := b; { donnees }
Produit;
c := z; { resultat }
x := a-1
; y := b+1
; { donnees }
Produit;
d := z; { resultat }
writeln ('c = '
, c, ' d = '
, d);
END
.
Remarques
- L'écriture est un peu lourde.
- Il faut savoir que la procédure < communique > avec les variables x, y, z.
- Cela interdit de se servir de x, y, z pour autre chose que de communiquer avec la procédure ; sinon gare aux effets de bord !
- Deux sortes de paramètres : données et résultats.
II-2-2. Paramètrage▲
La solution élégante consiste à déclarer des paramètres à la procédure :
[ Dire que c'est équiv à 2.1 ; mettre les progs côte à côte ]
PROGRAM
exemple5bis;
VAR
a, b, c, d : real
;
PROCEDURE
Produit (x, y : real
; var
z : real
); { parametres }
BEGIN
z := x * y;
END
;
BEGIN
write
('a b ? '
); readln (a, b);
Produit (a, b, c); { passage de }
Produit (a-1
, b+1
, d); { parametres }
writeln ('c = '
, c, ' d = '
, d);
END
.
II-2-3. Comment ça marche▲
- l'appel, on donne des paramètres dans les parenthèses, séparés par des virgules,
et dans un certain ordre (ici a puis b puis c).
L'exécution de la procédure commence ; la procédure réçoit les paramètres et identifie chaque paramètre à une variable dans le même ordre (ici x puis y puis z).
[ Dessiner des fèches a --> x , b --> y , c --> z ] - Les types doivent correspondre ; ceci est vérifié à la compilation.
- Il y a deux sorte de passage de paramètres : le passage par valeur et le passage
par référence.
-Passage par valeur : à l'appel, le paramètre est une variable ou une expression.
C'est la valeur qui est transmise, elle sert à initialiser la variable correspondante dans la procédure (ici x est initialisé à la valeur de a et y à la valeur de b).
-Passage par référence : à l'appel, le paramètre est une variable uniquement (jamais une expression). C'est l'adresse mémoire (la référence) de la variable qui est transmise, non sa valeur. La variable utilisée dans la procédure est en fait la variable de l'appel, mais sous un autre nom (ici z désigne la même variable (zone mémoire) que a).
C'est le mot-clé var qui dit si le passage se fait par valeur (pas de var) ou
par référence (présence du var).
Pas de var = donnée ; présence du var = donnée/résultat. [ dessiner une double flêche c <--> z ]
Erreurs classiques
- Mettre un var quand il n'en faut pas : on ne pourra pas passer une expression en paramètre.
- Oublier le var quand il en faut un : la valeur calculée ne pourra pas < sortir > de la procédure.
Exemples d'erreurs à l'appel de Produit (a-1, b+1, d);
PROCEDURE
Produit (var
x : real
; y : real
; var
z : real
);
ne compile pas à cause du paramètre 1, où une variable est attendue et c'est une expression qui est passée.
PROCEDURE
Produit (x, y, z : real
);
produit une erreur à l'exécution : d ne reçoit jamais le résultat z car il s'agit de 2 variables distinctes.
- Portée des variables :
dans Exemple5bis, les paramètres x, y, z de la procédure Produit sont des variables locales à Produit.
Leur nom n'est donc pas visible de l'extérieur de la procédure.
Attention : redéclarer un paramètre comme variable locale ----> erreur à la compilation.
Exemple :
PROCEDURE
Produit (x, y : real
; var
z : real
);
VAR
t : real
; { déclaration d'une var locale : permis }
x : real
; { redéclaration d'un paramètre : interdit }
BEGIN
z := x * y;
END
;
II-2-4. Bons réflexes▲
Le seul moyen pour une procédure de communiquer avec l'extérieur, c'est à dire
avec le reste du programme, ce sont les variables globales et les paramètres.
Il faut toujours éviter soigneusement les effets de bords. Le meilleur moyen est de
paramétrer complètement les procédures, et d'éviter la communication par variables
globales.
Les variables de travail tels que compteur, somme partielle, etc doivent être locales
à la procédure, surtout pas globale.
Prendre l'habitude de prendre des noms de variables difféerents entre le programme
principal et les procédures : on détecte plus facilement à la compilation
les effets de bords.
Chaque fois que l'on appelle une procédure, on vérifie particulièrement le bon
ordre des paramètres et la correspondance des types. La compilation est très pointilleuse
sur les types, mais par contre elle ne détecte pas les inversions de paramètres
de même type.