Banière du site

[koala01.free.fr]->Tutoriaux->Principes de Programmation ->Les routines ou sous-programme

Image d'imprimante   image d'enveloppe

14.1 Introduction

Le terme routine est un terme tout à fait général synonyme de sous-programme, et qui regroupe aussi bien ce que l'on appelle, en POO (acronyme de «Programmation Orientée Objet»), les méthodes d'un objet que les procédures et fonctions en programmation non orientée objet.

D'ailleurs, les méthodes en POO ne seront rien d'autres que des fonctions ou des procédures…

fleche haut

14.2 Le but

L'idée de base d'un sous-programme est de disposer d'une partie de code capable de prendre en charge toute une partie de traitement de manière autonome.

De cette manière, il suffira de faire appel à cette partie du code pour être sûr que le traîtement soit correctement effectué sans avoir besoin de réécrire le code, avec le risque d'erreurs qui en découle, à chaque fois que l'on a besoin de ce traîtement.

Accessoirement (enfin, ce n'est pas si accessoire que cela), il faut reconnaître qu'il est beaucoup plus facile de se retrouver dans 5 fois 50 lignes de codes correctement structurées que dans le même code, tout aussi correctement structuré, mais d'une fois 250 lignes…

fleche haut

14.3 Les trois parties d'une routine

Pour créer un sous-programme, il faut disposer de trois informations importantes:

Ces trois parties, mises bout à bout -généralement sous la forme de "type de retour + nom + (liste des arguments)"- fourniront ce qu'il est convenu d'appeler «le prototype» ou «la signature» de la routine.

Sémantiquement parlant, la signature sera surtout attentive au nom de la routine et aux différents types d'arguments, ce qui permettra d'identifier la routine de manière unique, alors que le prototype de la routine sera attentif, non seulement au nom de la routine et aux différents types des arguments, mais également au type de renvoi et aux noms des arguments.

Bien que, dans l'idée gérale, prototype et signature soient relativement synonymes, la sémantique demanderait à ce que l'on parle de prototype lorsqu'il s'agit de déclarer ou de définir une routine, alors que la signature sera utilisée lorsqu'il s'agira d'appeler une routine afin de savoir quel(s) argument(s) lui passer.

fleche haut

14.4 Procédure ou fonction?

La seule différence qu'il existe entre une procédure et une fonction est que la procédure ne renverra aucune valeur alors que la fonction en renverra une d'un type donné.

Selon les manières de regarder les choses, on pourrait dire, au choix, qu'une fonction est une procédure spéciale en ce qu'elle renvoie une valeur ou qu'une procédure est une fonction spéciale en ce qu'elle ne renvoie pas de valeur.

fleche haut

14.5 Un exemple qui en vaut un autre

Imaginons que vous vouliez créer une application qui demande à l'utilisateur d'introduire, en vérifiant que l'utilisateur n'essaie pas d'introduire trop de caractères pour les données attendues(cf: les problèmes relatifs à l'espace mémoire alloué pour les tableaux), une série d'informations sur plusieurs personnes, puis qui réaffiche ces informations avant de les enregistrer (en somme, créer un carnet d'adresses).

Que l'utilisateur doive introduire le nom, le prénom ou l'adresse d'un contact, le code sera toujours similaire, seul le nombre de caractères acceptés sera susceptible de changer (30 caractères pour le nom, 20 pour le prénom et 50 pour l'adresse, par exemple).

La "sécurisation" de ces différents encodages pourrait donc très bien être confiée à une fonction, qui renverrait une chaine de caractères, et qui prendrait en paramètre le nombre de caractères maximal à accepter.

De plus, il sera sûrement plus simple de se retrouver dans le code en sachant où commencent et où finissent les différentes parties du programme :

Dans l'exemple que j'ai pris, on pourrait préférer avoir une partie qui ne prend en charge que l'introduction des informations, une autre qui ne prend en charge que leur récapitulation et une troisième qui ne prend en charge que l'enregistrement des données.

Ainsi, au lieu de devoir reparcourrir tout le code (qui risque quand même de comporter deux ou trois centaines de lignes) à la recherche de l'endroit où une erreur a été commise, on pourra se contenter de ne parcourrir que la partie du code où l'on sait que l'erreur survient: lors de l'encodage, lors de la récapitulation, ou lors de l'enregistrement.

fleche haut

14.6 Le renvoi d'une valeur

En fonction du but de la routine, il se peut qu'elle doive renvoyer ou non une valeur.

En suivant l'exemple précédent, portant sur les trois routines principales de l'application (encodage, affichage, enregistrement sur le disque dur), on pourrait estimer que la routine d'affichage ne doit pas forcément renvoyer une valeur, alors qu'il peut être intéressant que la routine d'enregistrement sur le disque dur renvoie éventuellement une valeur précisant si l'enregistrement a pu être mené à bien ou non, ou que la routine d'encodage nous renvoie l'adresse mémoire du début des valeurs, en fonction de la gestion générale de l'application.

fleche haut

14.7 Le nom

Comme tout ce qu'il est possible de nommer (et principalement les variables) le nom de la routine doit suivre exactement les mêmes règles:

fleche haut

14.8 Les paramètres

Tout comme un moteur à explosion a besoin d'essence, d'air et d'électricité pour pouvoir fonctionner, il n'est pas rare de devoir fournir certaines valeurs de travail récupérées ailleurs dans le code à une routine pour qu'elle puisse travailler correctement.

Il ne s'agit nullement de déclarer les variables qui sont nécessaire à son fonctionnement, mais bien de fournir à la routine des valeurs qu'elle n'est pas en mesure d'obtenir par elle même:

Si on ne passe pas à la pompe à essence, si le filtre à air est bouché, ou si la batterie est plate, le moteur est dans l'incapacité de trouver par lui même l'essence, l'air ou l'électricité…  Le principe est ici le même.

En suivant l'exemple précédent, nous pourrions indiquer en paramètre le nombre maximal de caractère que la fonction d'aquisition sécurisée peut accepter en fonction qu'elle est appelée pour obtenir le nom, le prénom ou l'adresse du contact.

Ce paramètre fournis lorsque l'on appelle la routine sera considéré comme une variable locale, parfaitement utilisable par celle-ci.

fleche haut

14.9 Mettons un peu d'ordre

Maintenant que l'on sait ce dont on a besoin pour créer la fonction, il est peut être temps de remettre tout cela dans l'ordre.

La plus part des langages demandent à ce que quand on crée une routine on commence par indiquer le type de variable qu'elle renverra, suivi de son nom, lui-même suivi par le type et un nom des paramètres qu'il faut lui donner entre parenthèse.

Toujours selon l'exemple de notre carnet d'adresse, la routine qui prend en charge la sécurisation de l'introduction clavier devrait renvoyer une chaine de caractère (donc un tableau de caractère), sera (logiquement) nommée acquis et prendra en paramètre un entier (nommons le logiquement long_max) qui permettra à la fonction de savoir combien de caractère elle doit accepter.

Le code prendra donc à peu près cette apparence:

caractere[] acquis (entier long_max)
{
//les instructions à suivre pour sécuriser l'acquisition
}

Et l'appel dans le code

Comme le but d'une routine est d'être accessible de partout, il semble pour le moins logique qu'il soit possible de l'appeler de n'importe où…

Cela se fera tout simplement en prévoyant une variable pour récupérer la valeur renvoyée par la routine (s'il échoit, bien sûr) et en nommant la routine à appeler en lui fournissant les paramètres dont elle a besoin.

Selon notre exemple, l'appel de la fonction pour l'acquistion du nom d'un contact ressemblerait tout simplement à

nom=acquis(30)|

30 étant le nombre maximal de caractère que l'on souhaite que la fonction accepte lors de l'acquisition.

fleche haut

14.10 Un nassichneiderman complet

Cessons un instant de "parler dans le vide", et attelons nous à du concret.

Nous voulons donc créer une application qui soit en mesure de permettre l'introduction du nom, du prénom et de l'adresse de 10 contacts (comme on n'a pas encore vu la gestion dynamique des données, limitons nous à une valeur qui ne soit pas trop importante), et de les réafficher par la suite (n'ayant pas encore vu la théorie sur les fichiers, ne mettons pas la charrue avant les boeufs en voulant en gérer l'enregistrement)

Reprenons donc l'étude depuis le début:

NOTA:

Comme cette application fait appel à une série de concepts qui n'ont pas encore été abordés du point de vue du nassichneiderman, j'ai décidé de simplifier au maximum l'énoncé de cet exemple pour ne pas surcharger inutilement les explications.

Représentationdu nassichneiderman de la fonction principale

Représentation du nassichneiderman de la fonction principale

Représentation du nassichneiderman de la fonction d'initialisation

initialisation

Représentation du nassichneiderman de la fonction d'introduction"

introduction

Représentation du nassichneiderman de la fonction d'affichage

affichage

fleche haut

14.11 Quelques remarques

Le caractère ASCII de valeur 8 (pas le huit lui-même, mais bien celui qui a la valeur 00001000 en binaire) est le caracètre obtenu par l'enfoncement de la touche "backspace" (effacement arrière)

Le caractère ASCII de valeur 13 (00001101 en binaire) correspond au caractère obtenu par l'enfoncement de la touche <Enter>

La plupart des langages de programmations considèrent qu'une chaine est terminée quand ils rencontrent le caractère null (valeur 00Hexa)

Si, donc, on introduit ce caractère à une place quelconque dans la chaîne, le programme conscidérera la chaine comme étant finie, quel que puisse être le contenu des éléments suivants.

Typiquement, quand on travaille dans une fenêtre de ligne de commande (fenêtre "Dos" sous windows), on dispose de 25 lignes de 80 caractères.

On peut très bien décider de faire effectuer un affichage à une position donnée en indiquant les coordonnée X (horizontale) et Y(verticale) du curseur (ce qui est fait dans le nassichneiderman avec les instructions "Va en X,Y")

Bien que trois routines fassent appel à une variable locale du même nom (cpt en l'occurence), il s'agira en réalité de trois variables tout à fait indépendantes les unes des autres, du fait que ce sont des variables locales.

Leur valeur sera systématiquement oubliée dés que la routine qui les utilise sera quittée.

De même, si on avait appelé une routine en passant cpt en paramètre (qui aurait été, lui aussi, appelé cpt), la variable cpt de la routine appelée n'aurait rien eu à voir avec la variable cpt de la routine appelante

fleche haut

14.12 Le tableaux des routines

Quand on crée plusieurs routines, il est souvent intéressant d'en dresser un tableau récapitulatif de manière à se faciliter la vie si on se trouve dans l'obligation de laisser quelques temps un projet sur le côté.

Tableau des routines du projet
nom Valeur renvoyée Arguments Commentaires
Principale aucune aucun fonction principale du programme
Affichage aucune aucun Fonction qui prend en charge l'affichage des éléments encodés
Introduction aucune aucun Fonction qui se charge de l'encodage des dix contacts
Aquis chaine de caractères entier "Long_Max" Fonction qui prend en charge l'acquisition "sécurisée" des chaines de caractères d'une longueur maximale de Long_max.

fleche haut

14.13 Les tableaux des variables

Il est souvent préférable de dresser un tableau de variables pour chaque routine et pour chaque structure dont on aura besoin.

Cela aura principalement comme avantage de déterminer avec précision les variables dont chaque routine aura besoin

Structure personne
nom type commentaire
Nom chaine de caractères trente caractères
Prenom chaine de caractères vingt caractères
Adresse chaine de caractères cinquante caractères

Les variables globales
nom type commentaire
Contact personne Tableaux de dix éléments

Les variables locales de la procédure principale()
nom type commentaire
    Il n'y a pas besoin de variable locale dans la procédure principale

Les variables locales de la procédure affichage()
nom type commentaire
cpt entier Permet de gérer le contact dont il faut afficher les informations
Touche Caractères Variable dont la seule utilité est de respecter la synthaxe de l'instruction

Variable locale de la procédure Introduction()
nom type commentaire
cpt entier permet de gérer la boucle et le contact dont on souhaite introduire les informations

Variables locales de la fonction acquis()
nom type commentaire
Long_max entier paramètre fournis à la fonction pour déterminer la taille maximale de la chaine à accepter
chaine chaine de caractères

50 caractères pour être en mesure d'accepter tous les caractères possibles de l'adresse.

Permet la récupération et le renvois de la chaine en quittant la fonction

Touche caractère variable temporaire permettant d'effectuer une acquisition non bufferisée du clavier
X entier Permettra de garder en mémoire la position de départ X (horizontale) du curseur
Y entier Permettra de garder en mémoire la position de départ Y (verticale) du curseur

fleche haut

14.14 La transformation en code

Il ne nous reste plus qu'à transformer notre algoritme en code source.

Pour y arriver, il ne manque que quelques instructions:

//La déclaration de la structure
structure personne{
    caractere[30] Nom|
    caractere[20] Prenom|
    caractere[50] Adresse|
}
//déclaration de la variable globale
personne contact|

//la procédure principale
vide principale(vide)
{
//déclaration de la variable locale
    Init()
//L'appel de la procédure d'introduction
    Introduction()|
//L'appel de la procédure d'affichage
    Affichage()|
}

//La procédure d'initialisation
vide init(vide)
{
    Entier cpt|
    pour (cpt = 0 à 9)
    {
        Contact[cpt].Nom=""
        Contact[cpt].Prenom=""
        Contact[cpt].Adresse=""
    }
}

//La procédure d'obtention des informations
vide Introduction(vide)
{
    Entier cpt
    pour (cpt = 0 à 9)
    {
        EffaceEcran()|
        VaEnxy(20,1)|
        Affiche "Introduction des coordonnées du contact "cpt|
        Vaenxy(20,2)|
        Affiche "========================================="|
        VaEnxy(10,4)|
        Affiche "Nom : "|
        Contact[cpt].Nom=Acquis(30)|
        VaEnxy(10,5)|
        Affiche "Prenom : "|
        Contact[cpt].Prenom=Acquis(20)|
        VaEn(10,6)|
        Affich "Adresse : "|
        Contatct[cpt].Adresse=Acquis(50)|
    }
}
//La procédure d'affichage
vide Affichage(vide)
{
    Entier cpt|
    Caractere Touche|
    pour (cpt =0 à 9)
    {
        EffaceEcran()|
        VaEnxy(20,1)|
        Affiche "Informations sur le contact "cpt|
        Affiche "============================="|
        VaEnxy(10,4)|
        Affiche "Nom : "Contact[cpt].Nom|
        VaEnxy(10,5)|
        Affiche "Prenom : "Contact[cpt].Prenom|
        VaEnxy(10,6)|
        Affiche "Adresse : "Contact[cpt].Adresse|
        VaEnxy(15,30)|
        Affiche "Une touche pour continuer"|
        clavier Touche|
    } 
}
//et enfin la fonction de sécurisaiton de l'acquisition
//Nota: quand on souhaite renvoyer un tableau d'éléments,
//on renvoie en réalité le pointeur vers ce tableau
caractères * acquisition(Entier Long_Max)
{
    Caractere Chaine[50]
    Caractere Touche
    Entier Long
    Entier X
    Entier Y
    Chaine = ""
    X = PositionX()
    Y = PositionY()
    Faire
    {
        VaEnxy(X+Long,Y)
        clavier Touche
        Selon Touche
        {
            Cas 8:
        //Pour effacer un caractère, il faut bien qu'il y en
        //aie un minimum ;-)
                Si (Long>0)
                {
                    Long=Long-1
                    Chaine[Long]=0
                }
                Arret
            Cas 13:
                Chaine[Long]=0
                Arret
            Defaut:
        //On ne peut rajouter un caractère que si on n'a pas atteind
        //la longueur maximale de la chaine
                if(Long<Long_Max)
                {
                    Chaine[Long]=Touche
                    Long=Long+1
                }
        }
    }Jusque (Touche=13)
    Renvoie Chaine
}

fleche haut

14.15 La notion de récursivité

Certains langages acceptent qu'une fonction puisse s'appeler elle-même.

C'est ce que l'on appelle la «récursivité» d'une fonction.

La page suivante y est consacrée.

fleche haut

14.16 C'est bien beau, les fonctions mais…

Mais elles ne permettent que de renvoyer une et une seule variable à la fois (au mieux, il est possible de leur faire renvoyer un tableau)…

Pourtant, il peut être intéressant qu'une seule fonction soit en mesure de modifier plusieurs valeurs.

Et l'utilisation de variables globales ne serait qu'un pis aller, du fait, entre autre, du gaspillage de ressources que ca peut représenter, mais aussi, du fait qu'il n'est pas possible de gérer dynamiquement la mémoire allouée à une variable globale.

La solution va passer par l'utilisation de pointeurs, dont on parlera dans quelques pages..

image d'imprimante   image de mail   fleche haut

Evaluation donnée par les visiteurs
Cette page n'a pas encore été évaluée sur la compréhension
Mon appréciation sur la compréhensibilitéde cette page est:
  • incompréhensible
  • mal expliquée
  • compréhensible, sans plus
  • bien expliquée
  • très bien expliquée

fleche haut

[koala01.free.fr]->Tutoriaux->Principes de Programmation ->Les routines ou sous-programme

Copyright (©) 2005 (Philippe Dunski)

Ce cours est libre, vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU publiée par la Free Software Foundation (version 2 ou bien toute autre version ultérieure choisie par vous).

Ce cours est distribué car potentiellement utile, mais SANS AUCUNE GARANTIE, ni explicite ni implicite, y compris les garanties de commercialisation ou d'adaptation dans un but spécifique. Reportez-vous à la Licence Publique Générale GNU pour plus de détails.

Cependant, l'auteur apprécierait grandement que vous lui fassiez part de toute modification apportée à son contnu

Vous pouvez le contacter par mail à l'adresse koala01@free.fr

Vous pouvez trouver une adaptation française de la licence GNU/GPL à l'URL http://www.linux-france.org/article/these/gpl.html