| CHAPITRE
|
APPRENDRE
À PROGRAMMER AVEC FUTUREBASIC
|
NOTES
|
|
Les branchements II |
Leçon 11 :
Les fonctions-utilisateur Pour pallier les carences des routines appelées par GOSUB, FutureBASIC a introduit, dans son implémentation du langage BASIC, la notion de fonction-utilisateur, comme les langages Pascal ou C le proposaient déjà. Cette notion de fonction-utilisateur ou LOCAL FN est dune importance primordiale. Les avantages patents des fonctions-utilisateur que nous allons examiner ont conduit tout simplement à la disparition de la commande GOSUB dans la quasi totalité des bouts de code que vous serez amené à rencontrer ultérieurement. Il est bien sûr toujours possible de programmer en BASIC dit standard avec FutureBASIC, mais il est clair que la commande GOSUB a été supplantée par la commande LOCAL FN à juste titre. Les fonctions locales doivent être déclarées avant de pouvoir être utilisées. La déclaration dune fonction est a priori très simple : LOCAL FN maFonction PRINT "A lintérieur de maFonction" END FN Les commandes LOCAL FN/END FN délimitent le corps de la fonction qui contient les instructions à exécuter. Vous devez donner un nom unique à votre fonction; tout comme avec les constantes, vous ne pouvez pas avoir deux fonctions ayant des noms identiques dans un même programme. Tout comme les routines invoquées par GOSUB, les fonctions peuvent appeler dautres fonctions préalablement définies, toutefois, vous ne pouvez pas déclarer une fonction à lintérieur dune autre fonction, dit autrement, les instructions LOCAL FN ne peuvent pas être imbriquées. On peut brancher très facilement le flux du programme sur une fonction (on dit appeler une fonction). La ligne de code suivante appellera maFonction et provoquera lexécution des lignes de code quelle contient, en loccurrence dans lexemple ci-dessus, la chaîne « A lintérieur de maFonction » sera affichée à lécran. FN maFonction De manière générale, les fonctions sont déclarées avant lexécution du programme principal. Un programme se présente le plus souvent comme une longue suite de déclarations de fonctions terminée par le programme principal. Le Compilateur créera le code exécutable pour chacune delles sans exécuter les instructions, car les fonctions ne sont exécutées que si vous les appelez explicitement par des instructions de votre code source. LOCAL FN maPremiereFonction END FN LOCAL FN maDeuxiemeFonction END FN LOCAL FN maTroisiemeFonction END FN REM programme principal. Début de lexécution FN maDeuxiemeFonction FN maPremiereFonction Notez que les fonctions sont déclarées au fur et à mesure que le Compilateur les rencontre lorsquil produit le code exécutable. Arrivé donc au programme principal toutes les fonctions auront été déclarées et pourront donc être utilisées. Mais que se passerait-il si, par exemple la fonction maDeuxiemeFonction devait faire elle-même appel à maTroisiemeFonction ? LOCAL FN maPremiereFonction END FN LOCAL FN maDeuxiemeFonction FN maTroisiemeFonction END FN LOCAL FN maTroisiemeFonction END FN Lorsque le Compilateur arrivera à maDeuxiemeFonction il voudra générer le code exécutable pour le corps de la fonction, malheureusement maTroisiemeFonction naura pas encore été déclarée, il ne pourra donc pas compiler le code adéquate et arrêtera son travail en vous signalant que maTroisiemeFonction na pas encore été définie. Il y a heureusement des solutions à ce problème. La plus évidente est de réorganiser le code source pour que maTroisiemeFonction soit déclarée avant maDeuxiemeFonction : LOCAL FN maPremiereFonction END FN LOCAL FN maTroisiemeFonction END FN LOCAL FN maDeuxiemeFonction FN maTroisiemeFonction END FN Lordre de déclaration des fonctions a donc une importance. Il existe cependant un autre mécanisme qui permet de pré-déclarer des fonctions et qui peut vous éviter la réorganisation de votre code source. Les fonctions peuvent-être prototypées, cest-à-dire quen fait, vous indiquez au Compilateur que la définition de la fonction viendra par la suite dans votre code source. La commande DEF FN vous permet de prototyper des fonctions : DEF FN maTroisiemeFonction LOCAL FN maPremiereFonction END FN LOCAL FN maDeuxiemeFonction FN maTroisiemeFonction END FN LOCAL FN maTroisiemeFonction END FN Certains programmeurs ont pris lhabitude de prototyper toutes les fonctions utilisées dans leur programme et de placer cette liste de prototypes au début de leur code source de manière à ne plus se poser la question de savoir quelle fonction doit être déclarée avant telle autre. Les fonctions-utilisateur ont non seulement la qualité des routines appelées par GOSUB, mais elles en décuplent la puissance. A lintérieur dune fonction locale vous pouvez déclarer des variables qui nauront dexistence que tant que la fonction sera en cours dexécution. Cette particularité introduit la notion de portée des variables. Ceci est très important : toutes les variables nont pas la même portée. Une fonction-utilisateur ne sait rien des autres variables déclarées en dehors de son champ daction. Chaque fonction dispose donc de son propre jeu de variables à la manière dun petit programme à lintérieur dun plus grand. Deux fonctions différentes peuvent déclarer des variables ayant le même nom sans que le Compilateur ne signale de conflit. Les deux variables homonymes sont deux entités bien distinctes qui peuvent par ailleurs être de types différents : LOCAL FN maPremiereFonction DIM x AS SHORT END FN LOCAL FN maDeuxiemeFonction DIM x AS RECT END FN Lorsquune fonction a fini dêtre exécutée, ses variables locales retournent au néant à lexception des variables qui référencent des blocs de mémoire relogeables ou non que vous avez réservés explicitement ou indirectement. En réalité, dans un tel cas, la variable disparaît, et vous ne pouvez plus y faire référence dans une instruction, mais le bloc de mémoire est considéré comme toujours alloué si vous ne prenez pas soin de le libérer avant de quitter la fonction. Notez quil est assez rare de créer soi-même des pointeurs qui allouent un espace en mémoire, car généralement on laisse FutureBASIC se charger du travail en déclarant des variables. FutureBASIC alloue lespace et le libère automatiquement pour chaque variable que vous déclarez avec linstruction DIM. Le problème devient plus délicat du fait que lon peut faire appel aux fonctions et procédures de la Toolbox dans le corps de la fonction locale, car le programmeur a la possibilité de réserver lui-même un bloc en mémoire de la taille quil souhaite avec des instructions comme NewPtr ou NewHandle. Prenez lexemple suivant : LOCAL FN danger DIM monPointeur AS PTR monPointeur = FN NewPtr(100) END FN Dans lexemple ci-dessus, la variable monPointeur est déclarée, rappelez-vous quune variable pointeur est censée contenir ladresse dun bloc mémoire non relogeable. La fonction de la Toolbox NewPtr réserve un bloc en mémoire de la taille que vous lui demandez (ici 100 octets) et retourne ladresse du bloc en mémoire si tout sest bien passé. Cette adresse sera alors rangée dans la variable monPointeur. Lorsque la fonction se termine, FutureBASIC détruit la variable monPointeur qui contient cette adresse, vous navez alors plus aucun moyen de connaître lemplacement où le bloc a été alloué. Maintenant, imaginez que votre fonction soit appelée des centaines de fois au cours du programme. Que se passera-t-il ? Eh bien, à chaque fois que la fonction sera exécutée une nouvelle variable monPointeur sera créée, et un nouveau bloc de 100 octets sera réservé. Les blocs successifs qui vont être alloués ne disparaîtront pas comme par enchantement, au contraire, ils vont saccumuler jusquà saturer la mémoire et conduire au blocage de votre application. Ce phénomène est une erreur très fréquente en programmation, on appelle cela plus communément une fuite de mémoire (ou memory leak en anglais). Il nest pas toujours facile de déceler les fuites de mémoire dans un programme, dans la mesure où tout peut sembler fonctionner normalement pendant très longtemps si la fuite nest pas importante, jusquau moment où le programme se bloque faute de mémoire disponible sur une opération anodine nayant aucun rapport avec la cause du plantage. Pour éviter de tomber dans ce piège, il est souhaitable de suivre au plus près les règles suivantes : - Tout espace réservé durant lexécution dune fonction locale devrait autant que faire se peut être libéré avant que la fonction ne prenne fin : LOCAL FN fonctionSecurisee DIM monPointeur AS PTR monPointeur = FN NewPtr(100) REM faire quelque chose avec le pointeur DisposePtr(monPointeur) END FN La procédure de la Toolbox DisposePtr libère la mémoire occupée par un bloc non relogeable. - La deuxième règle est de ne pas allouer vous-même de pointeur sur un bloc de mémoire et de laisser FutureBASIC faire le ménage pour vous : LOCAL FN fonctionSansDanger DIM maVariable.100 REM faire quelque chose avec maVariable END FN Ci-dessus, le Compilateur réservera un bloc de 100 octets pour la variable maVariable, bloc quil libèrera en même temps que la variable sera détruite. Notez que les bouts de code ci-dessus sont présentés à titre dillustration, en réalité, sur les millions de lignes de code quil ma été donné de consulter, je peux compter sur les doigts dune seule main le nombre de fois où jai pu constater que le programmeur avait réservé de la mémoire au moyen de la fonction NewPtr. La dernière forme montrée est la forme en vigueur chez les programmeurs FutureBASIC. Si la difficulté engendrée par les pointeurs explicitement déclarés peut être réduite à néant par lemploi de variables, comme dans lexemple montré ci-avant, il nen va pas de même avec les handles. Le Compilateur ne réservera, ni ne libérera automatiquement la mémoire dans le cas des handles. Le plus souvent, cest le programmeur qui alloue et libère lui-même les blocs relogeables au moyen des appels à la Toolbox NewHandle et DisposeHandle. Les choses se compliquent dautant que beaucoup dautres fonctions et procédures de la Toolbox peuvent se charger de réserver des blocs relogeables pour vous et de vous communiquer le moyen daccéder à ces blocs. La libération des blocs ainsi réservés doit se traiter au cas par cas suivant les fonctions invoquées. Ici, il ny a pas de mystère, il faut consulter la documentation sur la Toolbox d'Apple pour savoir ce quon est autorisé à faire ou pas dans telle ou telle circonstance. Grâce à l'autonomie des fonctions locales, la mémoire est utilisée plus efficacement, leurs variables noccupant lespace mémoire que durant le bref instant de lexécution de la fonction. Lavantage immédiat des fonctions locales est de faciliter lécriture de programmes très sophistiqués, comportant des milliers de fonctions, sans jamais saturer la mémoire par une cohorte de variables. De surcroît, il devient facile de faire évoluer de tels programmes. Portée des variables Tout ceci est parfait, mais que se passe-t-il si une fonction locale a besoin de connaître, pour éventuellement modifier, la valeur dune variable qui a été déclarée et assignée en dehors de son champ daction ? LOCAL FN bienvenue PRINT "Bonjour "; prenom END FN // Programme principal DIM prenom AS STR63 prenom = "Élodie" FN bienvenue Le programme ci-dessus ne donnera pas le résultat qu'on pourrait croire, car la variable prenom n'est pas connue de la fonction locale bienvenue. Créer une autre variable prenom à l'intérieur de la fonction ne résoudra pas le problème, on aura juste créé une variable différente qui n'existera que durant l'exécution de la fonction. Il y a deux possibilités pour régler cette apparente difficulté. La première qui vient à l'esprit est de se dire qu'il serait nécessaire d'avoir des variables particulières qui seraient "vues" par toutes les fonctions locales qui pourraient en avoir besoin. Il est, en effet, possible de créer ce genre de variable grâce à la structure BEGIN GLOBALS/END GLOBALS. Toutes les variables déclarées à l'intérieur d'une telle structure sont accessibles par l'ensemble des fonctions locales du programme. Rappelez-vous que l'ordre dans lequel apparaissent vos déclarations a une importance au moment de la compilation, c'est pourquoi de manière générale, on commence par déclarer dans son code source les variables qui auront une portée globale dans l'ensemble du programme. Le programme ci-dessus, s'écrirait éventuellement comme ceci pour atteindre le résultat souhaité : BEGIN GLOBALS DIM gPrenom AS STR63 END GLOBALS LOCAL FN bienvenue PRINT "Bonjour "; gPrenom END FN // Programme principal gPrenom = "Élodie" FN bienvenue La tendance naturelle d'un apprenti programmeur est de déclarer une variable en tant que globale à chaque fois qu'il a besoin de la manipuler dans différentes fonctions. Autant le dire tout de suite, ceci n'est pas une bonne pratique. La première bonne habitude à prendre est de faire le contraire en utilisant le moins de variables globales possible; cet exercice vous oblige à réfléchir sur les réels besoins de votre programme, sur l'organisation des variables qu'il doit manipuler et sur les fonctions qu'il vous faudra écrire pour réaliser les tâches dévolues à votre application. Le deuxième avantage, et sans doute le plus important, est de vous conduire à écrire des fonctions complètement indépendantes qui pourront, par conséquent, être réutilisées dans d'autres projets par simple copier/coller sans plus de travail à fournir de votre part. À la différence des routines appelées par GOSUB qui ont besoin d'avoir accès aux variables qu'elles doivent manipuler, les fonctions-utilisateur sont très facilement réutilisables. C'est de cette façon que les programmeurs se constituent au fil des années leurs bibliothèques de fonctions spécialisées prêtes à l'emploi. Fort heureusement, on ne réinvente pas la roue à chaque fois que l'on commence un nouveau projet, mais notez que ceci est vraiment beaucoup plus difficile à obtenir avec des routines appelées par GOSUB due à leur dépendance avec le programme principal dans lequel elles auront été programmées à l'origine. Une autre bonne habitude à prendre également concernant les variables globales est de les nommer de manière à pouvoir les distinguer facilement partout où elles sont utilisées. La convention traditionnellement adoptée est de leur donner un nom symbolique qui commence avec un g minuscule. Il est alors aisé pour vous, comme pour un éventuel lecteur, de déterminer à la première lecture, dans un bout de code isolé, les variables qui sont locales à une fonction et celles qui sont susceptibles d'être modifiées ailleurs par le programme. La deuxième solution au problème posé consiste à envoyer à la fonction des informations au moment où elle est appelée. On peut passer des paramètres à une fonction locale, mais cette fonctionnalité nécessite à elle seule un long chapitre d'explications [Precédent] [Table des Matières] [Suivant] |
{Note} |
|
FutureBASIC est une marque déposée appartenant à Staz Software, Inc et utilisée avec permission. |
||