next up previous
Next: Les fichiers Up: Initiation au Langage Fortran Previous: Les tableaux

Sous-sections

Fonctions et subroutines

Premier objectif

Il arrive fréquemment que l'on doive faire plusieurs fois la même chose au sein d'un même programme, mais dans un contexte différent. Par exemple :

Second objectif

On a un problème décomposable en plusieurs sous problèmes. On cherche à « enfermer » chaque sous problème dans un bloc et à faire communiquer ces blocs.

Exemple : écrire un programme qui lit trois matrices au clavier, en fait le produit, puis affiche le résultat. On écrira trois blocs de base :

Les objets FORTRAN correspondants à ces blocs sont les subroutines ou les  fonctions.

On voit que chacun de ces blocs peut être écrit séparément, et qu'il est relié à l'extérieur par des « portes » d'entrée/sortie repérées par un nom. Persuadons-nous que ce nom n'a de sens que pour le bloc, et qu'il est là pour identifier une entrée ou une sortie.

Les connexions des boites avec l'extérieur sont appelés en FORTRAN « paramètres formels », et seront traités comme des variables dans les instructions exécutables.

Avant d'aller plus loin, montrons comment utiliser les trois blocs définis ci-dessus pour résoudre le problème proposé.

Symboliquement cela revient à connecter les blocs comme le montre la figure 10.1.


  
Figure 10.1: Connexion de blocs fonctionnels pour réaliser un programme.
\includegraphics[width=\linewidth]{mulmat.eps}

Les subroutines

C'est une séquence d'instructions appelable d'un point quelconque du programme. Elle peut être appelée depuis le programme principal, ou depuis une autre subroutine ou fonction.

Une subroutine est définie par :

Écriture d'une subroutine

subroutine nomsub(pf1, pf2, pf3, ...)
type pf1
type pf2
type pf2
...
Déclaration des variables locales
...
Instructions exécutables
...
return
end

Les instructions de la subroutine peuvent manipuler :

Les instructions de la subroutine ne peuvent pas manipuler les variables locales du programme principal ou d'une autre subroutine ou fonction.

Appel d'une subroutine

L'appel d'une subroutine se fait depuis un bloc fonctionnel quelconque (programme principal, subroutine, fonction) avec l'instruction call.

call nomsub (v1, v2, v3, ...)

Les arguments v1, v2, v3 peuvent être :

Très important

Les types des arguments v1, v2, v3,... doivent correspondre exactement à ceux des paramètres formels pf1, pf2, pf3,...

Exemple

Écrire une subroutine qui calcule les coordonnées polaires associées à des coordonnées cartésiennes (x,y).


\begin{displaymath}\begin{array}{ll}
r=\sqrt{x^2+y^2} & \\
\theta=\arctan{\left...
...} \\
\theta=-\pi/2 & \mbox{$x=0$\space et $y<0$ }
\end{array}\end{displaymath}


  
Figure 10.2: Code source d'une subroutine de calcul de coordonnées polaires à partir de coordonnées cartésiennes.
      subroutine polar(x, y, r, theta)
      
      double precision x, y, r, theta
      
      double precision pi
      double precision temp
      
      pi=4*datan(1d0)
      
      r=dsqrt(x**2+y**2)
      
      temp=datan(y/x)
      if (x.gt.0d0) then
        theta=temp
      else if (x.lt.0d0) then
        theta=-temp
      else if (x.eq.0d0) then
        if (y.ge.0d0) then
          theta=pi/2
        else
          theta=-pi/2
        endif
      endif
      
      return
      end

Le figure 10.2 donne un code source possible de cette subroutine.

Un exemple d'appel de cette subroutine dans le programme principal :

      programm
      ...
      double precision a, b, rho, phi
      ...
      call polar (a, b, rho, phi)
      ...
      end

Remarques très importantes

En particulier, s'il y a dans le programme principal des variables locales appelées x, y, r ou theta, elles n'ont rien à voir avec les paramètres formels de la subroutine.

En revanche, il est possible de passer ces variables locales en tant qu'arguments d'appel à polar :

      call polar (x, y, rho, phi)

La variable locale x du programme principal est alors passée à la subroutine via son premier paramètre formel, qui incidemment s'appelle aussi x, mais les deux objets sont bien distincts.

Exercice 1

Réécrire le programme de résolution des équations du second degré avec des subroutines.

Exercice 2

Dans le programme de la figure 10.3, il y a trois erreurs ô combien classiques. Corrigez-les, puis répondez ensuite aux questions suivantes :

Complétez ensuite les subroutines pour qu'elles calculent effectivement n! et Cnp.


  
Figure 10.3: Programme de calcul de n! et de Cnp (à compléter).
      program factcnp
      
      do i=1,100
        call factor(i, facti, ifaux)
        write(*,*) i, facti
      enddo
      
      write(*,*)
     &  'Entrez deux entiers i et j'
      read(*,*) i, j
      
      call calcnp (i, j, cij)
      end
      
      subroutine factor(n, fact, ierr)
      
      integer n, ierr
      double precision fact
      ...
      return
      end
      
      subroutine calcnp (n, p, cnp, ierr)
      implicit double precision (a-h,o-z)
      integer n, p, cnp
      ...
      return
      end

   
Les fonctions

Une fonction est en tout point identique à une subroutine, mais son nom contient en plus une valeur. C'est-à-dire qu'au lieu de l'appeler par call, on l'utilise à droite du signe = dans une instruction d'affectation.

On avait déjà vu les fonctions prédéfinies du FORTRAN, par exemple datan, qui n'a qu'un paramètre formel :

      pi=4*datan(1d0)

Valeur de retour

Puisque le nom de la fonction contient une valeur, cette valeur doit être typée. Par exemple datan renvoie une valeur double precision. En plus du type de ses paramètres formels, la définition d'une fonction doit donc décrire le type de la valeur qu'elle retourne.

La valeur de retour de la fonction sera affectée dans le corps de la fonction, comme si le nom de la fonction était une variable ordinaire.

Écriture d'une fonction

type function nomfonc (pf1, pf2, ...)
type pdf1
type pdf2
...
déclarations des variables locale
...
instructions exécutables
! devrait contenir quelque chose comme : nomfonc = ...
...
return
end

Le type peut être omis auquel cas le type de la fonction est fixé par les règles par défaut.

Utilisation d'une fonction

Une fonction est utilisable dans tout autre bloc fonctionnel, comme une variable qui aurait des arguments. Comme pour les subroutines, il est indispensable de respecter la correspondance entre les arguments passés à la fonction et ses paramètres formels.

Attention : il faut non seulement déclarer le type de la fonction lors de sa définition, mais aussi dans tous les blocs fonctionnels où on l'utilise. Si cette déclaration est absente, les règles de typage automatiques du bloc fonctionnel courant s'appliquent.

Exemple 1

Fonction qui calcule le rayon-vecteur $r=\sqrt {x^2+y^2}$ associé à un couple (x,y) : figure 10.4.


  
Figure 10.4: Calcul de $r=\sqrt {x^2+y^2}$ par une fonction.
      program test
      
      double precision abscis, ordonn, r
      double precision rayon
      
      read(*,*) abscis, ordonn
      r=rayon(abscis, ordonn)
      write(*,*) r
      end
      
      double precision function rayon (x, y)
      
      double precision x, y
      
      rayon=dsqrt(x**2+y**2)
      
      return
      end

Exemple 2

Fonction qui saisit un caractère au clavier : figure 10.5.


  
Figure 10.5: Fonction de lecture d'un caractère au clavier.
      program test
      character c, litcar
      
      do while (litcar() .ne. 'q')
        write(*,*) 'On continue'
      enddo
      end
      
      character function litcar()
      read(*,*) litcar
      return
      end

Mécanisme de passage des arguments à une subroutine ou une fonction


  
Figure 10.6: Passage des paramètres dans l'exemple de la fonction polar.
\includegraphics[width=\linewidth]{passageparam.eps}

Les variables locales des divers blocs fonctionnels sont stockées à une adresse mémoire fixée par le compilateur. Cette adresse mémoire est un grand entier, qui est « le numéro de bureau » de la variable.

Certains bureaux sont plus grands que d'autres. Une variable integer ou real occupera un bureau à 4 cases, une variable double precision un bureau à 8 cases, un vecteur de 100 real, un bureau de $4
\times 100$ cases, une matrice de $50 \times 50$ double precision un bureau de $50 \times 50 \times 8$ cases.

Mais dans tous les cas, l'adresse de la variable est l'adresse de la première de ces cases.

A un paramètre formel de subroutine ou de fonction est associé en mémoire un nombre de cases suffisant pour stocker une adresse (4 octets sous UNIX). Lors d'un appel à une subroutine ou fonction, l'adresse du premier argument est écrite à l'emplacement réservé au premier paramètre formel, idem pour le second, le troisième, etc.

La figure 10.6 illustre ceci dans l'exemple de la subroutine polar.

Lorsque dans le corps de la subroutine, le programme rencontrera le paramètre formel x de type double precision à droite de =, il ira lire 8 octets à partir de l'adresse contenue dans x (de 10000 à 10007), c'est-à-dire la valeur de a.

Lorsqu'il rencontrera une affectation du paramètre formel theta, il ira écrire les 8 octets à partir de l'adresse contenue dans theta (de 10024 à 10031), c'est-à-dire phi.

D'où l'importance de respecter le nombre et le type d'arguments.

   
L'instruction common

On a vu que par défaut les variables d'un bloc fonctionnel lui étaient locales, donc inconnues des autres blocs. Il existe un moyen d'étendre la portée d'une variable à plusieurs blocs fonctionnels : le common.

Un common comporte un nom et une liste de variables. Les variables de cette liste seront connues dans tous les blocs fonctionnels où l'on écrit le common.

Syntaxe

common/nomcom/v1, v2, v3...

Remarques

Exemples


  
Figure 10.7: Exemple d'utilisation de l'instruction common.
      program test
      
      double precision pi
      real a, b
      
      common /trig/ pi
      common /bidon/ a, b
      
      pi=4*datan(1d0)
      ...
      end
      
      subroutine truc (x,y)
      
      common /trig/ pi
      common /bidon/ u, v
      
      double precision pi
      real u, v
      ...
      y=x*tan(pi*u/v)
      ...
      return
      end

Dans l'exemple de la figure 10.7, on voit que les noms des variables du common bidon sont différentes dans le programme principal et dans truc.

Mais cela est correct puisqu'il y a 2 reals de chaque coté. u et a représentent exactement le même objet car ils correspondent à la même zone mémoire. Cela dit, il vaut mieux garder les mêmes noms partout.

Paramètres formels de type tableau

On peut passer des tableaux à des subroutines ou fonctions si celles-ci sont conçues pour les recevoir. Comment déclare-t-on des paramètres formels de type tableaux ? Cela dépend du nombre d'indices.

vecteur v (1 indice)
: on n'a pas besoin de la taille de déclaration du vecteur qui arrivera par le paramètre v :
subroutine sub (v, ...)
type v(*)
matrice m (2 indices)
: il faut la première taille de déclaration de la matrice et donc il faut prévoir un paramètre formel pour cette constante :

subroutine sub (a, mligna, ...)
type a (mligna, *)

Ces déclarations s'appliquent uniquement aux paramètres formels, qui rappelons-le ne sont que des portes de communication pour les subroutines et les fonctions. En aucun cas une variable ne pourra être déclarée avec une *. Une variable de type tableau (rappel) doit toujours être déclarée avec des constantes entières

Exercice

Au vu du mécanisme de passage des arguments aux subroutines, expliquez pourquoi les paramètres formels de type vecteurs et matrices sont déclarés de cette manière.

Exemple

Subroutine qui lit une matrice au clavier. La subroutine devra sortir la matrice, son nombre de lignes réelles, son nombre de colonnes réelles (figure 10.8).

Il faut bien comprendre que la seule matrice ayant une existence réelle est la matrice mat du programme principal, et que pour lui réserver de la mémoire, il faut la déclarer avec un nombre de lignes et un nombres de colonnes explicites.


  
Figure 10.8: Lecture d'une matrice au clavier.
      program test
      parameter(mligne=10, mcolon=20)
      double precision mat (mligne, mcolon)
      
      call litmat (mat, mligne, nligne, ncolon)
      
      end
      
      subroutine litmat (a, ma, nl, nc)
      
      double precision a (ma, *)
      
      write(*,*)
     &  'Entrez nombre de lignes-colonnes'
      read(*,*) nl, nc
      
      do i=1, nl
        write(*,*) 'Ligne ', i
        read(*,*) (a(i,j), j=1,nc)
      enddo
      
      return
      end

Le paramètre formel a de la subroutine va recevoir l'adresse de mat au moment de l'appel, et connaissant sa première taille de déclaration (10) via le paramètre formel ma, elle sera à même de lire et écrire un élément quelconque de mat.

Exercice 1

Que se passe-t-il si l'utilisateur tape un nombre de lignes supérieur à 10 et/ou un nombre de colonnes supérieur à 20 ?

Exercice 2

Écrire une subroutine qui affiche une matrice à l'écran, et combinez-la avec litmat pour vérifier votre réponse à la question précédente.

Déclaration external

Objectif

Utiliser le nom d'une fonction ou d'une subroutine comme argument d'une autre fonction ou subroutine.

Quelle drôle d'idée ?

Examinons le problème suivant : Écrire une subroutine qui calcule l'intégrale $\int_a^b f(x) \; dx$ pour une fonction f quelconque. Que faudra-t-il faire entrer et sortir de la subroutine ?

Or quel est le moyen en FORTRAN de programmer une fonction f(x) ? C'est d'utiliser une fonction FORTRAN, qui renverra par exemple une valeur real.

On peut donc prédire la forme de la subroutine d'intégration :

      subroutine integ (a, b, f, valint)

Les paramètres formels a, b et valint seront double precision, mais de quel type est f ?

C'est le nom d'une fonction FORTRAN, et pour déclarer un paramètre formel aussi bizarre, on écrira :

      external f

et aussi

      real f

car f renvoie une valeur real. On peut aussi déclarer des paramètres formels de type subroutine, en utilisant simplement external. Dans le corps de la subroutine, on fera bien sûr appel à cette fonction f pour calculer l'intégrale. Pas de difficulté ! On fait comme si elle existait et on écrira des choses du style :

      valint = valint + h/2 *(f(x1+h) + f(x1))

Maintenant ma subroutine d'intégration est écrite. Je souhaite l'appliquer à une fonction que j'ai écrite en FORTRAN, du style :

      real function truc(x)
      real x
      ...
      truc=...
      return
      end

Je veux appeler la subroutine integ pour calculer l'intégrale de cette fonction entre disons 1 et 2. J'écrirai :

      call integ (1.0, 2.0, truc, somtruc)

1.0 et 2.0 sont des constantes real, somtruc une variable real qui me renverra la valeur de l'intégrale...

Et truc ? C'est le nom d'une fonction FORTRAN. Ai-je le droit de passer ce genre de chose en argument à une subroutine ? La réponse est oui, si je la déclare :

      external truc

dans le bloc fonctionnel d'où je fais le call. De plus, comme cette fonction renvoie une valeur real, j'ajouterai :

      real truc

Récapitulation

La figure 10.9 récapitule tout ce que l'on vient d'expliquer.


  
Figure 10.9: Structure générale d'un programme d'intégration simple.
      program test
      
      real somtruc
      
      external truc
      real truc
      
      call integ (1.0, 2.0, truc, somtruc)
      
      end
      
      
      subroutine integ (a, b, f, valint)
      
      real a, b, valint
      external f
      real f
      ...
      valint=valint + ( f(x1+h) + f(x1) )*h/2
      ...
      return
      end
      
      
      real function truc(x)
      real x
      ...
      truc=...
      return
      end

Remarquons que la structure de truc est imposée par la subroutine qui demande que la fonction représentée par son paramètre formel f ait un argument réel et renvoie une valeur réelle. Nous ne pourrions donc pas par exemple déclarer :

      real function truc(x,y)

En général, les subroutines du type integ sont des boites noires toutes faites (par des spécialistes), et on vous indique juste le type de ses paramètres formels. Lorsque l'un d'entre eux est une fonction ou une subroutine, on vous indique en plus la liste des paramètres formels que doit avoir cette fonction ou subroutine.

Exercice

Écrire la structure d'un programme (programme principal / subroutine / fonctions) pour trouver les zéros d'une fonction f(x) par la méthode de Newton. On rappelle que cette méthode nécessite la connaissance de la fonction f(x) et de sa dérivée f'(x).


next up previous
Next: Les fichiers Up: Initiation au Langage Fortran Previous: Les tableaux

Copyright © EMAC - 1996-1999 - Paul GABORIT