Procédures et fonctions La modularité et ses avantages. Exemples en C. Notion de blocs et portée des identificateurs. Variables globales et variables locales. Déclaration et définition de fonctions. Communication entre fonctions. Renvoi d’un résultat. Paramètres formels et effectifs d’une fonction. Mode de transmission des données par valeur ou par adresse. Tableau à une ou deux dimensions comme paramètre d’entrée ou de sortie d’une fonction. Les enregistrements comme paramètres de fonctions. Pile d’exécution. Portée des variables. Le type « void ». Appels de fonctions. Fonction « main ». Fonctions arithmétiques standard. Décomposition d’un programme en fonctions.
Les modules Jusqu’ici, nous avons résolu nos problèmes à l’aide de fonctions prédéfinies (printf de stdio, strlen de string, …) et d’une seule fonction nouvelle : la fonction principale main(). Pour résoudre des problèmes plus complexes, nous avons alors besoin de programmes longs, peu structurés et souvent peu compréhensibles. En plus, dans un même programme, on doit répéter plusieurs fois plusieurs suites d’instructions ce qui entraîne un gaspillage en espace mémoire. Objectif de ce chapitre : Définir et utiliser nos propres fonctions afin de rendre nos programmes modulaires. Définition d’un module (une fonction en C) : Désigne une entité constituée de données et d’instructions agissant sur ces données qui fournissent une solution à une partie bien définie d’un problème plus complexe. Un module peut faire appel à d’autres modules, leur transmettre des données et en recevoir. L’usage de ces modules doit permettre de résoudre le problème original.
Avantages d’un programme modulaire Meilleure lisibilité. Diminution du risque d’erreurs. Dissimulation des méthodes : Lors de l’utilisation d’un module, il faut seulement connaître son effet, sans devoir se préoccuper des détails de son implantation. Réutilisation de modules déjà existants : Cela permet d’utiliser maintes fois une portion de code déjà écrite. Simplicité de l’entretien : Des modifications peuvent être apportées à un module sans devoir toucher aux autres modules du programme. Favoriser le travail en équipe : Un programme peut être développé en équipe par délégation de la programmation des modules à différentes personnes ou groupes. Hiérarchisation des modules : Donne lieu à une méthodologie de program- mation (méthode de raffinement successif).
Modules fonctions en C Dans l’environnement de programmation C ou C++, il existe plusieurs librairies de fonctions disponibles. Exemple :
Fonctions en tant que boîte noire Par exemple, sqrt retourne la racine carrée d’un nombre x à virgule flottante passé en paramètre. On peut se servir de cette fonction sans connaître les détails de son implantation. 1. Lors d’un appel à cette fonction, la valeur du paramètre x est transmise à la fonction sqrt. 2. L’exécution de la fonction main est temporairement suspendue. 3. La fonction sqrt devient active et calcule la valeur de renvoi x. 4. Cette valeur de renvoi x est transmise en retour à la fonction main(). 5. La fonction main() termine le calcul en employant cette valeur.
Fonctions en tant que boîte noire La valeur d’entrée d’une fonction n’est pas obligatoirement une variable unique, mais peut être une expression quelconque dont le type correspond au type du paramètre. Exemple : b * b – 4 * a *c.
Usage de la fonction sqrt #include <stdio.h> #include <cmath> void main() { float a, b, c, discriminant; printf("Entrez les coefficients a, b, c : "); scanf("%f%f%f", &a, &b, &c); discriminant = (float) sqrt(b * b - 4 * a * c); printf("L'equation du second degre %f x * x + " "%f x + %f = 0 possede les racines suivantes :" " %f et %f.\n", a, b, c, (discriminant - b) / (2 * a), (discriminant + b) / (-2 * a)); }
Généralités sur les fonctions Certaines fonctions possèdent plusieurs entrées comme, par exemple, la fonction pow(x, y) possède 2 paramètres x et y en entrée et calcule xy. Toute fonction accepte des entrées et une valeur de renvoi d’un type particulier. Ex. : sqrt accepte en entrée et comme valeur de renvoi un nombre. Pour définir notre propre fonction, nous avons la syntaxe suivante : type de la valeur de renvoi nom de la fonction (paramètre1, paramètre2, …, paramètren) { déclarations locales suite d’instructions } Ex. : double absolu(double x) { if (x >= 0) return x; else return –x; }
Modules fonctions en C Exemple I : Afficher à l’écran un rectangle de longueur L et de hauteur H, formé d’astérisques. #include <stdio.h> // Affiche un rectangle formé d'astérisques. // Paramètres d’entrée : // Longueur : longueur du rectangle, // Hauteur : hauteur du rectangle. // Valeur de renvoi : // Nil. void Rectangle(int Longueur, int Hauteur) { int i, j; for (i = 1; i <= Hauteur; i++) for (j=1; j<=Longueur; j++) printf("*"); printf("\n"); } *********** *********** *********** H L Cette fonction ne possède pas de valeur de renvoi.
Modules fonctions en C void main() { // Déclaration des variables de la fonction principale. int L, // Longueur du rectangle. H; // Hauteur du rectangle. // Saisie de la dimension du rectangle. printf("Entrez la longueur(L) et la hauteur(H) du rectangle : "); scanf("%d%d", &L,&H); // Affichage d'un rectangle L x H formé d'astérisques. Rectangle(L, H); } void main() Variables locales: L, H Fonction : Rectangle(L, H) void Rectangle(int Longueur, int Hauteur) Variables locales: Longueur, Hauteur, i, j
Modules fonctions en C main Rectangle Les paramètres sont initialisés lors de l’appel de la fonction. L Longueur H Hauteur Note : L’ordre dans lequel les fonctions sont définies est importante. Une fonction doit être définie avant son utilisation.
Calcul de la valeur d’un compte d’épargne Exemple II : Calculer la valeur d’un compte d’épargne possédant un solde initial s au bout de n années. Si le taux d’intérêt est de p%, le solde au bout de n années est : s (1 + p / 100)n. #include <stdio.h> #include <cmath> // Calcule la valeur d'un compte d'épargne. // Paramètres d’entrée : // s : valeur initiale de l’investissement, // n : le nombre d’années de conservation de l’investissement, // p : taux d'intérêt par année, en pourcentage, // Valeur de renvoi : // le solde après n années. double Valeur_Compte(double s, int n, double p) { double b = s * pow(1.0 + p / 100.0, (double) n); return b; } L’instruction return permet de retourner la valeur de renvoi. On aurait pu écrire : return s * pow(1.0 + p / 100.0, (double) n);
Calcul de la valeur d’un compte d’épargne void main() { // Déclaration des variables de la fonction principale. float solde_initial; int periode; float taux_d_interet; // Entrée des trois paramètres de la fonction. printf("Entrez le solde initial, la periode et le taux d'interet : "); scanf("%f%i%f", &solde_initial, &periode, &taux_d_interet); // Affichage de la valeur du compte d'épargne. printf("\nValeur du compte d'epargne : %f\n", Valeur_Compte(solde_initial, periode, taux_d_interet)); } Note : On a pensé à la réutilisation lors de la définition de la fonction Valeur_Compte en optant pour 3 paramètres clés.
Présence de commentaires Les commentaires sont là pour le bénéfice des humains et non pour les compilateurs. Parmi les humains visés, la priorité doit être donnée à l’utilisateur de la fonction plutôt qu’au programmeur. Les commentaires présents doivent décrire précisément le rôle joué par cette fonction et non des détails d’implantation. Écrire d’abord les commentaires propres à une fonction avant son code. Cela permet de vérifier votre compréhension du rôle joué par cette fonction. Ajouter, le cas échéant, les conditions qui s’appliquent aux paramètres d’entrée. Ces conditions seront prises en compte plus tard. Ex. : // Calcule la sommation suivante : 1 + 2 + 3 + . . . + n. // Paramètre d’entrée : // n : le nombre de termes dans la sommation. // n ≥ 1. // Valeur de renvoi : // la valeur de l’expression : n (n + 1) / 2.
Renvoi du résultat Lorsque l’instruction return est exécutée, la fonction prend fin immédiatement avec la transmission de la valeur de retour. double Valeur_Compte(double s, int n, double p) { if(n == 0 || p == 0) return s; double b = s * pow(1.0 + p / 100.0, (double) n); return b; } Il est important que chaque alternative à l’intérieur de la fonction nous mène à une instruction return. double Valeur_Compte(double s, int n, double p) { if(n >= 0 && p >= 0) return s * pow(1.0 + p / 100.0, (double) n); } Le type du résultat peut être un type simple, un type pointeur, un type struct, un type union ou void. Une fonction ne peut pas fournir comme résultat un tableau, une chaîne de caractères ou une fonction mais un pointeur sur le 1e élément d’un tableau ou d’une chaîne de caractères.
Fonction prédicat Une fonction prédicat est une fonction qui retourne une valeur TRUE ou FALSE. #include <iostream.h> #include <cmath> // Détermine si un nombre entier positif est premier ou non. // Paramètre d'entrée : // n : un nombre entier positif. // Valeur de renvoi : // TRUE si n est un nombre premier, // FALSE autrement. bool Premier(int n) { for (int i = 2; i <= (int) sqrt(n); i++) if ((n%i) == 0) return false; return true; }
Fonction prédicat void main() { int entier; cout << "Entrez un nombre entier : "; cin >> entier; if (Premier(entier)) cout << entier << " est un nombre premier.\n"; else cout << entier << " n'est pas un nombre premier.\n"; }
Paramètres Lors de l’exécution d’une fonction, les paramètres de la fonction sont d’abord initialisés à partir des expressions fournies lors de l’appel. Il s’agit simplement d’une copie où la vérification de types est effectuée. #include <iostream.h> int min(int u, int v, int w) { if (u < v) v = u; if (v < w) w = v; return w; } void main() int A = 3, B = 2; cout << min(A, 2, A + B); Les paramètres u, v, w de la fonction peuvent être modifiés par la suite; cela n’a pas d’impact sur la fonction appelante. Mauvaise habitude : cela porte à confusion (voir plus loin). Expressions permettant d’initialiser les paramètres de la fonction lors de l’appel.
Noms significatifs pour les paramètres Choisir des noms explicites pour des paramètres possédant un rôle spécifique et des noms simples pour ceux restant génériques. Objectif : Le lecteur doit comprendre le but du paramètre sans avoir à lire sa description. Exemples : double sin(double radian) est préférable à double sin(double x). Cela précise que l’angle ne peut être fourni en degrés. En C++, double atan2(double y, double x) porte à confusion. Est-ce tan-1(x / y) ou tan-1(y / x) ? Écrivons plutôt : double atan2(double numerator, double denominator) bool approx_egal(double x, double y) // Des noms simples sont appropriés.
Valeurs par défaut pour les paramètres Exemple : Évalue un polynôme de degré 3 de la forme : a0 + a1 x + a2 x2 + a3 x3. Paramètres d'entrée : a0, a1, a2, a3 : les coefficients du polynôme, x : la valeur de la variable indépendante. Valeur de renvoi : la valeur du polynôme de degré 3. #include <iostream.h> float Polynome_de_degre_3 (float x, float a0, float a1 = 0, float a2 = 0, float a3 = 0) { return a0 + (a1 + (a2 + a3 * x) * x) * x; } valeurs par défaut
Valeurs par défaut pour les paramètres void main() { float x = 2.3f; cout << "La valeur du polynome pour x = " << x << " est : " << Polynome_de_degre_3(x, 7) << endl; << Polynome_de_degre_3(x, 7, 6) << endl; << Polynome_de_degre_3(x, 7, 6, 5) << endl; << Polynome_de_degre_3(x, 7, 6, 5, 4) << endl; cout << endl << endl; }
Déclaration de prototypes de fonctions Une fonction doit être connue avant d’être employée. Nous avons résolu le problème jusqu’à maintenant en définissant d’abord les fonctions élémentaires puis, les fonctions de plus haut niveau et, finalement, la fonction main. Les déclarations de fonctions classiques comme sqrt sont contenues dans les fichiers d’en-tête comme cmath. Il se peut qu’une fonction f appelle une fonction g et g fasse de même avec f. On doit alors déclarer au début le prototype d’une fonction pour pouvoir l’appeler avant qu’elle ne soit définie. type de la valeur de renvoi nom de la fonction (paramètre1, paramètre2, …, paramètren); Idem à la définition d’une fonction mais sans le corps de la fonction et avec le point-virgule. Exemple : int max(int u, int v); Cela indique que la fonction est définie ailleurs, soit plus loin dans le fichier actuel soit dans un autre fichier.
Déclaration de prototypes de fonctions Certains programmeurs préfèrent énumérer tous les prototypes de fonction en haut du fichier, puis écrire main et ajouter la définition des fonctions. Lors de la déclaration, le nombre et le type des paramètres doivent nécessairement correspondre à ceux de la définition de la fonction. Exemple I : #include <stdio.h> // Affiche un rectangle formé d'astérisques. // // Paramètres d’entrée : // Longueur : longueur du rectangle, // Hauteur : hauteur du rectangle. // Valeur de renvoi : // Nil. void Rectangle(int Longueur, int Hauteur);
Déclaration de prototypes de fonctions void main() { // Déclaration des variables de la fonction principale. int L, // Longueur du rectangle. H; // Hauteur du rectangle. // Saisie de la dimension du rectangle. printf("Entrez la longueur(L) et la hauteur(H) du rectangle : "); scanf("%d%d", &L,&H); // Affichage d'un rectangle L x H formé d'astérisques. Rectangle(L, H); } void Rectangle(int Longueur, int Hauteur) int i, j; for (i = 1; i <= Hauteur; i++) for (j=1; j<=Longueur; j++) printf("*"); printf("\n");
Déclaration de prototypes de fonctions Exemple II :
Effets secondaires à éviter Examinons la fonction suivante : double Valeur_Compte(double s, int n, double p) { if(n == 0 || p == 0) return s; double b = s * pow(1.0 + p / 100.0, (double) n); return b; } Pourquoi la fonction n’imprimerait-elle pas la valeur en même temps ? double Valeur_Compte(double s, int n, double p) { if(n == 0 || p == 0) return s; double b = s * pow(1.0 + p / 100.0, (double) n); cout << "Le solde est maintenant " << b << endl; return b; } Un principe à respecter : une fonction ne doit laisser aucune trace à part renvoyer une valeur.
Effets secondaires à éviter Une autre mauvaise pratique est d’imprimer des messages d’erreur à l’intérieur d’une fonction. double Valeur_Compte(double s, int n, double p) { if(n < 0 || p < 0) cout << "n ou p sont invalides."; return 0; } double b = s * pow(1.0 + p / 100.0, (double) n); return b;
Modes de transmission des paramètres Jusqu’à maintenant, nous avons opté pour un mode de transmission des paramètres par valeur qui consiste à copier la valeur des expressions lors de l’appel de la fonction dans chaque paramètre de la fonction. Avantage : La fonction appelée ne peut pas altérer le contenu des variables de la fonction appelante. Supposons maintenant que nous voulons écrire une fonction Lecture spécifiée comme suit : #include <stdio.h> // Lire un nombre entier positif et ranger cette valeur dans la variable // de la fonction appelante. // // Paramètre de sortie // *n : conservera la valeur entière positive lue. Pour y arriver, on doit transmettre dans la fonction appelante l’adresse de la variable entière qui conservera la valeur lue et la fonction appelée doit déclarer le paramètre comme pointeur.
Mode de transmission par adresse void Lecture(int * n) { int i = -1; while (i < 0) scanf("%i", &i); *n = i; // Range la valeur de i dans la variable pointée par n. } void main() int j = 1; Lecture(&j); // L’adresse de la variable j est transmise. printf("%i", j); // Le contenu de j est modifié à partir de la valeur lue. *n = -1; while (*n < 0) scanf("%i", n); ou encore main Lecture à l’appel j 1 n après la lecture j 24 n au retour de la fonction j 24
Mode de transmission par adresse Exemple III : Écrire une fonction PERMUTER qui échange le contenu de 2 variables de type int. Pour modifier le contenu de X et Y, PERMUTER a besoin des adresses de X et Y.
Mode de transmission par adresse
Exemple de fonctions en C Exemple IV : Tableau de valeurs d’une fonction. Soit F(x) = x3 – 2 x + 1, on désire construire un tableau de valeurs de cette fonction où N (le nombre de valeurs) ainsi que les valeurs de x sont entrés au clavier. #include <iostream.h> #include <iomanip.h> void Lire_Nombre_d_evaluations(int * n); // Lecture du nombre d'évaluations à effectuer. // // Paramètre de sortie : // *n : contiendra le nombre d'évaluations à réaliser.
Exemple de fonctions en C void Lire_Vecteur_reel(int n, float x[]); // Lecture des n composantes réelles d'un vecteur x. // // Paramètre d'entrée : // n : le nombre de composantes du vecteur x, // Paramètre de sortie : // x[] : contiendra les n composantes à évaluer. x désigne l’adresse de la 1ière composante du vecteur.
Exemple de fonctions en C void Calculer_Valeurs(int n, float x[], float F[]); // Calcul de la valeur de la fonction pour chaque composante // dans x. // // Paramètres d'entrée : // n : le nombre de composantes à évaluer, // x[] : les n composantes du vecteur à évaluer. // Paramètre de sortie : // F[] : contiendra la valeur de la fonction // pour chaque composante du vecteur à évaluer.
Exemple de fonctions en C void Afficher_Resultats(int n, float x[], float F[]); // Affichage de n valeurs réelles et de la valeur de la fonction pour chaque // valeur réelle. // // Paramètres d'entrée : // n : le nombre de composantes évaluées, // x[] : les n composantes évaluées, // F[] : contient la valeur de la fonction pour chaque // composante.
Exemple de fonctions en C void main() { float Valeurs_de_x[100]; // Valeurs de x. float Valeurs_de_F[100]; // Valeurs de F(x). int Nombre_d_evaluations; Lire_Nombre_d_evaluations(&Nombre_d_evaluations); Lire_Vecteur_reel(Nombre_d_evaluations, Valeurs_de_x); Calculer_Valeurs(Nombre_d_evaluations,Valeurs_de_x,Valeurs_de_F); Afficher_Resultats(Nombre_d_evaluations,Valeurs_de_x,Valeurs_de_F); }
Exemple de fonctions en C void Lire_Nombre_d_evaluations(int * n) { do cout << "Entrez un entier entre 1 et 100 : "; cin >> *n; } while (*n < 1 || *n > 100); void Lire_Vecteur_reel(int n, float x[]) cout << "Entrez " << n << " nombres reels :\n"; for (int i = 0; i < n; i++) cin >> x[i];
Exemple de fonctions en C void Calculer_Valeurs(int n, float x[], float F[]) { float Fonction(float x); // Retourne la valeur de la fonction évaluée à x. // // Paramètre d'entrée : // x : valeur réelle. // Valeur de renvoi : // Valeur de la fonction évaluée à x. for (int i = 0; i < n; i++) F[i] = Fonction(x[i]); } Note : Il est interdit de définir des fonctions à l’intérieur d’une autre fonction.
Exemple de fonctions en C float Fonction(float x) { return x * x * x - 2 * x + 1; } void Afficher_Resultats(int n, float x[], float F[]) cout << "\n X : "; for (int i = 0; i < n; i++) cout << setw(10) << x[i]; cout << "\n F(X) : "; for (i = 0; i < n; i++) cout << setw(10) << F[i]; cout << endl;
Modes de transmission par valeur ou par adresse Pour des raisons de sécurité, on ne choisit pas un mode de transmission par adresse si notre objectif consiste uniquement à transmettre la valeur d’une expression à une fonction. En optant pour un mode de transmission par adresse, l’expression lors de l’appel d’une fonction doit être obligatoirement l’adresse d’une variable. Ex. : Lecture(5); Lecture(u + v);
Passage de l’adresse d’un tableau à une dimension Comme il est impossible de passer le contenu de tout un tableau à une fonction, on fournit l’adresse d’un élément du tableau. En général, on fournit l’adresse du premier élément du tableau, qui est donnée par le nom du tableau. Déclaration : Dans la liste des paramètres d’une fonction, on peut déclarer un tableau par le nom suivi de crochets : type des composantes nom du tableau [] ou simplement par un pointeur sur le type des éléments du tableau : type des composantes * nom du tableau
Passage de l’adresse d’un tableau à une dimension Exemple :
Passage de l’adresse d’un tableau à une dimension Appel : L’adresse d’un tableau peut être donnée par le nom du tableau, par un pointeur ou par l’adresse d’un élément quelconque du tableau. Exemple :
Particularités avec un tableau qui n’est pas du type char Il faut fournir la dimension du tableau ou le nombre d’éléments à traiter comme paramètre, sinon la fonction risque de sortir du domaine du tableau. Exemple : Lecture de N données dans un tableau.
Passage de l’adresse d’un tableau à 2 dimensions Problème : Écrire une fonction qui calcule la somme de tous les éléments d’une matrice de réels dont nous fournissons les 2 dimensions N et M comme paramètres. Comment passer l’adresse de la matrice à la fonction ? 1e option : Déclarer le tableau dans l’en-tête de la fonction sous la forme A[][]. Le compilateur a besoin de la 2ième dimension de A pour déterminer l’adresse de A[i][j]. 2e option : La fonction recevra un pointeur de type float * au début de la matrice. Il s’agit de parcourir tous les éléments comme s’il s’agissait d’un tableau à une dimension N*M.
Passage de l’adresse d’un tableau à 2 dimensions Appel de la fonction : Il faut transmettre l’adresse du début du tableau. Prenons par exemple un tableau déclaré par : float A[3][4]; Le nom A correspond à la bonne adresse, mais cette adresse est du type pointeur sur un tableau de 4 éléments de type float. La conversion aura lieu automatiquement mais vous recevrez un message d’avertissement. On peut éviter ceci à l’aide d’une conversion explicite.
Passage de l’adresse d’un tableau à 2 dimensions L’idéal est de renvoyer explicitement l’adresse du 1e élément du tableau : SOMME(&T[0][0], 3, 4).
Notion de bloc et portée des identificateurs Les fonctions en C sont définies à l’aide de blocs d’instructions. Un bloc d’instructions est encadré d’accolades et composé de 2 parties : { déclarations locales instructions } Cela est vrai aussi pour les commandes if, while ou for. Exemple : #include <stdio.h> void main() { int i = 5; if (i == 5) int j; for (j = 1; j <= i; j++) printf("%i", j); }
Variables locales Les variables déclarées dans un bloc d’instructions sont uniquement visibles à l’intérieur de ce bloc. Ce sont des variables locales à ce bloc. Ex. : Aucune autre fonction n’a accès à la variable NOM : La déclaration de la variable I n’est pas visible à l’extérieur du bloc d’instructions conditionnel, ni dans la fonction qui l’entoure. Ex. :
Variables locales Une variable déclarée à l’intérieur d’un bloc cache toutes les variables de même nom des blocs qui l’entourent. Les paramètres d’une fonction sont des variables locales initialisées par les valeurs obtenues lors de l’appel. Réfère à la variable de type int. Réfère à la variable de type double. La variable X de type int n’est pas accessible. Note : Éviter de cacher des variables; cela peut mener à des malentendus. Effectuer toutes nos déclarations locales au début des fonctions.
Passage des paramètres par valeur : exemple La fonction ETOILES dessine une ligne de N étoiles. Le paramètre N est modifié à l’intérieur de la fonction. La fonction TRIANGLE, appelle la fonction ETOILES en utilisant la variable L comme paramètre : Au moment de l’appel, la valeur de L est copiée dans N. N peut donc être décrémentée à l’intérieur de ETOILES sans influencer la valeur originale de L.
Passage des paramètres par valeur : exemple
Variables globales Les variables déclarées au début du fichier, à l’extérieur de toutes les fonctions sont disponibles partout. Ce sont des variables globales. Exemple : La variable STATUS est déclarée globalement pour pouvoir être utilisée dans les procédures A et B.
Usage des variables globales Écrire nos programmes aussi localement que possible. Les fonctions ont accès à des données qui ne leur appartiennent pas. Les données ne sont pas protégées. Un programme n’est plus constitué de composantes indépendantes; Ces variables globales créent des liens entre les fonctions. Lors de la définition d’une fonction, il est nécessaire de connaître le traitement accordé aux variables globales dans les autres fonctions. Solution : Usage de classes.
Règles pour la déclaration de fonctions (prototype) De façon analogue aux déclarations de variables, nous pouvons déclarer une fonction localement ou globalement. Déclaration locale La fonction est déclarée localement dans la fonction qui l’appelle avant la déclaration des variables. Elle est alors disponible à cette fonction. Déclaration globale La fonction est déclarée globalement au début du programme après les instructions #include. Elle est alors disponible à toutes les fonctions du programme. Déclaration implicite par la définition La fonction est disponible à toutes les fonctions qui suivent sa définition. main La fonction principale main n’a pas besoin d’être déclarée.
Règles pour la déclaration de fonctions : exemple Considérons la hiérarchie suivante : main FA FB Il y a plusieurs possibilités pour déclarer et définir ces 3 fonctions. 1e cas : Déclarations locales des fonctions et définition top-down. Avantage : Reflète la hiérarchie des fonctions. Facile pour un usager qui ne s’intéresse qu’à la solution globale du problème.
Règles pour la déclaration de fonctions : exemple 2ième cas : Définition bottom-up sans déclarations. Elle débute en bas de la hiérarchie et termine avec la fonction main. Les fonctions qui traitent des détails sont définies en premier lieu. Inconvénient : Il est plus difficile de retrouver les dépendances entre les fonctions.
Règles pour la déclaration de fonctions : exemple 3ième cas : Déclaration globale des fonctions et définition top-down. Approche la plus simple car nous ne sommes pas forcés de nous occuper de la dépendance entre les fonctions.
Pointeurs de fonctions Un pointeur vers une fonction contient l’adresse de la fonction en mémoire i.e. l’adresse mémoire du début du code de la fonction. Les pointeurs vers des fonctions peuvent être passés aux fonctions ou renvoyés, être stockés dans des tableaux et être affectés à d’autres pointeurs de fonction. Exemple : Tri d’un tableau d’entiers en ordre croissant ou décroissant. Tiré de Deitel & Deitel, Comment Programmer en C++. Prentice-Hall, 2000. #include <iostream.h> #include <iomanip.h> // Vérifie si le 1e paramètre est plus grand que le 2e. // // Paramètres d'entrée : // a, b : 2 valeurs entières, // Valeur de renvoi : // TRUE : si a > b, FALSE : si a <= b. bool ascendant(int a, int b) { return a > b; }
Pointeurs de fonctions // Vérifie si le 1e paramètre est plus petit que le 2e. // // Paramètres d'entrée : // a, b : 2 valeurs entières, // Valeur de renvoi : // TRUE : si a < b, // FALSE : si a >= b. bool descendant(int a, int b) { return a < b; }
Pointeurs de fonctions // Permutation du contenu de 2 variables entières. // // Paramètres d'entrée : // int * pA, int * pB : contient l'adresse des 2 variables entières // à permuter. // Paramètres de sortie : // dont les valeurs ont été permutées. void permutation(int * pA, int * pB) { int temporaire = * pA; * pA = * pB; * pB = temporaire; }
Pointeurs de fonctions // Tri des composantes d'un tableau d'entiers dans un ordre particulier. // // Paramètres d'entrée : // int tableau[] : renferme un tableau d'entiers à ordonner, // int dimension : renferme le nombre de composantes du tableau, // bool (*comparer)(int a, int b) : // pointeur vers une fonction booléenne qui retourne // TRUE si les paramètres a et b doivent être permutés. // Paramètres de sortie : // int tableau[] : renferme le tableau d'entiers après ordonnancement. void bulle(int tableau[], int dimension, bool (*comparer)(int a, int b)) { void permutation(int * pA, int * pB); for (int i = 1; i < dimension; i++) for (int j = 0; j < dimension - 1; j++) if ((*comparer)(tableau[j], tableau[j+1])) permutation(&tableau[j], &tableau[j+1]); }
Pointeurs de fonctions void main() { const int dimensionTableau = 10; int Tableau_a_trier[dimensionTableau] = {2, 6, 4, 8, 10, 12, 89, 68, 45, 37}; int choix; cout << "Entrez 1 pour un tri en ordre croissant,\n" << "entrez 2 pour un tri en ordre decroissant : "; cin >> choix; // Affichage des données dans l’ordre initial. cout << "\n\nAffichage des donnees dans l'ordre initial : \n"; for (int i = 0; i < dimensionTableau; i++) cout << setw(4) << Tableau_a_trier[i]; cout << endl << endl;
Pointeurs de fonctions // Tri des éléments du tableau en ordre croissant ou décroissant. if (choix == 1) { bulle(Tableau_a_trier, dimensionTableau, ascendant); cout << "Affichage des donnees en ordre croissant : \n"; } else bulle(Tableau_a_trier, dimensionTableau, descendant); cout << "Affichage des donnees en ordre decroissant : \n"; // Affichage des données une fois triée. for (i = 0; i < dimensionTableau; i++) cout << setw(4) << Tableau_a_trier[i]; cout << endl << endl;
Pointeurs de fonctions Un pointeur vers une fonction est aussi utilisé dans les systèmes pilotés par menus où l’utilisateur doit sélectionner une option parmi les choix proposés qui correspondent chacun à une fonction différente. Les pointeurs vers chaque fonction sont stockés dans un tableau de pointeurs vers des fonctions. Le choix de l’utilisateur sert d’indice au tableau, tandis que le pointeur appelle la fonction. Exemple : #include <iostream.h> // Calcule la somme de 2 entiers. // // Paramètres d'entrée : // a, b : 2 valeurs entières, // Valeur de renvoi : a + b // la somme de a et de b. int somme(int a, int b) { return a + b; }
Pointeurs de fonctions // Calcule la différence de 2 entiers. // // Paramètres d'entrée : // a, b : 2 valeurs entières, // Valeur de renvoi : a - b // la différence de a et de b. int difference(int a, int b) { return a - b; } // Calcule le produit de 2 entiers. // Valeur de renvoi : a * b // le produit de a et de b. int produit(int a, int b) { return a * b; }
Pointeurs de fonctions // Calcule le quotient de 2 entiers. // // Paramètres d'entrée : // a, b : 2 valeurs entières, // Valeur de renvoi : a / b // le quotient de a avec b. int quotient(int a, int b) { return a / b; }
Pointeurs de fonctions void main() { int (*f[4])(int a, int b) = { somme, difference, produit, quotient}; int u = 5, v = 2; int choix; cout << "\nEntrez un nombre entre 0 et 3, ou 4 pour terminer: "; cin >> choix; while (choix >= 0 && choix <= 3) cout << (*f[choix])(u, v); }
Structures et fonctions une structure comme argument d’une fonction: Rien d’inhabituel. #include <iostream.h> struct etudiant { int matricule; int age; char nom[20]; char prenom[20]; }; void main() bool meme_age(struct etudiant E1, struct etudiant E2); struct etudiant P = {99876123, 23, "Dugre", "Luc"}; struct etudiant Q = {98123987, 20, "Dupre", "Leon"}; if (meme_age(P, Q)) cout << "Ils sont de meme age.\n"; else cout << "Ils sont d'ages differents.\n"; } bool meme_age(struct etudiant E1, struct etudiant E2) return E1.age == E2.age;
Structures et fonctions un pointeur à une structure comme argument d’une fonction : Si la structure possède un grand nombre de champs, il est préférable d’opter pour un pointeur vers la structure plutôt que la structure elle-même. Motifs : Économie mémoire et réduction du temps d’exécution. Des pointeurs à des structures constantes nous empêchant de modifier le contenu des structures. #include <iostream.h> struct etudiant { int matricule; int age; char nom[20]; char prenom[20]; }; void main() bool meme_age(struct etudiant const * pE1, struct etudiant const * pE2); struct etudiant P = {99876123, 23, "Dugre", "Luc"}; struct etudiant Q = {98123987, 20, "Dupre", "Leon"}; if (meme_age(&P, &Q)) cout << "Ils sont de meme age.\n"; else cout << "Ils sont d'ages differents.\n"; } bool meme_age(struct etudiant const * pE1, struct etudiant const * pE2) { return (* pE1).age == (* pE2).age; }
Structures et fonctions un pointeur à une structure comme argument d’une fonction (suite) : On ajoute const pour empêcher que l’on modifie les données dans la fonction. La fonction meme_age nécessite un accès en lecture seulement des paramètres. Réécrivons la fonction précédente : bool meme_age(struct etudiant * const pE1, struct etudiant * const pE2) { return (* pE1).age == (* pE2).age; } Des pointeurs constants à des structures qui nous empêchent de modifier le contenu des pointeurs mais non celui des structures.
Structures et fonctions une structure comme valeur de renvoi d’une fonction : #include <iostream.h> struct etudiant { int matricule; int age; char nom[20]; char prenom[20]; }; void main() struct etudiant plus_jeune(struct etudiant E1, struct etudiant E2); struct etudiant P = {99876123, 23, "Dugre", "Luc"}; struct etudiant Q = {98123987, 20, "Dupre", "Leon"}; cout << "Le plus jeune a comme age " << plus_jeune(P, Q).age << " ans.\n"; } struct etudiant plus_jeune(struct etudiant E1, struct etudiant E2) if (E1.age < E2.age) return E1; else return E2; Note : La valeur de renvoi pourrait être un pointeur vers une structure.