Procédures et fonctions en Pascal

Un article de Mangue.org, l'encyclopéde libre.


Dans cet article vous allez apprendre comment écrire des procédures et des fonctions afin de rendre certaines parties de votre programme réutilisables tout en facilitant la lisibilité et la maintenance de celui-ci.


Sommaire

De quoi s'agit-il ?


Les procédures sont en quelque sorte des bouts de programmes que vous invoquez au sein de votre code. Nous avons déjà utilisé des fonctions telles que write, writeln...
En règle générale une fonction ne retourne aucun résultat, elle se contente d'effectuer un traitement précis, pas toujours un calcul, on parle plus d'action. Chaque procédure peut admettre un ou plusieurs arguments (ou même aucun) mais ne peut pas renvoyer directement de résultat. Si vous souhaitez néanmoins en retourner, il faudra utiliser des notions que nous verrons plus loin dans ce cours.


Les fonctions ressemblent aux procédures, à ceci près qu'elles retournent toujours une valeur. Vous avez aussi utilisé des fonctions prédéfinies, comme sqrt, dans les programmes que nous avons écrit ensembles.
Comme les procédures, les fonctions admettent un ou plusieurs arguments (ou aucun) et renvoient un résultat.


Vous pouvez vous demander à quoi correspond exactement un argument, et vous auriez raison puisque ce n'est expliqué dans aucun des cours précédents. LA notion d'argument en Pascal (et en programmation en général), est différente de la notion d'arguments en mathématiques.
L'argument est quelque chose de plus général qui se confond parfois avec le concept de variable globale ou locale que nous verrons en contrebas.


Présentation des procédures


Comme nous l'avons déjà dit une procédure permet (entre autres) de répéter plusieurs fois une même action aucours de notre programme. Nous souhaitons dans un premier temps écrire un programme qui affiche toutes les lettres de l'alphabet les unes après les autres au sein d'une boucle for.
Mais nous voulons que l'affichage de la lettre soit pris en charge par une procédure afin de faciliter les modifications ultérieures du programme :


program Alphabet;
 
(* déclaration de la variable *)
var Lettre : char;
 
(* définition de la procédure *)
procedure Affiche; (* la procédure n'a pas de paramètres *)
begin
  write(Lettre);
end;
 
(**** début du programme principal ****)
begin
  for Lettre := 'a' to 'z' do
    Affiche;
end.


Rien d'exceptionnel pour le moment, en effet il n'était pas très utile d'écrire une procédure pour si peu ! Nous souhaitons maintenant modifier le programme afin qu'il n'affiche qu'une lettre par ligne, rien de plus simple, il nous suffit de remplacer le write par un writeln dans le corps de notre procédure.
Vous commencez déjà à comprendre l'utilité des procédures et fonctoins, grace à elles nous allons pouvoir isoler certaines parties du programme afin de les modifier plus facilement. Maintenant nous souhaitons afficher sur la même ligne la lettre de l'alphabet suivie de son code ASCII. Les modifications à apporter au programme ne concernent que la procédure Affiche dans laquelle nous utiliserons un writeln(Lettre, ' ', ord(Lettre);


Supposons que quelqu'un d'autre ait écrit ce programme, cette personne a défini la procédure Affiche avant de déclarer la variable Lettre et m'a demandé sur mon mail pourquoi celà ne fonctionnait pas (ça risque d'arriver ça ;-))
Ceci s'explique par la portée des variables globales que vous déclarez. En Pascal vous pouvez utiliser dans vos programmes tout ce qui est déjà connu par le programme, ce qui implique que si vous déclarez la variable Lettre après la procédure Affiche alors vous ne pourrez pas l'utiliser dans cette procédure.


Astuce
Lorsqu'une variable est déclarée dans le programme principal elle est connue de toutes les procédures et fonctions déclarées par la suite. On parle de variable globale bien que ce terme soit quelque peu ambiguë comme nous le verrons par la suite chaque variable a une portée relative Vous en prendrez conscience dès que nous utiliserons plusieurs niveaux de procédures et fonctions dans nos programmes.


Variables locales


Une variable locale est différente d'une variable globale justement parce qu'elle n'est pas connue de tout le programme. De telles variables sont déclarées au sein d'une procédure par exemple comme nous le voyons dans l'exemple suivant chargé d'ordonner deux nombres que vous entrez au clavier :


program Nombres;
 
(* déclaration des variables *)
var A, B : integer;
 
(* définition de la procédure *)
procedure Ordonne; (* toujours pas d'arguments pour la procédure *)
var C : integer; (* variable locale à la procédure *)
begin
  if (A > B) then
  (* on ordonne les variables dans un ordre croissant *)
    begin
      C := A;
      A := B;
      B := C;
    end;
end;
 
 
(**** début du programme principal ****)
begin
  (* entrées *)
  write('Entrez deux nombres entiers : ');
  readln(A,B);
 
  (* traitement *)
  Ordonne;
 
  (* sorties *)
  writeln; (* ligne vide *)
  write('Vos nombres par ordre croissant : ');
  writeln(A, ' ', B);
end.


La variable C est inconnue dans le programme principal, si nous cherchions à l'utiliser ou à afficher sa valeur le compilateur nous signalerait tout simplement une erreur à ce niveau.
Mais que ce passerait-il si nous déclarions également une variable globale C ?


program Nombres;
 
(* déclaration des variables *)
var A, B : integer;
var C   : integer;
 
(* définition de la procédure *)
procedure Ordonne; (* toujours pas d'arguments pour la procédure *)
var C : integer; (* variable locale à la procédure *)
begin
  if (A > B) then
  (* on ordonne les variables dans un ordre croissant *)
    begin
      C := A;
      A := B;
      B := C;
    end;
end;
 
 
(**** début du programme principal ****)
begin
  (* entrées *)
  write('Entrez deux nombres entiers : ');
  readln(A,B);
  C := chr(225);
 
  (* traitement *)
  Ordonne;
 
  (* sorties *)
  writeln; (* ligne vide *)
  write('Vos nombres par ordre croissant : ');
  writeln(A, ' ', B);
  writeln; (* ligne vide *)
  writeln('Variable C : ', C);
end.


Ainsi la variable C locale à la procédure Affiche est indépendante de la variable globale C. Des modifications sur la première n'affecteront pas la seconde puisqu'elles utilisent des zones mémoires différentes pour stocker leurs valeurs.


Remarque
N'oubliez pas que le concept de variable est abstrait en programmation et que pour le compilateur le nom associé n'est que symbolique. En vérité tout fonctionne par référence à des adresses mémoires au sein du programme.
la variable globale C et la variable locale du même nom ne désignent pas du tout la même adresse mémoire, seul leur nom est le même ; mais ce nom n'existe pas pour le compilateur, seul le programmeur le voit.


Paramètres transmis par valeur


En pratique il convient d'éviter les variables globales car ils nuisent à la compréhension du code source. On préfère donc utiliser des paramètres pour les fonctions et les procédures qui en ont besoin.
De plus celà permet de bien comprendre quelles valeurs sont utilisées par quelle(s) procédure(s) (ou fonction).


program UnPeuDeMusique;
 
(* définition du type *)
type Note = (ut,re,mi,fa,sol,la,si);
    (* on ne peut pas utiliser do qui est un mot-clé *)
 
(* déclaration de la variable *)
var Son  : note;
 
(* déclaration de la procédure permettrant d'afficher les notes *)
procedure AfficheNote(N : note); (* procédure avec un argument *)
begin
  case N of
    ut  : write('do ');
    re  : write('re ');
    mi  : write('mi ');
    fa  : write('fa ');
    sol : write('sol ');
    la  : write('la ');
    si  : write('si ');
  end;
end;
 
(**** début du programme principal ****)
begin
  writeln('Un peu de musique :');
  for Note := ut to si do
    AfficheNote(Note);
end.


Astuce
Vous percevez ici l'utilité de définir vos types énumérés. L'intérêt était faible dans le cours précédent mais dorénavent vous ne pourrez plus vous ne passer (si vous voulez faciliter la lisibilité de vos programmes) ;-)


N est le paramètre de la procédure AfficheNote, il est donc inconnu partout ailleurs dans le programme. Vous l'utiliserz au sein de la procédure un peu comme vous utiliseriez une variable locale, à ceci près que sa valeur vient de l'appel de la procédure dans le programme principal.


Si vous modifiez la valeur d'un paramètre transmis comme nous l'avons vu jusqu'à présent (on parle de paramètre transmis par valeur), sa valeur ne sera pas affectée dans le programme principal.
Essayez d'ajouter l'instruction N := ut; dans le corps de la procédure AfficheNote. Vous verrez que ceci n'a aucun effet sur le programme principal qui poursuit son exécution normale.


Paramètres transmis par adresse


Nous savons que les valeurs du type char sont codées suivant une table de caractères (ASCII) qui associe à chacun d'entre eux un rang. Nous souhaitons écrire une procédure qui modifie son argument de manière à ce qu'il représente le caractère suivant.
Si nous écrivons une procédure qui prends en argument ce caractère et le modifie avant de rendre la main au programme principal rien ne se passera.


Il est nécessaire pour celà de passer l'argument par adresse afin que les modifications éventuelles de sa valeur aient cours dans le programme principal.
Pour celà il faut introduire le mot clé var dans le prototype de la procédure afin que l'argument correspondant soit transmis par adresse et non plus par valeur comme précédemment.


Les deux types de transmission des arguments


Lorsque vous passez des paramètres par valeur, une nouvelle zone mémoire est créée et la valeur du dit-paramètre y est copiée. Dans le cas d'un passage par adresse, aucune nouvelle zone n'est crée, les modifications du paramètre au sein de la procédure auront un effet sur la valeur dans le programme principal.
Nous sommes maintenant en mesure d'écrire le programme que nous souhaitons afin d'obtenir le successeur d'un caractère passé en argument :


program CaractereSuivant;
 
(* définition de la procédure *)
procedure Suivant(var C : char); (* paramètre transmis par adresse *)
begin
  C := chr(ord(C)+1);
end;
 
(* déclaration de la variable *)
var Lettre : char;
 
(**** début du programme principal ****)
begin
  write('Entrez un caractère : ');
  readln(Lettre);
 
  Suivant(Lettre);
 
  writeln('Le caractère suivant est : ', Lettre);
end.


Remarque
Nous avons, dans cet exemple, déclaré la variable Lettre après avoir défini la procédure Suivant. Ceci permet de faire en sorte que la portée de Lettre se limite effectivement au programme principal.


Danger
Si vous essayez d'appeller la procédure Suivant avec une constante comme argument, une erreur sera générée par le compilateur. Vous savez déjà que les constantes ne sont pas des objets stockés en mémoire et qu'elles n'ont pas d'adresse ;)


Les fonctions


Les fonctions peuvent être considérées comme un cas particulier des procédures. La principale différence réside dans le fait qu'il est possible de renvoyre une valeur.
Pensez par exemple à la fonction sqrt qui prend en argument un nombre et vous renvoie comme valeur sa racine carrée.


Plutôt que de vous tenir un long discours, je vous propose tout simplement de réécrire la procédure Suivant sous forme de fonction :


program CaractereSuivant;
 
(* définition de la procédure *)
function Suivant(C : char) : char;
begin
  Suivant := chr(ord(C)+1);
end;
 
(* déclaration de la variable *)
var Lettre : char;
 
(**** début du programme principal ****)
begin
  write('Entrez un caractère : ');
  readln(Lettre);
 
  writeln('Le caractère suivant est : ', Suivant(Lettre));
end.


Comme pour les procédures il est possible de passer les arguments par adresse ou bien par valeur.


Astuce
Parfois vous verrez des programmes dans lesquels les paramètres sont passés par adresse même si on ne les modifie pas.
Le principal intérêt est une économie de mémoire, ce qui entraine souvent un gain de performances du programme en général.


Il y a pourtant des limitations sur les fonctions. Elles ne peuvent renvoyer qu'une seule valeur à la fois et celle-ci doit appartenir à un type de base du Pascal.
Il est donc impossible de renvoyer une valeur de type note ou jour (pour ne prendre que des exemples des cours précédents)


Le type sous-programme


Il existe en Pascal un concept très intéressant permettantde déclarer des variables d'un type nouveau, le type sous-programme.
Ce concept fût introduit avec la version 5 du Turbo Pascal. Concrètement celà permet de passer en paramètre d'une procédure ou fonctoin une autre procédure ou fonction justement.


Voici un exemple très simple qui plaira aux plus matheux d'entre vous. Nous avons deux fonctions mathématiques prenant chacune un argument de type entier et renvoyant une valeur entière.
Ces deux fonctions sont connues par le programme principal mais on ne sait pas laquelle l'utilisateur voudra utiliser au moment de l'exécution, nous lui laissons le choix. Essayez de comprendre par vous même comment ceci fonctionne :


program ChoixDeLaFonctionAExecuter;
 
(* définition du type fonction *)
type fonction = function(a : integer) : integer;
 
(* définition des fonctions *)
 
{$F+}
function f1(x : integer) : integer;
begin
  f1 := x + 1;
end;
 
function f2(x : integer) : integer;
begin
  f2 := x - 1;
end;
{$F-}
 
 
(* déclaration des variables *)
var f : fonction; (* fonction à exécuter *
     x : integer; (* paramètre de la fonction à exécuter *)
     n : integer; (* choix de la fonction à exécuter *)
 
(**** début du programme principal ****)
begin
  write('Valeur de x : ');
  readln(x);
  write('Fonction à exécuter (1 ou 2) : ');
  readln(n);
 
  if (n=1) then
    f := f1
  else
    f := f2;
 
  write('Valeur renvoyée par la fonction : ');
  writeln(f(x));
end.


Malheureusement peu de compilateurs en dehors du Turbo PAscal utilisent ce concept, c'est la raison pour laquelle vous n'en verrez pas d'autres exemeples dans ce cours.
Si vous avez des questoins n'oubliez pas que le forum est à votre disposition ;)


Remarque
Dans ce programme nous utilisons la directive {$F} de manière locale. Pour plus de détails lisez l'annexe correspondante. Tout ce que vous avez besoin de savoir pour le moment est que celà permet d'accéder à l'adresse de la fonction depuis n'importe où dans le programme.
Si nous n'avions pas utilisé cette directive il aurait été impossible de stocker les fonctions f1 et f2 dans f
Outils personels