Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul...

22

Click here to load reader

Transcript of Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul...

Page 1: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

INF145   : DEUXIÈME COURS Les fonctions (première partie)

Rappelons-nous quelques considérations générales toutes issues du travail fait sur les algorithmes numériques (théorie comme exercices du premier cours)

Dans l’implémentation d’un algorithme, on trouve au tout début les déclarations des variables nécessaires aux conditions initiales (CI) qui recevront des valeurs choisies librement par l’utilisateur. Puis viennent les déclarations des variables de travail avec leurs initialisations correctes (à valeurs contraintes, ces variables ne sont pas libres, les variables de travail servent au maintien du résultat et des valeurs transitoires des calculs). Vient ensuite le groupe cohérent d’instructions {expressions, conditionnelles et itératives} qui mènent au résultat.

Seules les conditions initiales (CI) peuvent changer lors d’une exécution de l’algorithme. Tout le reste (initialisations des variables de travail et instructions) reste invariant et c’est fondamental pour l’assurance de qualité du logiciel : La méthode de calcul est bien spécifiée, l’utilisateur de ce code (le client) peut être assuré de sa performance. L’ensemble des variables de travail et des instructions doit être considéré comme une boîte noire aux comportements bien définis par son auteur

Conceptuellement, un algorithme porte un nom, ce sera l’identificateur de la fonction bien choisi par son auteur. Ce nom doit être représentatif du résultat obtenu à son utilisation. Les Conditions Initiales essentielles pour obtenir une exécution correcte formeront ses paramètres -- et donner des valeurs cohérentes aux paramètres lors d’un appel de la fonction sera l’unique responsabilité du client –

Grâce aux fonctions, peu importe la langue de programmation, on parle de gains fonctionnels. La création d’une fonction introduit un outil logiciel qui

1. donne un passage automatique des Conditions Initiales (C.I.) du client à l’algorithme, sans intervention humaine. C’est le procédé de paramétrisation analogue à celui bien connu des mathématiques.

2. donne un retour automatique du résultat de l’algorithme au client de la fonction3. assure l’indépendance totale de l’algorithme.4. donne une sérieuse assurance de résultat  puisque l’auteur de la fonction a eu la

responsabilité de lui faire subir toute une batterie de tests assurant la validité et l’universalité de son code.

5. bénéficie d’une excellente description à travers un système d’aide complet et standardisé. C’est encore la responsabilité de l’auteur de donner dans l’aide les spécifications d’exécution qui varient selon les valeurs données en paramètres

6. permet une réutilisation naturelle et sécuritaire de la fonction dans de nombreux projets où cet algorithme est nécessaire (et je tiens beaucoup à la métaphore de la montre, le fabricant ne conseille jamais au client de la démonter)

Toute la conception moderne de qualité du logiciel, après à peine 70 ans d’existence de l’informatique, est d’abord issue de la factorisation possible du code en fonctions et aux tests systématiques qui peuvent être faits sur des parties fonctionnelles indépendantes bien identifiées et bien documentées. C’est la véritable base du développement modulaire ou objet des logiciels modernes

Page 2: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Maintenant, on doit se rappeler des spécificités des blocs du C :

Les instructions en C doivent être dans des blocs

On peut faire des déclarations de variables « locales » dans tout bloc avant les instructions contenues dans ce bloc

Les blocs peuvent être imbriqués « à volonté »

Les blocs les plus externes – un bloc qui n’est pas dans un bloc – sont différents, ils possèdent un identificateur -- un nom -- Ce sont les fonctions qui peuvent recevoir des paramètres et structurent notre code

On formalise aujourd’hui le concept de fonction avec  trois règles fondamentales sur leur existence un standard obligé dans la présentation d’une fonction, le commentaire à la déclaration

d’une fonction un deuxième standard, le commentaire à la définition d’une fonction le concept de variable locale à une fonction le passage par référence ( dans la partie 2)

Règle #1 : l’appel d’une fonction existante par un clientDans une expression, un identificateur immédiatement suivi d’une parenthèse ouvrante indique l’appel de la fonction portant ce nom

Les expressions séparées par des virgules à l’intérieur des parenthèses sont d’abord évaluées et forment les paramètres effectifs de cet appel

Lors d’un appel, la fonction reçoit les valeurs des paramètres effectifs et s’exécute alors avec ces valeurs librement choisies par le client de la fonction. La fonction retourne une valeur qui prendra littéralement la place de l’appel de la fonction dans le code client.

Tout ce procédé fonctionne de façon très intuitive et on considère l’appel d’une fonction comme une expression qui sera évaluée

les paramètres effectifs sont les valeurs à donner aux conditions initiales (CI) de l’algorithme numérique implémenté par la fonction. Ce sont des variables libres, le client de la fonction a la possibilité de les choisir et ce choix est réalisé avec notre règle #1

le nombre de paramètres effectifs nécessaires au déclenchement d’une fonction est bien défini et invariant

si la fonction n’a pas de paramètres, ce qui reste rare en programmation scientifique, l’identificateur avec des parenthèses vides déclenche son exécution

Prenez bien note que l’identificateur seul d’une fonction ne déclenche pas l’exécution du code. Le nom seul d’une fonction pourra servir comme expression correcte en C dans un contexte nettement plus avancé – on verra qu’une fonction peut être passée en paramètre --

Exemples d’appel Soit une fonction nommée pgcd qui retourne le plus grand commun diviseur de deux entiers, un appel correct serait pgcd(12*15, cpt_num %14) en assumant que cpt_num est entier

L’expression sin( 2.56 * pgcd(35672, 89*56)) est un appel correct du sinus offert dans la librairie classique (math.h) du C qui donnera la valeur -0.9140915941 de type double.Règle #2 : la déclaration d’une fonction ( la responsabilité de l’auteur )

Page 3: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Comme tout identificateur doit être déclaré avant d’être utilisé à l’intérieur d’une expression, une fonction doit être déclarée avec la syntaxe :

modif_acces type identificateur(liste de paramètres formels);

dont chaque élément est décrit par :

modif_acces sert à limiter la disponibilité de la fonction, sa valeur se limite aux deux mots réservés (extern, static). static sert à limiter l’accessibilité de cette fonction à son unité de compilation (elle sera privée à son fichier). En absence de spécification, la valeur par défaut considérée par le compilateur est extern et l’accès du client est illimité.

type indique un type connu du compilateur auquel appartient la valeur retournée par un appel correct de la fonction.

Identificateur est le nom choisi pour la fonction par son auteur. Ce sera le nom d’usage normal d’un appel de la fonction par un client.

On aura le même nombre de paramètres formels dans la déclaration que de paramètres effectifs dans l’appel (pour satisfaire la règle #1)

Un paramètre formel, c’est la déclaration d’une variable locale à la fonction qui sera initialisée par son paramètre effectif correspondant lors d’un appel. Cette initialisation vous est gracieusement offerte par le compilateur.

Si la liste de paramètres formels est vide, on l’indique avec le mot réservé void. Si la fonction ne retourne rien – ce qui reste relativement rare en C – on l’indique avec le mot réservé void.

Dans la littérature plus ancienne du C, on retrouve souvent le mot prototype de fonction à la place de déclaration de fonction.

Le terme prototype fait référence à l’action du compilateur qui traite l’appel d’une fonction en vérifiant la bonne compatibilité du type de chaque paramètre effectif avec le type du paramètre formel correspondant.

On trouve souvent un prototype incomplet, il omet volontairement les identificateurs des paramètres formels ( qu’on ne retrouvera alors que dans la définition de la fonction). Cette variante provoque la perte d’identificateurs représentatifs et implique malheureusement une perte de qualité du code.

Nous éviterons la plupart du temps cette pratique (mais pas toujours, puisqu’elle est sans danger dans les cas où le contexte d’utilisation est évident, citons double sin(double);

Exemples de déclarations

La déclaration de la fonction pgcd déjà vue devra donc être extern int pgcd(int n1, int n2);

Et la déclaration de la fonction sinus dans math.h doit être extern double sin(double) ;

Page 4: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Commentaires à la déclaration d’une fonction ( de la responsabilité de l’auteur )

Ce sont les commentaires les plus importants.Ils s’adressent à tout client éventuel de la fonction et lui expliquent le service offert par cette fonction comme les limites normales prévues à son utilisation

Ce sont les vrais commentaires d’aide, les seuls prévus à tout client éventuel de la fonction qui lui expliquent :

1. le service offert par cette fonction

2. le sens à donner à chaque paramètre

3. les limites normales prévues à son utilisation – par exemple, pas de valeur négative donnée en paramètre au log de math.h sinon… --

Toute déclaration de fonction suit un modèle immuable dans l’institution où vous travaillez.

Je vous donne ici un modèle minimal que vous devrez obligatoirement suivre pendant toute la session

Ce modèle est basé sur une suite ordonnée :

1. Un séparateur seul ou possiblement avec l’identificateur de la fonction en majuscules à l’intérieur.

2. Une description succincte du but visé par cette fonction

3. Une description du rôle donné à chaque paramètre formel.

4. Une description des valeurs possibles au retour.

5. Et les spécifications qui donnent réponse aux interrogations légitimes du client sur les valeurs retournées par la fonction face à la présence de certains paramètres effectifs qui pourraient sembler bizarres ou incorrects lors d’un appel.

6. La déclaration proprement dite.

7. Un séparateur.

Trois exemples de déclarations de fonctions avec le commentaire:

/*--------------------------------------------------*//* nombre_diviseurs donne la somme des diviseurs d'un entier*/ /* PARAMETRE : l'entier à traiter *//* RETOUR : le nombre de ses diviseurs */ /* *//* SPÉCIFICATIONS : */ /* On traite la valeur absolue du paramètre *//* La valeur retournée sera toujours positive *//* La partie décimale d’un paramètre effectif réel sera tronquée*//* Tous les diviseurs sont sommés ( dont 1 et le paramètre ) *//* Recevant 0, la fonction retourne 0(et ce sera le seul 0)*/

extern int nombre_diviseurs(int valeur); /*--------------------------------------- ---------------*/

Page 5: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

/*============== MILES_2_KILOMETRES ==================*//* transforme une distance, de miles en kilomètres*/ /* PARAMETRE : une distance en miles, un réel quelconque *//* RETOUR : la même distance en kilomètres */ /* *//* SPÉCIFICATIONS : */ /* on respectera le signe du paramètre reçu */

double miles_2_kilometres( double distance ); /*=============================================*/

/*======================= X_ALA_N =======================*//* algorithme très rapide pour obtenir *//* une puissance entière d'un réel quelconque */ /* PARAMETRE : x un nombre réel quelconque *//* n la puissance entière *//* RETOUR : la puissance calculée */

/* SPÉCIFICATIONS : */ /* On traite correctement le signe de la puissance. *//* Le résultat d’un appel avec 0 à une puissance négative est *//* imprévisible */

extern double x_ala_n(double x , int n);

/*==========================================================*/

Règle #3 La définition d’une fonction (de la responsabilité de l’auteur)

C’est la nécessaire présence du code, de variables de travail bien initialisées, de la suite cohérente de blocs internes, d’expressions et d’instructions de la fonction.

Une fonction DOIT être définie

Une fonction est définie par la répétition de sa déclaration ( et ici les identificateurs des paramètres formels sont obligatoires) suivi de son bloc de code complet.

La définition répète la déclaration de la fonction sans son point-virgule à la fin et avec un identificateur pour chaque paramètre formel

On y retrouve le bloc de code à exécuter pour obtenir le résultat voulu. Dans ce bloc de code, on retrouve au moins une instruction comportant le mot réservé return permettant de terminer l’exécution de la fonction et de retourner la valeur de l’expression qui suit immédiatement ce return. C’est cette valeur qui remplacera littéralement l’appel de la fonction dans le code client.

On retrouve parfois plusieurs return dans une fonction dont l’activation dépend de conditions bien précises, ils sont évidemment conditionnels comme dans if(ca&&ca&&ca){return valeur;}//sinon l’exécution continue

Une fonction que l’on veut accessible hors du module où elle est définie doit être préfixée du mot réservé extern – on dit alors qu’elle est publique – . Ce modificateur étant donné par défaut à une fonction, on oublie souvent de noter explicitement sa présence

Page 6: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Une fonction quelconque définie dans un module ne sera accessible que dans son implémentation si elle est préfixée du mot réservé static. Une fonction peut donc être privée à son unité de compilation.

Les fonctions privées servent à bien factoriser une fonction publique trop lourde – la quantité de code nécessaire est alors si importante que naturellement on sépare ce processus en actions fonctionnelles distinctes plus faciles à tester individuellement lors de la conception et à maintenir à long terme. Cette forme de conception modulaire ajoute beaucoup à la qualité générale du logiciel.

Maintenant connus les principes de déclaration et de définition d’une fonction, l’obtention de véritables fonctions à partir des algorithmes numériques des exercices du cours précédent pourra se faire particulièrement rapidement.

Les commentaires à la définition d’une fonctionCe commentaire n’a rien à voir avec celui de la déclaration

Ce sont les commentaires du maintien ou de l’amélioration de l’algorithme

Ce sont les commentaires que personne ne veut lire   !!!

Ils ne s’adressent absolument pas au client. Êtes-vous intéressé à savoir comment le calcul du sinus se fait  dans l’implémentation de la librairie ? Avez-vous besoin de connaitre la méthode de calcul utilisée avant d’utiliser une fonction trigonométrique ? En fait, pour beaucoup de fonctions classiques, c’est beaucoup plus complexe que la simple série de Taylor vue dans votre premier cours de calcul, ce calcul serait bien trop lent et ce serait difficile avec cette technique primitive de maintenir une erreur relativement constante.

Ce sont des commentaires extrêmement importants qui assurent la qualité générale du code et sa stabilité. Ils s’adressent uniquement à un collègue responsable de renseigner encore mieux le code existant et/ou de le maintenir et/ou d’ajuster certaines spécifications et/ou de l’améliorer et aussi à la limite -- malheureusement trop souvent -- de le debugger.

Tout commentaire à la définition d’une fonction suit un modèle immuable dans l’institution où vous travaillez.

Je vous donne ici un modèle minimal en trois parties que vous devrez obligatoirement suivre pendant toute la session

À priori de la définition, une partie dite « stratégie » qui résume toute l’approche de la résolution algorithmique du problème. C’est une partie importante du commentaire de définition

Directement dans le code, le commentaire ligne des expressions est à même indentation que le code qu’il commente – et non pas à sa droite, fini l’assembleur-- Toute expression complexe comme toute structure conditionnelle ou itérative doit être sommairement commentée.

Le choix éclairé des identificateurs des variables locales amène un commentaire naturel à votre code et tend à limiter le besoin du commentaire à chaque ligne

Une vision trop élémentaire de la programmation vous ferait croire que seul le commentaire ligne existe. C’est une erreur assez banale qu’on rencontre malheureusement trop souvent.

Page 7: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Le concept de variable localeUne variable locale à une fonction peut exister, sa déclaration apparaît dans la fonction et elle correspond à une variable de travail nécessaire dans un bloc de l’algorithme.

Cette variable n’est accessible que de sa déclaration à la fin du bloc qui contient sa déclaration. Elle ne possède une place dans la mémoire de la machine hôte du programme que durant l’exécution de la fonction.

Une fois l’exécution de la fonction terminée, l’espace mémoire utilisé par ses variables locales est libéré et cet espace mémoire sera sans doute rapidement utilisé à d’autres fins (ce procédé maximise l’utilisation de la mémoire disponible).

Les variables locales d’une fonction n’utilisent pas nécessairement la même position dans la mémoire de travail de la machine d’appel en appel de la fonction. Une fonction ne mémorise pas les valeurs antérieures de ses variables locales, une fonction n’a pas de mémoire à priori.

Existe aussi dans toutes les langues de programmation, la capacité de forcer une variable locale à utiliser le même espace Ram durant toute l’exécution du programme tout en ne restant accessible que dans la fonction. L’obtention de ce privilège est toujours explicite et est toujours associé à un mot réservé de la langue, ce sera par exemple persistent en Matlab, option memory en maple et static en C.

On pourra donc mettre en place une attribution alternative d‘espace pour les variables, la déclaration d’une variable locale qui est préfixée du mot réservé static implique que cette variable aura une place inamovible dans la mémoire pour toute la durée de l’exécution du programme, cet espace ne sera utilisé que par cette variable. Le prix à payer, c’est une utilisation non optimale de la mémoire disponible. On n’utilise jamais static sans raison importante, l’apparition d’une variable statique – c’est le nom qu’on leur donne – devra toujours être amplement commentée.

Mise en garde importante Une variable globale, c’est une variable définie hors de tout bloc et qui est accessible dans toutes les unités de compilation d’un projet. Je tiens à être clair : L’utilisation de globales en programmation moderne est prohibée et infamante, jamais vous n’utiliserez de globales dans notre cours.

Voici maintenant un exemple de définition complète d’une fonction/*=========================================================*/ double x_ala_n( double x , int puissance){/*=========================================================*/ // obtenir une puissance entière d'un réel quelconque /* STRATÉGIE :La suite x, x^2, x^4, x^8, x^16, x^32….. obtenue en ln(puissance) opérations va déborder la puissance voulue. En construisant cette suite, on décompose l’entier puissance simultanément en binaire ( en puissances de 2), on sait donc si le terme de la suite sera utilisé pour obtenir (x^puissance). Sachant que 857 = 1101011001 = 512+256+64+16+8+1

X^857 = x** x^8 * * x^16 * * x^64 * * x^256 * x^512 Et si la puissance est négative, on inverse le résultat obtenu.*//*=========================================================*/

/* accumulateur de la puissance à rendre */

Page 8: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

double resultat = 1;

/* indicateur du signe de la puissance puis on rectifie le signe */int positif = 1; if ( puissance < 0 ) {

positif = 0;puissance *= -1;

}/* maintenant une descente à 0 de la puissance positive*/while ( puissance ){

/* si le bit de droite de puissance est un 1 *//* on accumule la puissance de x actuelle */if (puissance %2) {

resultat *= x ;} /* et on passe de (x ^k) à (x^2k) */x = x*x ;/* on coupe le bit de droite de la puissance */puissance /= 2;

}

/* on prend l'inverse si la puissance était négative*/if (!positif) {

resultat = 1.0 / resultat ; }return resultat;

}/*=========================================================*/

Considération plus théorique sur la définition en C Avec une fonction, le besoin de déclarer et de définir est évident :

Déclarer pour un client qui a besoin de connaître l’identificateur et les exigences en paramètres pour se servir de la fonction

Définir puisque le compilateur, malgré toutes ses qualités, n’inventera jamais la suite d’instructions à faire pour obtenir le résultat voulu.

Je n’avais pas parlé de définition au premier cours. Les identificateurs déclarés étaient simplement ceux de variables ou de constantes. Mais le concept de définition existe aussi pour elles sauf qu’ici c’est le compilateur qui en prend toute la responsabilité.

La définition d’une variable existe donc, c’est la nécessaire demande de positionnement dans la mémoire du train de bytes nécessaire à sa valeur durant sa durée de vie. Rien d’autre à ajouter sauf que vous pouvez toujours synchroniser déclaration et définition d’une variable en l’initialisant.

Autre mise en garde importante :Certains auteurs un peu « cheap » couplent toujours déclaration et définition de fonctions, se disant sans doute que leurs étudiants sont trop bêtes pour comprendre le sens d’unité de compilation, de module, de librairie ou de simple bonne pratique en programmation.

Ce n’est évidemment pas le cas dans notre cours où toujours vous devrez faire une différence très nette dans tous vos devoirs et travaux entre

déclaration et définition de fonctions

Page 9: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

commentaires à la déclaration et commentaires à la définition de fonctions

Exercices sur les fonctions Pour chaque problème posé, vous devez dans l’ordre :

1. écrire plusieurs appels corrects de la fonction donnant des résultats différents.

2. donner la déclaration de la fonction.

3. donner la définition de la fonction.

4. tester la fonction avec les appels corrects écrits en partie 1

Vous ne ferez pas tous ces exercices, je le sais. Faites d’abord les parties 1 et 2 pour toutes les demandes et après pour le plus grand nombre mais sans abuser…. ajoutez les parties 3 et 4, échangez vos solutions puis lisez et testez celles da vos collègues Surtout, dans la définition d’une nouvelle fonction, utilisez au maximum celles dont vous avez déjà la déclaration, on DOIT toujours maximiser en C la réutilisation des outils fonctionnels déjà présents. Le principe de factorisation du code en éléments fonctionnels indépendants ( conception, algorithme, tests et commentaires)

Une fonction qui teste si un entier donné est le cube d’un entier plus petit. Par exemple, recevant 343 elle retourne vrai(1), mais avec 1070 c’est faux.

Une fonction qui compte le nombre de chiffres impairs d’un entier donné.

Une fonction qui compte les occurrences d’un chiffre donné dans un entier donné.

Une fonction qui retourne la valeur d’un chiffre à une position donnée dans un entier.

Une fonction qui teste si tous les chiffres d’un entier sont impairs.

Une fonction qui transforme une distance donnée en miles en kilomètres

Une fonction qui transforme les gallons en litres. Une fonction qui transforme une consommation en miles au

gallon en litres aux 100 kilomètres. Une fonction qui donne le nombre de diviseurs d'un entier. Une fonction qui donne la somme des diviseurs d'un entier

autres que i et lui-même. Une fonction qui teste si un entier est parfait (un entier N

est parfait si la somme de tous ses diviseurs différents de lui-même = N) par ex : 6 = 1+2+3.

Une fonction qui teste si un entier est premier. Une fonction next_prime qui retourne le plus petit entier

premier supérieur au paramètre. Une fonction qui reçoit deux paramètres : un entier k et une

valeur N, elle retourne le k_ième nombre premier supérieur à N si k est positif et inférieur sinon.

Page 10: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Une fonction qui retourne la distance entre deux réels. Une fonction qui donne le nombre de diviseurs premiers d'un

entier. Une fonction qui retourne la distance maximale entre trois

réels reçus en paramètres. Une fonction qui renverse les chiffres d'un entier donné. Une fonction qui trouve l’entier ayant un maximum de

diviseurs dans un intervalle donné. Une fonction qui fait la moyenne du nombre de diviseurs des

entiers dans un intervalle donné. Une fonction qui compte le nombre d’entiers premiers dans

un intervalle donné. Une fonction qui donne la partie fractionnaire d’un nombre

réel (recevant -608.345, elle retourne 0.345)

Note importante Ces exercices peuvent aussi servir à s’initier au problème des spécifications : Souvent le créateur d’une fonction limite de façon cohérente les valeurs admises en paramètres effectifs (donc pas à tout le domaine représenté par le type du paramètre formel). L’auteur doit alors décrire (dans le commentaire standard de spécifications du .h) tout comportement spécial prévu et le comportement correspondant aux spécifications DOIT être implémenté dans la définition de la fonction ( dans le .c).

Page 11: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Les fonctions (deuxième partie, passage par référence)

Introduction naïve :

Cette partie du cours #2 n’est qu’une petite partie de notre cours #4 – TOUT SUR LES ADRESSES –

On ne s’intéresse ici qu’à augmenter la capacité de transformation de l’univers du client à l’utilisation d’une fonction.

Mise en situation :

Rapidement les étudiants qui abordent les fonctions en C nous demandent :

comment faire pour qu’une fonction nous retourne DEUX résultats ? À priori c’est impossible…. Une fonction ne retourne qu’une valeur d’un type bien défini ( et peu importe votre connaissance du C avec tous les artifices possibles…. Cette règle restera immuable)

comment faire pour qu’une fonction puisse modifier une ou plusieurs de MES variables…. Par exemple une fonction qui pourrait mettre en ordre deux de mes variables réelles. Actuellement vous sauriez uniquement faire une fonction qui dit si ces deux valeurs sont en ordre.

Mais WOW …… la possibilité en C d’obtenir la véritable position dans la mémoire d’une variable dans un programme en exécution et de donner en paramètre effectif cette adresse à une fonction apte à recevoir une telle valeur dans un de ses paramètres formels existe en C. La fonction pourra donc de se servir de la position de cette variable pour y lire ou y écrire…..

Ce privilège extraordinaire se nomme « passage par référence », nous ne ferons que l’aborder ici pour des entiers et des réels. Durant tout le reste de la session, il prendra une importance capitale.

La mémoireLa mémoire symbolique d’une machine est un vecteur d’octets. Chaque octet possède un numéro dans l’intervalle [ 0, quantité de mémoire ]. Tout programme, peu importe la langue de programmation, se sert uniquement de cette mémoire symbolique.

La mémoire physique d’une machine dont la géographie, le maintien et le comportement sont d’une grande complexité, est toujours atteinte avec le contrôleur d’adresse au service de tout programme dont le système d’exploitation(OS)

Une adresse Une adresse est une valeur qui possède une position mémoire et un type (donc potentiellement le pouvoir de considérer son contenu). C’est la conjonction d’un numéro d’octet de départ dans la mémoire et d’un contenu de type T.

L’adresse d’un contenu de type T possède le type T* et on pourra atteindre son contenu en lecture comme en écriture à l’unique condition que sizeof(T) soit bien défini ( évidemment que c’est vrai pour les types numériques int et double que nous utilisons actuellement)

L’opérateur &  : l’expression obtenue de l’opérateur & en préfixe à une variable de type T est évaluée comme une adresse de type T*

Les pointeursOn donne le nom générique de pointeur à toute variable faite pour garder une adresse.

Page 12: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Pour un type quelconque T, la déclaration d’une variable de type T* est possible. Ce pointeur est apte à garder une adresse dont le contenu est un T. Ainsi avec la déclaration T * ptr (où T n’est ni void ni de type fonction) bien initialisé avec l’adresse d’une variable de type T, l’expression *ptr donnera accès au contenu de type T à la position actuelle du pointeur.

Deux exemples de base Le premier, une fonction qui retourne la moyenne des diviseurs d’un entier (c’est un réel) et qui inscrira le nombre de diviseurs du premier paramètre dans le second paramètre par référence

Voici sa déclaration :

/*retourne la moyenne et donne dans son second paramètre le nombre de diviseursPARAMETRE : valeur : l’entier à traiter ptr_nb : l’adresse où donner le nombre de diviseurs

RETOUR : la moyenne des diviseurs SPÉCIFICATIONS : on traite la valeur absolue du premier paramètre

recevant 0 comme valeur, elle retourne automatiquement 0 */ double moy_nb_divs( int valeur , int * ptr_nb);

je vous montre son usage par un client dans un bloc quelconque 

{ int va = 2045770 ;int div ;double moy ;

// remarquez avant tout dans l’appel de la fonction// l’obligation d’utiliser l’opérateur adresse pour // que le type du second paramètre effectif soit cohérent// avec celui du paramètre formel correspondant moy = moy_nb_divs(va, & div ) ;

// puis j’affiche les résultats obtenusprintf("\n moyenne des diviseurs de % d = %f ", va, moy );printf("\n le nombre de diviseurs de % d = %d ", va ,div);

}

Voici sa définition :

double moy_nb_divs( int valeur , int * ptr_nb){ /* STRATÉGIE :

prendre la valeur absolue de la valeuren traversant l’intervalle de 1 jusqu’à valeur on compte et on somme les diviseurs

*/

Page 13: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

double som_div = 0;int iter = 1 ;

* ptr_nb = 0 ;valeur = abs(valeur);

if (valeur == 0){return 0;}

while(iter <= valeur){// un diviseur est sommé et comptéif( (valeur % iter) == 0){

* ptr_nb +=1 ;som_div += iter;

}

iter += 1 ;}//on calcule la moyennereturn som_div /(* ptr_nb) ;

}

Le second exemple, la fonction ordonner qui permet au client de mettre en ordre les valeurs de deux de ses variables réelles.

Voici sa déclaration :

/* elle va permuter les valeurs de deux variables si leurs valeurs ne sont pas dans l’ordre naturel, la plus petite valeur vient en premier. PARAMETRE : deux adresses de nombres réels

RETOUR : Rien SPÉCIFICATIONS : aucune

*/ void ordonner( double * ptr_val1 , double * ptr_val2);

je vous montre son usage dans un bloc quelconque {

// un client déclare et initialise deux réels double x1 = 10.2 ;double x2 = -15.25 ;

// puis on les afficheprintf(" ( %f , %f ) \n", x1 , x2);

// remarquez avant tout dans l’appel de la fonction// l’obligation d’utiliser l’opérateur adresse pour // que le type des paramètres effectifs soit cohérent// avec celui des paramètres formels ordonner( &x1 , &x2) ;

Page 14: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

// on les affiche encoreprintf(" ( %f , %f ) \n", x1 , x2);

}

Voici sa définition :

void ordonner( double * ptr_val1 , double * ptr_val2){

/* STRATÉGIE : tester si l’indirection des pointeurs nousdonne l’ordre naturel, sinon on permute les valeurs aux adresses données

*/ if( (* ptr_val1) > (* ptr_val2)){

// on se sert d’une variable temporairedouble temp = * ptr_val1 ;* ptr_val1 = * ptr_val2 ;* ptr_val2 = temp ;

}}

C’est tout, l’existence des pointeurs permet ces constructions utiles.Mais le risque associé à l’utilisation d’adresses dans un programme demande beaucoup de rigueur chez les ingénieurs qui s’en servent.

Page 15: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

Les fonctions (troisième partie, les modules)

Les modules existent pour que :

1. l’ensemble de ce que l’auteur y met pour son client soit complet et cohérent2. l’offre soit bien commentée à travers une interface bien faite.3. le contenu soit facilement réutilisé de projet en projet.

On retrouve deux formes générales de modules

Les modules de fonctions, plusieurs fonctions apparentées sont regroupées et forment alors un ensemble cohérent dans un module.

La présence de déclarations bien commentées dans le fichier d’interface permet au client de bien utiliser chaque fonction, de donner des conditions initiales crédibles(en paramètres effectifs) en déclenchant la fonction et d’obtenir ainsi le résultat souhaité.

Ces modules sont relativement simples, le module op_bits que je vous ai donné en est un bon exemple, la librairie standard « math » en est un autre.

Les modules offrant des objets qui ont un état propre associés à de la mémoire. Ces modules doivent aussi offrir les fonctions qui assurent un bon maintien et un usage correct des objets offerts.

Les plus « simples » de ces modules offrent un objet unique (mtwister ou un_chrono par exemples) avec les fonctions qui assurent leur bon fonctionnement.

Les plus généreuses offrent à un client une multiplicité d’objets mutuellement indépendants ( bien que simple, le module « ecrir_texte » permet de maintenir ouverts plusieurs fichiers texte et d’y écrire ). Ce type de modules forme le cœur et les os de toute la programmation scientifique moderne.

Tous les modules ont en commun de se composer de deux fichiers portant le même nom mais différenciés par l’extension ( .h et .c )

Le premier, le fichier d’interface – celui qui porte l’extension .h –

C’est uniquement cette interface qui intéresse tout client potentiel, puisqu’elle décrit ce que l’on gagne à utiliser correctement les fonctions présentes. Les commentaires de l’auteur destiné au client sont fondamentaux à la qualité et à la stabilité du code du client.

Un client avec un ( #include "nom_module.h" ) dans son propre fichier peut inclure toutes les déclarations publiques offertes par l’auteur et avoir accès aux outils offerts.

Par tradition et uniquement pour éviter les éventuels problèmes d’inclusions multiples de l’interface dans un projet, le contenu du fichier d’interface est TOUJOURS enveloppé de trois commandes au pré-compilateur :

Page 16: Microsoft Word - h07_145_cours_02A.doc Web viewPrenez bien note que l’identificateur seul d’une ... c’est beaucoup plus complexe que la simple série de Taylor vue dans votre

#ifndef NOM_MODULE#define NOM_MODULE

Puis vient le contenu de l’interface elle-même : déclarations de fonctionsdéclarations de constantes ou de types

#endif

Le second, celui portant l’extension( .c ) , le fichier d’implémentation contient tout le code, toutes les définitions nécessaires à l’utilisation des déclarations contenues dans l’interface. Mais dans les faits, c’est le fichier qui n’intéresse personne….un client veut utiliser la librairie, pas voir toute la complexité du comment sont obtenus les résultats offerts!

Dans le monde réel de la programmation, ce sont les meilleurs et les plus expérimentés qui décident des interfaces et des algorithmes à utiliser. Les juniors, les débutants, écrivent et testent les implémentations (évidemment encore sous la responsabilité des premiers).

Voilà, c’est bien suffisant pour aujourd’hui.