PROCEDURES ET FONCTIONS


Nous avons deja vu un certain nombre de procedures (ex WRITELN) et fonctions (SQRT, SIN...) predefinies par le compilateur. Mais si l'on en desire d'autres, il suffit de les definir.

GENERALITES

On peut regrouper un ensemble d'instructions sous un meme nom. On forme alors un sous-programme ou procedure. On utilise les procedures :

* chaque fois qu'une meme suite d'instructions doit etre repetee plusieurs fois dans un programme,

* quand une suite d'instruction forme une action globale. Le programme est alors plus clair et les erreurs plus facilement detectables.

Pour pouvoir utiliser une procedure, il faut d'abord la declarer. La declaration des procedures et fonctions se fait apres toutes les autres declarations.

structure d'une entite de programme (routine) :

entete 
declaration des :
	labels; {pour les GOTO, deconseille}
	constantes;
	types;
	variables;
definition des sous-routines; {sous-programmes}
BEGIN
  instructions
END
le programme principal comme les procedures et les fonctions ont toujours cette structure. L'entete est composee d'un mot clef (PROGRAM, PROCEDURE ou FUNCTION), suivi de l'identificateur (nom) de la routine, et de la liste des arguments entre parentheses. Les arguments forment la liaison avec l'exterieur de la routine (clavier, ecran et eventuellement fichiers pour le programme).


PROGRAM remplir (output);


{entete du prog principal}

var i:integer;

{declarations prog princ.}



{dont declaration LIGNE}

PROCEDURE ligne(n:integer);

{entete de la procedure}

var j:integer;

{declarations procedure}

BEGIN

{corps de la procedure}

for j:=1 to n do write('*');



writeln



END;



BEGIN

{instructions du prog princ}

for i:=1 to 25 do ligne(70)



END.


la procedure LIGNE ecrit N caracteres '*' sur une meme ligne. Le programme remplit donc l'ecran (25 lignes 70 colonnes) d'etoiles.

On peut appeler une procedure declaree dans une routine n'importe ou dans cette routine en indiquant simplement son nom comme si c'etait une instruction. A l'appel d'une procedure, le programme interrompt son deroulement normal, execute les instructions de la procedure, puis retourne au programme appelant et execute l'instruction suivante. Tout ce passe donc comme si le nom de la procedure etait remplace dans le programme par les instructions de la procedure (avec n=70).

PORTEE DES DECLARATIONS

Celle-ci est symbolisee dans l'exemple ci-dessus par deux cadres : la variable I et la procedure LIGNE (avec un argument entier) sont declarees dans REMPLIR, et donc connues dans tout le programme (rectangle exterieur). Par contre N et J sont declares dans LIGNE et ne sont connus (et utilisables) que dans le rectangle interieur.

En d'autres termes :

* Une variable est LOCALE pour une procedure X si elle est declaree dans X. Elle n'existe que dans X (et dans les procedures declarees a l'interieur de X). La routine qui comporte la procedure X ne peut donc pas acceder a cette variable locale.

* Une variable est GLOBALE pour une procedure X si elle est declaree dans une routine ENGLOBANT la procedure X. Elle peut etre utilisee dans la procedure. La modifier la modifie egalement dans le routine appelante (englobante).

Si l'on avait declare une variable I dans la procedure LIGNE (au lieu de N ou J), celle-ci aurait ete locale a la procedure, c'est a dire que, dans le programme principal, I designe une autre case memoire que dans la procedure. Modifier la variable locale I ne modifie pas la variable globale I (momentanement inaccessible).

Rq : Ceci s'applique a toutes les declarations. En particulier, une procedure declaree localement a l'interieur d'une procedure est indefinie a l'exterieur.

ARGUMENTS (OU PARAMETRES)

Les echanges d'informations entre une routine appelant une sous-routine peuvent se faire par l'intermediaire des variables globales. Mais il est beaucoup plus interessant d'utiliser les PARAMETRES :

Ex : PROGRAM machin (input,output);
     VAR a,b,c,d:real;
       PROCEDURE aff_somme(x,y:real);
       var z:real;
       begin
         z:=x+y;
         writeln(x ,' + ', y ,' = ', z)
       end;
     BEGIN                 { programme principal }
       writeln('entrez 4 valeurs : ');
       readln(a,b,c,d);
       aff_somme(a,b); aff_somme(3,5); aff_somme(c+a,d)
     END.
En appelant AFF_SOMME(A,B), la procedure prend pour X la valeur de A, et pour Y la valeur de B. On dit que les arguments sont "passes par valeur". Mais si la procedure modifiait X ou Y, A et B ne seraient pas modifies dans le programme appelant. Pour repercuter les modifications des arguments, il faut les declarer comme "variables" (ils sont alors dits "passes par adresse").
ex : procedure echange(VAR x,y:real);
     var z:real;
     begin
       z:=x;
       x:=y;
       y:=z
     end; {cette procedure echange les contenus des 2
arguments}

LES FONCTIONS

Tout ce qui a ete dit pour les procedures s'applique egalement aux fonctions. La difference avec une procedure est qu'une fonction renvoie un resultat. L'entete est du type :

FUNCTION nom_fonction (liste_parametres):type_de_la_fonction

la liste des parametres (en general passes par valeur) est de la meme forme que pour une procedure, le type de la fonction etant le type du resultat retourne. On retourne le resultat par :

NOM_FONCTION := ... Cette affectation ne peut se faire qu'une seule fois par appel a la fonction.

ex : program classer(input,output);
     var a,b,c:real;
     function MAX(x,y:real):real;
       begin
         if x>=y then MAX:=x else MAX:=y
       end;
     begin
       writeln('entrez deux valeurs : ');
       readln(a,b);
       c:=max(a,b);
       writeln('le plus grand est ',c)
     end.
La fonction MAX a 2 parametres reels (X et Y) et renvoie un reel.

RECURSIVITE

C'est ainsi que l'on appelle le fait qu'une routine puisse s'appeler elle-meme.

ex : function factorielle(n:integer):integer;
     begin
       if n<=1 then factorielle:=1
               else factorielle:=n*factorielle(n-1)
     end;
Par exemple en appelant factorielle(3), on calcule 3*factorielle(2). Or factorielle(2)=2*factorielle(1), qui lui vaut 1. Donc factorielle(3)=3*(2*1) (ce qui me parait juste). Faites un petit dessin, a chaque appel on recree de nouvelles variables locales, donc on obtient 3 cases N distinctes valant 3, 2 et 1, on les supprime petit a petit en passant sur le END.

Rq : Il faut toujours verifier qu'en aucun cas on ne puisse avoir une boucle infinie qui bloquerait la machine. Ce serait le cas en mettant le test IF N=1 et en appelant factorielle pour une valeur negative ou nulle.

Une procedure devant toujours etre declaree pour pouvoir etre utilisee, on utilise FORWARD pour les cas de recursivite passant par 2 procedures :

function prem(a,b:real):boolean; FORWARD;
{declaration anticipee de l'entete }
procedure deux(x,y:real);
var bool:boolean;
begin
  ......
  bool:=prem(x,y); {on peut utiliser PREM car deja declaree}
  ......
end;
function prem; 
{ne plus donner les arguments car deja declares}
begin
  ......
  if pas_fini then deux(a,b);  (* DEUX deja
declaree*)
  ......
end;
EXERCICE (rec) ecrire le programme qui calcule le determinant d'une matrice carree NxN sachant que celui-ci vaut :
                         n
                DETn =  Sigma  (-1)i+1 .M[i,1].DETn-1

                        i=1
ou M[i,1] est l'element de la matrice (ligne i, 1ere colonne),
DETn-1 est le determinant de la sous-matrice d'ordre n-1 obtenu en otant la ligne i et la 1ere colonne.

Le determinant d'une matrice 1x1 est son seul element.

On utilisera bien evidement une fonction recursive, et l'on separera le calcul de sous-matrice dans une procedure.

Rq : Il existe des methodes numeriques permettant d'acceder au resultat beaucoup plus rapidement que par cette methode.