[EN] [IT]
[INFO] [ACQUÉRIR] [PLAN] [RESSOURCES]
CHAPITRE
APPRENDRE À PROGRAMMER AVEC FUTUREBASIC
NOTES

Les branchements III

Les arguments d'une fonction
Résultat d'une fonction

Leçon 12 : Paramètres des fonctions

Les arguments d'une fonction
Une autre façon de faire connaître une variable à une fonction est de lui communiquer sa valeur au moment de l'appeler. Les fonctions acceptent des paramètres. Lorsque vous déclarez une fonction qui accepte des paramètres, vous devez indiquer dans sa définition la liste des paramètres et le type de chacun d'entre eux. Cette liste est aussi appelée liste des arguments formels de la fonction. FutureBASIC vous permet de déclarer jusqu'à 16 arguments formels pour les fonctions locales.

LOCAL FN Bienvenue(prenom AS STR63)
  PRINT "Bonjour "; prenom
END FN

// Programme principal
FN Bienvenue("Élodie")


Dans l'exemple ci-dessus, la chaîne littérale "Élodie" est envoyée à la fonction Bienvenue, cette chaîne est copiée, puis assignée à la variable prenom qui sera créée pour l'accueillir. Cette variable pourra alors être manipulée dans le corps de la fonction, comme tout autre variable ordinaire.

Dans beaucoup de situations, nous n'avons besoin de transmettre à une fonction que la valeur d'une variable donnée, nous pouvons passer en paramètre la variable comme ceci :

LOCAL FN Bienvenue(prenom AS STR63)
  PRINT "Bonjour "; prenom
END FN

// Programme principal
DIM utilisateur AS STR63

Utilisateur = "Élodie"
FN Bienvenue(utilisateur)


Il est important de retenir ici la chose suivante : lorsque vous passez une variable en paramètre (utilisateur) c'est la valeur qu'elle contient (Élodie) qui est transmise à la fonction, l'argument formel (prenom) qui reçoit cette valeur n'a pas de lien avec la variable qui a été utilisée (utilisateur), vous pouvez modifier la valeur de la variable (prenom) dans le corps de la fonction sans affecter la variable initiale (utilisateur) qui a servi dans l'instruction appelante.

Cette règle ne s'applique pas aux variables records comme nous le verrons par la suite.

Les arguments formels sont donc des variables locales qui, au même titre que les autres variables locales éventuellement déclarées à l'intérieur de la fonction, disparaîtront une fois que la fonction aura terminé son exécution.

Résultat d'une fonction
Le mécanisme est bien adapté pour protéger les variables extérieures de toute modification intempestive, mais qu'en est-il lorsque l'on veut effectivement modifier le contenu d'une variable qui est extérieure à la fonction ?

Deux mécanismes peuvent être mis en œuvre, le premier, le plus simple, tient au fait qu'une fonction, à la différence d'une routine invoquée par GOSUB, peut retourner une valeur unique à l'instruction qui l'a appelée.

LOCAL FN Bienvenue$(prenom AS STR63)
  prenom = UCASE$(prenom)
  PRINT "Bonjour "; prenom
END FN = prenom

// Programme principal
DIM utilisateur AS STR63

Utilisateur = "Élodie"
Utilisateur = FN Bienvenue$(Utilisateur)


L'exemple ci-dessus illustre le mécanisme : une variable est déclarée, en l'occurrence ici dans le programme principal mais elle pourrait aussi bien l'être dans une autre fonction locale, la valeur de cette variable est envoyée à une fonction locale qui retourne une valeur, qui finalement sera rangée dans une variable (dans notre exemple, la même variable qui a servi à expédier la valeur à la fonction).

Vous avez noté très certainement l'identificateur de type $ accolé au nom de la fonction. C'est la seule façon jusqu'à présent d'indiquer au Compilateur le type de la valeur que la fonction doit retourner.

Notez deux choses cependant :
  • ce suffixe peut-être omis si la valeur retournée est du type entier, entier long ou octet. En réalité, la plupart du temps on ne spécifie le suffixe que pour les fonctions retournant des nombres en simple ou double précision et des chaînes de caractères;

  • le deuxième point important est de savoir qu'une fonction ne peut retourner à l'instruction appelante une structure, comme RECT à titre d'exemple.

    Vous avez peut-être remarqué la similitude entre les deux instructions suivantes :

    Utilisateur = FN bienvenue$(Utilisateur)
    prenom = UCASE$(prenom)


    La fonction BASIC UCASE$ utilisée dans notre exemple accepte une chaîne de caractères en paramètre, passe les caractères en majuscule, puis retourne la chaîne résultante.

    C'est ici l'occasion de rappeler que les mots-clés du BASIC ne sont en fait que des fonctions-utilisateur enfouies dans le Runtime. Le Runtime étant lui-même écrit avec FutureBASIC, vous pouvez, si vous aimez le risque, modifier le code à votre guise puisque le code source des différents runtimes est accessible dans le dossier Headers du Compilateur.

    Si vous suivez pas à pas le travail accompli par l'ordinateur dans notre exemple précédent, vous réaliserez assez vite le "gaspillage" de ressources induit :

  • une chaîne est déclarée puis des octets sont copiés dans le bloc de mémoire réservé à cette variable;

  • les octets copiés sont à nouveau transférés dans une zone mémoire maintenue en interne par FB pour permettre le passage de la chaîne entière en paramètre;

  • les octets de cette pile interne sont à nouveau transférés dans une variable locale qui a été créée pour les accueillir;

  • les octets sont à nouveau copiés dans la pile interne pour les passer en paramètre à la fonction UCASE$, puis la fonction UCASE$ modifie les octets un à un;

  • les octets sont à nouveau copiés dans la variable qui reçoit le résultat de la fonction UCASE$;

  • cette variable est retournée par la fonction Bienvenue$, les octets seront à nouveau transférés dans une autre variable prévue pour recevoir le résultat de cette fonction.

    L'ordinateur est peut-être infatigable, mais c'est beaucoup de travail pour un si petit résultat.

    Fort heureusement, il y a aussi des méthodes plus directes pour modifier une variable qui est passée en paramètre à une fonction. Si l'on s'attaque aux octets originaux qui composent la chaîne de départ, le travail sera plus immédiat, il consommera moins de mémoire et sera de surcroît plus rapide. Le bloc qui contient les octets est localisé à l'adresse de la variable. Lorsqu'on travaille avec les adresses, différentes stratégies peuvent être mises en œuvre. Pour commencer on peut utiliser indifféremment les deux méthodes suivantes pour le passage des paramètres:

    1 - Passage de l'adresse de la variable à la fonction invoquée :

    LOCAL FN maFonction(adresse AS PTR)
    END FN

    FN
    maFonction (@variable)


    2 - Réception de l'adresse par la fonction invoquée :

    LOCAL FN maFonction(@adresse AS PTR)
    END FN

    FN
    maFonction (variable)


    Une fois que vous disposez de l'adresse dans la fonction locale, il y a de très nombreuses façons d'examiner les octets et de les modifier.

    Vous pouvez utiliser la fonction PEEK BYTE (raccourci : valeur = |adresse|) pour lire et POKE BYTE (raccourci : | adresse,valeur) pour écrire un octet individuel. Rappelez vous que le premier octet trouvé à l'adresse de la chaîne nous renseigne sur le nombre de caractères qui la composent et que les autres octets représentent les codes ASCII des caractères.

    LOCAL FN Majuscule(@adresse AS PTR)
      DIM longueur      AS SHORT
      DIM i             AS SHORT
      DIM codeCaractere AS CHAR

      longueur = |adresse|
      LONG IF longueur
        FOR i = 1 TO longueur
          codeCaractere = |adresse + i|
          LONG IF codeCaractere >= _"a" AND codeCaractere <= _"z"
            | adresse + i, codeCaractere - 32
          END IF
        NEXT
      END IF
    END FN


    // Programme principal
    DIM utilisateur AS STR63

    Utilisateur = "Élodie"
    FN Majuscule(Utilisateur)
    PRINT "Bonjour ";utilisateur


    Que fait exactement notre fonction Majuscule ici ?

    Elle commence par intercepter l'adresse de la chaîne qui lui a été envoyée en paramètre et déclare les variables dont elle va avoir besoin. Elle extrait ensuite le premier octet trouvé à l'adresse de la chaîne pour ranger sa valeur dans la variable longueur qui contiendra donc le nombre de caractères composant la chaîne. Avant d'entamer une boucle pour examiner les caractères un à un, elle s'assure que la longueur est différente de zéro (que la chaîne passée n'est pas vide), car rappelez-vous que les boucles FOR/NEXT sont exécutées au moins une fois. Si la chaîne passée est vide, le programme n'entrera pas dans la boucle. Ensuite, à tour de rôle les caractères sont extraits individuellement et examinés. Si le code ASCII du caractère figure dans une certaine plage de valeurs (ici entre les codes du a minuscule et du z minuscule inclus), la fonction range à l'adresse du caractère, le code précédemment trouvé moins la valeur 32 (vous pouvez consulter la table des codes ASCII pour vérifier qu'il y a bien un écart de 32 entre les versions majuscule et minuscule des caractères courants).

    Ce code, bien qu'en apparence plus compliqué, est exécuté beaucoup plus rapidement que celui qui a été montré jusqu'ici. Il consomme moins de ressources également puisqu'aucune copie de chaîne n'est effectuée.

    Ce n'est pas la seule méthode, et vous aurez l'occasion de constater que les programmeurs adoptent différentes stratégies pour arriver au même résultat. Par exemple, essayez de comprendre le code suivant :

    LOCAL FN Majuscule(@adresse AS PTR)
      DIM adresseFinChaine AS LONG

      IF
    adresse.nil` = 0 THEN EXIT FN
      adresseFinChaine = adresse + adresse.nil`
      DO
        adresse++
        LONG IF adresse.nil` >= _"a" AND adresse.nil` <= _"z"
          adresse.nil` -= 32
        END IF
      UNTIL
    adresse = adresseFinChaine
    END FN

    // Programme principal
    DIM utilisateur AS STR63

    Utilisateur = "Élodie"
    FN Majuscule(Utilisateur)
    PRINT "Bonjour ";utilisateur


    Désorienté ? Rassurez-vous, c'est normal, je ne vous ai pas encore indiqué tous les raccourcis d'écriture, mais notez que le bout de code ci-dessus, essentiellement identique au précédent, n'utilise qu'une seule variable servant à stocker l'adresse où la chaîne prend fin en mémoire.

    Pour vous aider à mieux comprendre, sachez que FutureBASIC^3 emprunte au langage C, certaines de ses tournures syntaxiques, par exemple :

    variable++ est équivalent à variable = variable + 1 ou encore INC(variable)
    variable-- est équivalent à variable = variable - 1 ou encore DEC(variable)
    variable += x est équivalent à variable = variable + x
    variable -= x est équivalent à variable = variable - x

    J'ai introduit ici également un raccourci que certains programmeurs FutureBASIC préfèrent aux traditionnels PEEK et POKE, consistant à indiquer un décalage par rapport à une adresse, ce décalage étant suivi d'un suffixe identificateur de type. Enfin, je dois ajouter que la constante _nil est fréquemment utilisée par les programmeurs en lieu et place de la valeur 0.

    Si bien que adresse.nil` désigne la valeur de l'octet (` étant l'idendificateur de type pour les octets), trouvé à un décalage de 0 octet à partir de adresse, ce qui revient à la forme d'écriture plus courante PEEK BYTE (adresse) ou PEEK (adresse) [BYTE étant optionnel lorsqu'on souhaite manipuler des octets] ou |adresse|. Notez que si l'on avait voulu prendre un entier long localisé 2 octets plus loin que l'adresse mémoire on aurait pu écrire ceci : adresse.2& qui est équivalent à PEEK LONG(adresse + 2) ou [adresse + 2]. En résumé, l'écriture "adresse+ point+décalage+symbole identificateur de type" est une forme alternative aux commandes PEEK et POKE, pour lire et écrire directement en mémoire.

    À ce point, vous avez peut-être le vague souvenir qu'il est possible de considérer une chaîne de caractères comme un tableau d'octets. Eh oui, la commande XREF pourrait être mise à profit ici aussi :

    LOCAL FN Majuscule(@adresse AS PTR)
      DIM i AS SHORT
      XREF cars(63) AS CHAR

      cars& = adresse
      i = 0
      WHILE cars(i)
        i++
        LONG IF cars(i) >= _"a" AND cars(i) <= _"z"
          cars(i) -= 32
        END IF
        IF
    i = cars(0) THEN EXIT WHILE
      WEND
    END FN


    // Programme principal
    DIM utilisateur AS STR63

    Utilisateur = "Élodie"
    FN Majuscule(Utilisateur)
    PRINT "Bonjour ";utilisateur


    Essayez de comprendre la logique du bout de code ci-dessus. Si vous rencontrez des difficultés, consultez à nouveau les chapitres consacrés à la commande XREF et à la structure conditionnelle WHILE/WEND.

    Enfin, parmi toutes les autres possibilités offertes par le langage, en voici une, qui semble fonctionner correctement et pour laquelle vous ne trouverez aucun exemple, ni mention dans les manuels :

    LOCAL FN Majuscule(cars(63) AS CHAR)
      DIM i AS SHORT

      LONG IF
    cars(0)
        FOR i = 1 TO cars(0)
          LONG IF cars(i) >= _"a" AND cars(i) <= _"z"
            cars(i) -= 32
          END IF
        NEXT
      END IF
    END FN


    // Programme principal
    DIM utilisateur AS STR63

    Utilisateur = "Élodie"
    FN Majuscule(Utilisateur)
    PRINT "Bonjour ";utilisateur


    Notez que cela revient à se passer totalement de la commande XREF. Cette forme ne fonctionne qu'avec des pointeurs sur des blocs non relogeables en mémoire.

    Chaque problème posé peut-être abordé sous un angle différent, il n'y a jamais une façon unique de résoudre un problème, et c'est à la charge du programmeur, en fonction des outils dont il dispose et parfois de son inspiration, de rechercher la solution la plus appropriée à un problème donné.

    Le fait qu'une fonction locale a la possibilité de modifier directement les variables qui lui sont passées en paramètres en travaillant directement avec leur adresse en mémoire, permet d'offrir une plus grande autonomie à la fonction par rapport au reste du programme. Encore une fois, l'un des intérêts majeurs de cette indépendance est de permettre l'écriture de fonctions complètement réutilisables dans d'autres projets sans plus de modifications. Un problème peut subsister cependant, si vous déclarez une variable dans votre fonction locale qui porte le même nom qu'une variable globale de votre projet. Rappelez-vous que les variables globales sont censées être "vues" par les fonctions locales, elles peuvent donc être examinées et modifiées à l'intérieur de ces fonctions. Pour isoler totalement votre fonction du reste du programme, FutureBASIC dispose d'une déclaration spéciale que l'on place avant la définition de la fonction :

    LOCAL MODE
    LOCAL FN
    maFonction
    // corps de la fonction
    END FN


    La commande LOCAL MODE empêche la fonction de connaître les variables globales du programme. Si par hasard, vous déclariez dans une fonction en LOCAL MODE une variable locale ayant le même nom qu'une des variables globales du programme, le Compilateur saura qu'il s'agit de deux variables différentes. On peut alors copier et coller de telles fonctions d'un projet à un autre sans grands risques d'engendrer d'éventuels conflits. Seules les constantes et les définitions de records déclarées à l'extérieur de la fonction lui resteront encore accessibles.

    Si l'on souhaite qu'une variable globale soit altérée par la fonction isolée par l'instruction LOCAL MODE, on pourra alors utiliser une des deux méthodes précédemment mentionnées : en faisant en sorte d'écrire la fonction pour qu'elle retourne une valeur que l'on rangera dans la variable globale à modifier ou bien en travaillant à partir de l'adresse-mémoire de la variable globale que l'on passera en paramètre.

    Bien qu'il soit un peu plus délicat, à peine plus en fait, de concevoir les fonctions de cette façon, il est certain que vous ne regretterez pas d'avoir pris cette bonne habitude lorsque vous en serez à votre quatrième ou cinquième projet.



    [Precédent] [Table des Matières] [Suivant]
  • UCASE$
    Il subsiste cependant un problèmes avec les routines de la Toolbox du Mac et les ligatures ß,? et ? qui n'ont pas d'équivalents en capitales, la routine retourne, incorrectement, les mêmes caractères et non pas 'SS', 'FI' et 'FL'
    © 2000 Pix&Mix
    Tous droits réservés

    FutureBASIC est une marque déposée appartenant à Staz Software, Inc et utilisée avec permission.