programmation en C "Chapitre 5 : Fonctions"

Chapitre 5 : Fonctions.
4.1 Premières fonctions
4.2 Utilisation de paramètres
4.3 Valeur de retour
4.4 Utilisation de Bibliothèques de fonctions
4.5 Erreurs
4.6 Propreté du code
4.7 Exercices
4.1 Premières fonctions
4.1.1 Eviter les répétitions
Examinons le programme suivant : #include int main(){ int nb_colonnes = 0; double nombre_lu; while (nb_colonnes < 40) { printf("*"); nb_colonnes++; } printf("\n"); printf("Entrez un nombre décimal : "); scanf("%lf", &nombre_lu); printf("\nLe carré de ce nombre est %lf\n", nombre_lu*nombre_lu); nb_colonnes = 0; while (nb_colonnes < 40) { printf("*"); nb_colonnes++; } printf("\n"); return 0; }
Voici un exemple de résultat d'exécution de ce programme : ****************************************Entrez un nombre décimal : 42.234 Le carré de ce nombre vaut 1783.710756****************************************

............


Ce programme, en plus de calculer le carré d'un nombre entré par l'utilisateur, affiche une ligne d'étoiles au début et à la fin de son exécution. Ces lignes sont affichées par deux boucles identiques, au début et à la fin du code source.
Répéter plusieurs fois les mêmes instructions au sein d'un même programme est une chose qu'il faut éviter au maximum. D'une part cela peut devenir très fastidieux : si l'on souhaite afficher une telle ligne d'étoiles à une centaine d'endroits différents du programme, réécrire la même boucle est une perte de temps considérable. D'autre part, cela rend le programme plus difficile à lire et à maintenir.
Nous avons déjà présenté un outil permettant d'éviter de réécrire de nombreuses fois la même chose : la boucle while, qui offre d'ailleurs des applications bien plus évoluées qu'un simple évitement de répétitions. Cependant, une boucle while n'est d'aucune utilité dans le cas de notre exemple. Les deux occurences des instructions que l'on répète sont en effet séparées par d'autres instructions, qui ne doivent pas, elles, être répétées.
Il y aurait moyen, en cherchant un peu, d'utiliser une boucle while pour éviter la répétition dans cet exemple bien précis. Ceci n'est cependant possible que parce que l'exemple est très simple et le compliquerait, plutôt que de le simplifier. Nous allons voir dans ce chapitre un outil beaucoup plus adapté à ce cas et qui nous sera très rapidement indispensable : la fonction.
Une fonction permet d'associer un identifiant à un bloc d'instructions, pour éviter de réécrire celui-ci plusieurs fois dans le programme. On utilise ensuite l'identifiant à chaque endroit où l'on souhaite exécuter les instructions de ce bloc. Vous avez déjà eu l'occasion d'utiliser des fonctions : scanf et printf en sont. Ces deux fonctions sont cependant assez complexes et nous allons commencer par des exemples beaucoup plus simples.
4.1.2 Déclaration d'une fonction simple
Reprenons notre exemple, pour définir exactement quel bloc d'instructions nous souhaitons éviter de répéter grâce à l'utilisation d'une fonction. Nous pouvons constater qu'il y a une légère différence entre les deux versions au niveau de la déclaration et de l'initialisation de la variable nb_colonnes. Après de légères modifications, on peut cependant mettre en évidence deux blocs parfaitement identiques : #include int main(){ double nombre_lu; { int nb_colonnes = 0; while (nb_colonnes < 40) { printf("*"); nb_colonnes++; } printf("\n"); } printf("Entrez un nombre décimal : "); scanf("%lf", &nombre_lu); printf("\nLe carré de ce nombre est %lf\n", nombre_lu*nombre_lu); { int nb_colonnes = 0; while (nb_colonnes < 40) { printf("*"); nb_colonnes++; } printf("\n"); } return 0; }
Maintenant que l'on a mis en évidence le bloc que l'on veut éviter de répéter, nous allons pouvoir le transformer en fonction. Pour cela, nous allons l'isoler du reste du programme et lui associer un identifiant. Comme pour les variables, il faut choisir un nom significatif, comme par exemple "ligne_etoiles". Voici comment on procède pour associer cet identifiant à notre bloc, puis l'utiliser au sein de notre programme : #include void ligne_etoiles(){ int nb_colonnes = 0; while (nb_colonnes < 40) { printf("*"); nb_colonnes++; } printf("\n");} int main(){ double nombre_lu; ligne_etoiles(); printf("Entrez un nombre décimal : "); scanf("%lf", &nombre_lu); printf("\nLe carré de ce nombre est %lf\n", nombre_lu*nombre_lu); ligne_etoiles(); return 0; }
Nous avons repris précisément le contenu du bloc répété et l'avons placé au dessus du début du programme, juste avant "int main()". Pour lui associer l'identifiant, la ligne suivante a été placée juste au dessus : void ligne_etoiles()
Nous retiendrons pour l'instant la règle suivante :
Pour associer un identifiant à un bloc d'instructions et créer ainsi une fonction, on place ce bloc avant le début du programme et on le fait précéder d'une ligne contenant le mot clé void, l'identifiant choisi, puis une paire de parenthèses ouvrante et fermante.
Nous verrons un peu plus tard quel est le rôle du mot clé void et celui des parenthèses qui suivent l'identifiant de la fonction.
Une fois la fonction créée, on peut l'utiliser au sein du programme en y plaçant ce que l'on nomme un appel de fonction : on écrit une instruction composée de l'identifiant de la fonction, suivi d'une paire de parenthèses. Lors de l'exécution du programme, les instructions du bloc défini par la fonction sont exécutées à chaque fois qu'un appel à cette fonction est rencontré.
Les noms que l'on peut utiliser comme identifiants de fonctions sont les mêmes que ceux possibles pour les identifiants de variables. Comme pour le choix des noms de variables, il est important de bien choisir les noms des fonctions, pour qu'ils expriment très clairement ce que fait la fonction.
On pourrait dire qu'appeler une fonction revient à remplacer dans le programme, cet appel par le bloc d'instructions correspondant à l'identifiant de la fonction. Il y a cependant une différence : lorsque les instructions d'une fonction sont exécutées, les variables déclarées dans le bloc où est effectué l'appel ne sont pas accessibles : seules les variables déclarées dans le bloc de la fonction sont accessibles dans ce bloc.
Exercice : écrivez une fonction qui affiche "Entrez le code :" puis lit un code secret numérique (un entier) tapé par l'utilisateur et recommence jusqu'à-ce que l'utilisateur tape le bon code : 4242.
Ecrivez ensuite un programme qui utilise cette fonction pour vérifier que l'utilisateur connaît le code secret, puis qui affiche le texte "Encore une fois." et demande de nouveau le code secret grâce à cette fonction. Finalement, votre programme doit afficher "Bravo.".
Vous pouvez soumettre votre solution pour cet exercice, puis lire la correction dans l'épreuve "Cours de programmation C : Chapitre 4" sur www.france-ioi.org
4.2 Utilisation de paramètres
4.2.1 Fonctions à un paramètre
Nous venons de voir comment les fonctions permettent d'éviter de répéter exactement les mêmes instructions à plusieurs endroits d'un programme. Cependant il arrive souvent que l'on répète les mêmes instructions, mais avec de légères différences d'une version à l'autre. Supposons par exemple que nous souhaitions afficher une ligne d'étoiles au début et une ligne de tirets un peu plus loin. Les instructions utilisées dans les deux cas sont identiques à un caractère près.
Il est possible d'écrire des fonctions dont l'exécution dépend de certaines valeurs. Ces valeurs sont appelées des paramètres de la fonction. On pourra par exemple écrire une fonction ayant pour paramètre le caractère à utiliser pour afficher une ligne.
Un paramètre prend la forme d'une variable. Pour écrire notre fonction, nous devons commencer par exprimer son contenu en le faisant dépendre de la valeur d'une variable qui contient le caractère à afficher : void ligne_etoiles(){ char caractere_affiche = '*'; int nb_colonnes = 0; while (nb_colonnes < 40) { printf("%c", caractere_affiche); nb_colonnes++; } printf("\n");}
On ne veut cependant pas que cette variable soit initialisée dans le corps de la fonction (elle aurait toujours la même valeur). C'est au moment de l'appel de cette fonction que l'on doit pouvoir choisir le caractère : '*' lors d'un appel, puis '-' lors d'un autre. Pour transmettre une valeur à la fonction, on procède comme on le fait lors des appels à printf : on place la valeur entre parenthèses, lors de l'appel : int main(){ double nombre_lu; ligne_caracteres('*'); printf("Entrez un nombre décimal : "); scanf("%lf", &nombre_lu); printf("\nLe carré de ce nombre est %lf\n", nombre_lu*nombre_lu); ligne_caracteres('-'); return 0; }
Pour pouvoir appeler la fonction de cette manière, avec un paramètre, puis utiliser sa valeur dans ses instructions, il est nécessaire de modifier la déclaration de la fonction en indiquant que la fonction prend un paramètre, que ce paramètre est de type char et qu'on utilisera l'identifiant caractere_affiche pour le manipuler dans le corps de la fonction. Voici la nouvelle déclaration : void ligne_caracteres(char caractere_affiche){ int nb_colonnes = 0; while (nb_colonnes < 40) { printf("%c", caractere_affiche); nb_colonnes++; } printf("\n");}
Au sein de cette fonction, l'identifiant caractere_affiche est alors considéré comme une variable, qui a été initialisée à la valeur indiquée lors de l'appel. On peut l'utiliser comme une variable donc également modifier sa valeur au cours de la fonction. Modifier la valeur d'un paramètre d'une fonction n'est cependant pas conseillé, car cela rend la fonction plus difficile à comprendre.
Dans le cas où l'on modifierait sa valeur, il est important de noter que cette modification n'a d'effet qu'à l'intérieur de la fonction. Si le paramètre a été passé sous la forme d'une variable au moment de l'appel, celle-ci ne sera pas modifiée, comme le montre l'exemple suivant : #include void affiche_et_modifie(int parametre){ printf("Valeur du paramètre au début de la fonction : %d\n", parametre); parametre++; printf("Valeur du paramètre à la fin de la fonction : %d\n", parametre);} int main(){ int valeur = 42; printf("Valeur avant l'appel de la fonction : %d\n", valeur); affiche_et_modifie(valeur); printf("Valeur après l'appel de la fonction : %d\n", valeur); return 0;}
Le programme affiche la sortie suivante, qui montre que la modification dans la fonction n'affecte pas la valeur de la variable utilisée pour l'appel : Valeur avant l'appel de la fonction : 42Valeur du paramètre au début de la fonction : 42Valeur du paramètre à la fin de la fonction : 43Valeur après l'appel de la fonction : 42
On gardera en tête que lorsque l'on passe une valeur en paramètre à une fonction, cette valeur est copiée dans une variable de la fonction.
Exercice : Reprenez l'exercice précédent, mais en écrivant cette fois une fonction qui prend en paramètre le code secret numérique à trouver et qui lit des entiers au clavier, tant que l'utilisateur n'a pas entré ce code. Appelez la fonction une première fois avec le code 4242, puis une deuxième fois avec 2121.
Votre fonction doit afficher le texte "Entrez le code :" à chaque fois que l'utilisateur doit faire un essai. Lorsque le premier code est trouvé, votre programme doit afficher "Premier code bon.". Lorsque le deuxième code est trouvé, votre programme doit afficher "Bravo.".
Vous pouvez soumettre votre solution pour cet exercice, puis lire la correction dans l'épreuve "Cours de programmation C : Chapitre 4" sur www.france-ioi.org
4.2.2 Fonctions à plusieurs paramètres
Il est aussi possible de déclarer des fonctions qui prennent plusieurs paramètres. Dans l'exemple de la fonction qui affiche plusieurs fois une ligne de caractères, on peut imaginer que le nombre de fois où le caractère doit être affiché n'est pas toujours identique et doit être passé en paramètre lors de l'appel. Pour cela, il suffit de séparer les paramètres par des virgules lors de l'appel : ligne_caracteres('*', 40);ou ligne_caracteres('-', 35);
Pour pouvoir faire un tel appel, il faut cependant avoir défini dans la déclaration de la fonction que celle-ci prend un deuxième paramètre, cette fois un entier, puis modifier les instructions pour qu'elles utilisent la variable correspondante. Comme pour l'appel, on ajoute simplement un paramètre dans la déclaration en le séparant du premier par une virgule. On nommera par exemple ce paramètre nb_caracteres : void ligne_caracteres(char caractere_affiche, int nb_caracteres){ int nb_colonnes = 0; while (nb_colonnes < nb_caracteres) { printf("%c", caractere_affiche); nb_colonnes++; } printf("\n");}
Remarque : si l'on a défini que la fonction prenait deux paramètres, on ne peut plus l'appeler en n'en fournissant qu'un seul.
On peut définir les règles suivantes :
Déclaration de fonction :
Pour permettre à une fonction de dépendre de paramètres, on place après l'identifiant de la fonction, entre les parenthèses, la description des différents paramètres, séparés par des virgules. Chaque paramètre est défini par son type, puis le nom de la variable qui contiendra sa valeur dans le corps de la fonction.
Appel de fonction :
Pour transmettre des valeurs en paramètre lors de l'appel d'une fonction, on place ces valeurs entre les parenthèses qui suivent l'identifiant de la fonction, séparés par des virgules. Les valeurs doivent être dans le même ordre que celui défini lors de la déclaration de la fonction et doivent être du type correspondant.
Exercice : écrivez une fonction qui prend en paramètre un caractère et deux entiers, et qui affiche un rectangle rempli du caractère fourni, dont le nombre de lignes et de colonnes sont les entiers fournis. Appelez ensuite cette fonction pour afficher un rectangle de 4 lignes de 10 caractères 'X', puis un rectangle de 6 lignes de 5 caractères 'O'.
Vous pouvez soumettre votre solution pour cet exercice, puis lire la correction dans l'épreuve "Cours de programmation C : Chapitre 4" sur www.france-ioi.org
4.3 Valeur de retour
Nous avons vu comment utiliser les paramètres pour éviter de recopier plusieurs fois un groupe d'instructions faisant quelque-chose d'identique à quelques détails près. Les fonctions que nous avons écrites effectuaient des opérations, mais n'avaient cependant aucune influence sur la suite de l'exécution du programme. Supposons par exemple que nous ayons souvent besoin de calculer la valeur absolue d'un nombre, comme dans le programme suivant : #include int main(){ int nombre_lu1, nombre_lu2; int valeur_absolue1, valeur_absolue2; printf("Entrez deux nombres : "); scanf("%d%d", &nombre_lu1, &nombre_lu2); if (nombre_lu1 >= 0) valeur_absolue1 = nombre_lu1; else valeur_absolue1 = -nombre_lu1; if (nombre_lu2 >= 0) valeur_absolue2 = nombre_lu2; else valeur_absolue2 = -nombre_lu2; printf("Les valeurs absolues de ces nombres sont %d et %d.\n", valeur_absolue1, valeur_absolue2); return 0;}
Nous pouvons écrire une fonction qui prend en paramètre un nombre et calcule sa valeur absolue. Il nous faut cependant un moyen pour retransmettre cette valeur au programme. Au moment de l'appel, l'idée est de considérer dans le programme, qu'une fonction appelée a une valeur que l'on peut utiliser ensuite. Cette valeur, calculée par les instructions de la fonction, peut alors être affectée à une variable. On pourrait ainsi écrire notre programme en utilisant la fonction valeur_absolue : #include int main(){ int nombre_lu1, nombre_lu2; int valeur_absolue1, valeur_absolue2; printf("Entrez deux nombres : "); scanf("%d%d", &nombre_lu1, &nombre_lu2); valeur_absolue1 = valeur_absolue(nombre_lu1); valeur_absolue2 = valeur_absolue(nombre_lu2); printf("Leurs valeurs absolues sont %d et %d.\n", valeur_absolue1, valeur_absolue2); return 0;}
Notre fonction valeur_absolue prend en paramètre une valeur et retourne sa valeur absolue. Voyons maintenant comment écrire une telle fonction.
Pour commencer, il faut définir le type de la valeur que retournera la fonction. Dans notre cas, la fonction valeur_absolue va prendre en paramètre un int et retournera donc aussi un int. Le type de la valeur de retour doit être placé devant l'identifiant de la fonction dans sa déclaration, à la place du mot clé void, ce dernier signifiant en fait "aucune valeur de retour". La première ligne de la déclaration devient donc : int valeur_absolue(int valeur)
Pour que la fonction puisse effectivement retourner une valeur, il faut qu'elle contienne une instruction composée du mot clé return, puis de la valeur que l'on veut retourner. L'instruction return quitte la fonction et transmet la valeur qui suit au programme appelant : int valeur_absolue(int valeur){ int val_absolue; if (valeur >= 0) val_absolue = valeur; else val_absolue = -valeur; return val_absolue;}
Pour résumer :
La déclaration d'une fonction prend la forme suivante : ( , , <...>){ return ;}
On peut remarquer que la notion de fonction en C est comparable à la notion de fonction mathématique. On pourrait se représenter la valeur de retour comme une fonction mathématique des paramètres de la fonction. La notation elle-même est assez proche, puisque valeur_absolue(x) aura pour valeur la valeur absolue du contenu de la variable x. Il y a cependant des différences importantes, entre autres du fait qu'une fonction C peut faire bien plus que calculer une valeur et peut dépendre d'autres choses que de la valeur des paramètres, comme par exemple les actions de l'utilisateur.
Il est possible de mettre plusieurs instructions return à l'intérieur d'une fonction. Chacune d'entre-elles doit cependant être suivie d'une valeur dont le type correspond au type indiqué devant la déclaration de la fonction. Il est également important que quoi qu'il arrive dans la fonction, la dernière instruction exécutée soit toujours une instruction return. Notre fonction peut ainsi s'écrire plus simplement : int valeur_absolue(int valeur){ if (valeur >= 0) return valeur; else return -valeur;}
Dans le cas des fonctions dont le type de retour est void, il est également possible d'utiliser l'instruction return. Elle n'est alors suivie d'aucune valeur et a simplement pour effet de quitter la fonction immédiatement, lorsqu'elle est exécutée.
Vous l'avez sans doute déjà compris, tous les programmes que nous avons écrits jusqu'à présent contenaient déjà la déclaration d'une fonction : int main(){ return 0;}
Il s'agit de la déclaration d'une fonction nommée main, qui retourne un entier, en l'occurence 0. Le lancement d'un programme se traduit par l'appel automatique à cette fonction main(), dont les instructions sont les premières exécutées. Toutes les instructions d'un programme C se trouvent en fait à l'intérieur de fonctions, que ce soit la fonction main ou vos propres fonctions.
La valeur entière retournée par la fonction main correspond à un code d'erreur, où 0 signifie que le programme s'est terminé sans erreur. Prenez donc l'habitude de toujours faire retourner la valeur 0 à votre fonction main(), pour indiquer que votre programme a pu faire correctement son travail.
Exercice : Ecrivez une fonction nommée min qui prend en paramètre deux entiers et qui renvoie le plus petit des deux. Utilisez cette fonction dans un programme qui lit 10 entiers tapés au clavier, puis affiche le plus petit de ces dix entiers.
Solution : on lit le premier des nombres de l'utilisateur, pour le stocker dans une variable qui va contenir la plus petite valeur rencontrée jusqu'à présent. A chaque nouvelle valeur, on conserve la plus petite parmi la valeur stockée et la valeur lue, en appelant la fonction min. #include int min(int valeur1, int valeur2){ if (valeur1 < valeur2) return valeur1; else return valeur2;} int main(){ int valeur_min; int nb_valeurs_lues = 1; scanf("%d", &valeur_min); while (nb_valeurs_lues < 10) { int valeur_lue; scanf("%d", &valeur_lue); valeur_min = min(valeur_min, valeur_lue); nb_valeurs_lues++; } printf("%d\n", valeur_min); return 0;}
4.4 Utilisation de Bibliothèques de fonctions
4.4.1 Prototypes de fonctions
Nous avons vu que notre programme est contenu dans une fonction main, qui peut elle-même appeler d'autres fonctions. En fait, toute fonction peut appeler d'autres fonctions et celles-ci peuvent elle-mêmes en appeler. On appelle ceci des appels imbriqués : un appel de fonction se fait à l'intérieur d'un autre appel, qui lui-même se fait à l'intérieur d'un autre appel, etc. Le C ne définit pas de limite sur le nombre d'imbrications d'appels de fonctions.
Pour pouvoir appeler une fonction, il est nécessaire que cette fonction soit déclarée ailleurs dans le programme, plus précisément avant l'appel. En effet, un compilateur C lit le source de votre programme dans l'ordre du texte et a besoin d'avoir rencontré la déclaration d'une fonction pour pouvoir traiter son appel. Toujours écrire le contenu des fonctions avant de les appeler peut cependant devenir assez difficile lorsque l'on écrit des programmes qui en contiennent beaucoup. Dans certains cas que nous étudierons par la suite, cela peut même être impossible.
Pour s'assurer que le compilateur connaît les fonctions que l'on veut utiliser, au moment de leur appel, la déclaration des fonctions peut être séparée en deux parties : d'une part le prototype de la fonction, d'autre part son implémentation.
Le prototype d'une fonction est la partie qui définit comment on l'utilise : le nom la fonction, les paramètres qu'elle prend et le type de valeur qu'elle retourne. Il s'agit en fait de la première ligne de notre déclaration. Il est possible de le définir indépendamment du reste de la déclaration, en ajoutant simplement un point virgule après la ligne : int abs(int valeur);
Une fois le prototype d'une fonction défini et lu par le compilateur, il devient possible d'appeler cette fonction dans le reste du programme. Le reste de la déclaration, l'implémentation, peut être donné bien plus tard. L'implémentation répète le contenu du prototype, suivi cette fois du bloc d'instructions correspondant : int abs(int valeur){ if (valeur >= 0) return valeur; else return -valeur;}
Exercice : écrivez le prototype d'une fonction qui calculerait la valeur absolue d'un nombre décimal.
Solution : dans un programme C, on ne peut définir deux fonctions qui portent le même nom, mais ont des prototypes différents. On va donc choisir un nom différent pour notre nouvelle fonction, par exemple abs_double. double abs_double(double valeur);
4.4.2 Principe des fichiers d'en-tête (fichiers .h)
En plus de permettre d'appeler une fonction dans un source avant la définition de ses instructions, les prototypes permettent de compiler des programmes qui utilisent des fonctions, sans avoir à les recompiler.
Il est possible en C de séparer son programme en plusieurs fichiers .c et de les compiler indépendamment les uns des autres, pour les réunir ensuite en un même programme. Si vous écrivez un gros projet, cela permet de le séparer en plusieurs parties et de travailler indépendamment sur chaque partie. Le même fichier source .c peut ensuite être utilisé dans plusieurs programmes, qui appellent les fonctions qui y sont définies.
Pour éviter au compilateur de relire un fichier .c à chaque fois qu'un autre fichier fait appel à une fonction qu'il contient, on utilise des fichers supplémentaires. Ces fichiers, nommés fichiers d'en-tête, contiennent les prototypes de fonctions se trouvant dans d'autres fichiers sources. On leur donne en général l'extension ".h" (h pour header, qui signifie en-tête en anglais).
Supposons par exemple que nous voulions utiliser notre fonction de calcul de valeur absolue dans plusieurs projets. On peut la mettre dans son propre fichier, par exemple valeur_absolue.c. Si l'on veut alors l'utiliser dans un projet, il est nécessaire de créer un fichier .h correspondant, qui contienne son prototype. On créera donc le fichier valeur_absolue.h, avec le contenu suivant : int abs(int valeur);
Pour utiliser cette fonction dans un autre programme, il est alors nécessaire d'inclure le contenu de ce fichier d'en-tête dans le source .c du programme, pour que le compilateur connaisse le prototype de la fonction avant son appel. Pour cela, on doit mettre au début du programme : #include "valeur_absolue.h"
On écrit #include, suivi entre guillemets, du nom du fichier où se trouvent les prototypes des fonctions que vous voulez utiliser. Lorsqu'il rencontre cette directive, le compilateur lit le contenu du fichier, avant de passer à la suite.
Si vous voulez réellement créer vos propres fichiers de fonctions et vos propres fichiers d'en-tête, il y a un certain nombre d'autres choses à savoir pour le faire correctement, mais nous ne les verrons pas ici. Le but de cette section est surtout de vous permettre de comprendre comment utiliser les fonctions fournies avec le langage. Vous l'aviez déjà fait, puisque vous incluez dans tous vos programme la ligne suivante : #include
Cette ligne permet d'inclure le fichier "stdio.h" au début de votre programme. Ce fichier contient les prototypes de nombreuses fonctions concernant les entrées/sorties. En particulier, il contient ceux des fonctions printf et scanf, que vous avez déjà utilisées. Le nom du fichier d'en-tête est cette fois placé entre les signes <>, pour signifier qu'il ne s'agit pas d'un fichier de votre propre projet, mais d'un fichier qui est ici livré avec le compilateur.
4.4.3 Bibliothèque standard du C
Votre compilateur C est fourni avec de nombreuses fonctions déjà programmées, que vous pouvez utiliser dans vos programmes. Des ensembles de fonctions destinées à être utilisées dans des programmes sont en général appelées bibliothèques de fonctions. La bibliothèque de fonctions fournie avec votre compilateur est ainsi appelée la bibliothèque standard. Elle contient de nombreuses fonctions, dont nous ne verrons que quelques exemples dans ce cours. Vous en trouverez facilement des documentations détaillées en cherchant "Bibliothèque standard C" sur internet.
Voici un aperçu rapide du type de fonctions que vous pourrez trouver dans cette bibliothèque. Elle est décomposée en un certain nombre de fichiers d'en-tête, que vous devrez inclure au début de votre source pour utiliser les fonctions qui y sont définies :
assert.h : Fonctions de diagnostic, permettant d'aider à détecter les erreurs.
ctype.h : Fonctions permettant de déterminer le type d'un caractère (chiffre, majuscule, ...).
errno.h : Gestion des codes d'erreurs retournés par des fonctions de la bibliothèque.
float.h : Définit les diverses limites associées aux variables décimales.
limits.h : Définit les limites associées aux variables entières et caractères.
locale.h : Support du multilingue.
math.h : Fonctions mathématiques.
setjmp.h : Gestion du transfert d'exécution.
signal.h : Transmission de signaux entre programmes (processus).
stdarg.h : Accès aux paramètres des fonctions à nombre de paramètres variable.
stddef.h : Définitions générales.
stdio.h : Gestion des entrées sorties (dont maniplulation de fichiers).
stdlib.h : Fonctions utilitaires diverses.
string.h : Traitement de chaînes de caractères.
time.h : Manipulation du temps.
Une grande partie de ces fonctions utilise des notions du langage que nous n'avons pas encore vues. Il est donc encore un peu tôt pour vous jeter sur leur documentation détaillée, mais nous vous conseillons de la parcourir une fois que vous aurez terminé de lire ce cours. Nous en utiliserons bien sûr certaines au fil de ce cours et dans les exercices, mais nous vous les présenterons toujours avant de vous demander de les utiliser.
Vous aurez peut-être remarqué que cette bibliothèque ne contient pas de fonctions permettant de faire du graphisme, par exemple. L'une des raisons en est que cette bibliothèque de fonctions est fournie avec votre compilateur C quel que soit le système d'exploitation que vous utilisez (Linux, MacOS, ou même Windows). Le graphisme étant quelque-chose qui dépend fortement du système d'exploitation, il n'y a pas de manière simple et standard pour dessiner des objets graphiques à l'écran. Vous devrez pour cela utiliser des bibliothèques spécialisées.
Toutes les fonctions de la bibliothèque standard sont faites pour fonctionner quel que soit le système d'exploitation sur lequel vous exécutez votre prorgramme. On dit ainsi que les programmes qui n'utilisent que ces fonctions sont portables, puisqu'ils pourront fonctionner sur n'importe quel système. Il pourra cependant y avoir parfois de petites différences et il est toujours nécessaire de tester que votre programme marche bien partout.
Exercice : de nombreuses fonctions mathématiques sont disponibles dans la bibliothèque standard et définies dans le fichier d'en-tête math.h. Voici deux exemples de prototypes contenus dans ce fichier :
double sqrt(double x); : retourne la racine carrée du paramètre x.
double pow(double x, double y); : retourne la valeur de xy (x puissance y).
En utilisant ces fonctions, écrivez une fonction qui prend en paramètre les coordonnées (x1, y1) et (x2, y2) de deux points et retourne la distance euclidienne entre ces deux points. On rappelle que la distance euclidienne entre deux point est égale à la racine carrée de ((x2 - x1)2 + (y2 - y1)2).
Utilisez ensuite cette fonction dans un programme qui lit quatre nombres décimaux x1, y1, x2 et y2 tapés au clavier, puis affiche la distance entre les deux points correspondants.
Solution : #include #include double distance_euclidienne(double x1, double y1, double x2, double y2){ return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));} int main(){ double x1, y1, x2, y2; scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2); printf("%lf", distance_euclidienne(x1, y1, x2, y2)); return 0;}
4.4.5 Définitions de constantes
En plus de contenir les prototypes des fonctions de la bibliothèque standard du C, les fichiers d'en-têtes de celle-ci contiennent la définition d'un certain nombre de valeurs utiles. Par exemple, le fichier math.h contient une valeur approchée du nombre PI, très utilisé en mathématiques. Pour vous éviter d'avoir à écrire la valeur 3.1416, ou une valeur plus précise, à chaque fois que vous voulez utiliser PI dans un calcul, une constante est définie dans le fichier math.h, sous la forme suivante : #define M_PI 3.14159265358979323846
Cette ligne permet de définir que dans la suite du programme, toutes les occurences de l'identifiant M_PI seront remplacées par le nombre se trouvant à sa droite. Par exemple, le programme suivant affiche le périmètre d'un cercle dont le rayon est tapé au clavier : #include #include int main(){ double rayon; double perimetre; scanf("%lf", &rayon); perimetre = rayon * 2 * M_PI; printf("%lf\n", perimetre); return 0;}
Les instructions #include et #define, ainsi que d'autres qui commencent également par le caractère '#', ne sont pas des instructions traitées par le compilateur C lui-même, pour être transformées en instruction du microprocesseur. Il s'agit en fait d'instructions destinées à un programme qui lit votre source et y effectue des modifications, avant de le transmettre au compilateur. Ce programme est appelé préprocesseur : il traite (processe) votre source avant (pré) de le transmettre au compilateur, après quelques modifications.
Par exemple, dans le source ci-dessus, le préprocesseur remplace les instructions #include par le contenu complet des fichiers spécifiés et remplace l'identifiant M_PI par sa valeur, avant que le compilateur lui-même ne commence son travail.
Vous pouvez bien sûr définir vous-mêmes vos propres constantes. Par exemple, si vous voulez définir un programme qui demande l'âge de l'utilisateur, puis lui indique si cette personne est majeure, vous pouvez définir une constante AGE_MAJORITE qui vaudra 18, puis l'utiliser dans votre code : #include #define AGE_MAJORITE 18 int main(){ int age; scanf("%d", &age); if (age >= AGE_MAJORITE) printf("Vous êtes majeur\n"); else printf("Vous êtes mineur\n"); return 0;}
Les identifiants pouvant être utilisés pour définir des constantes sont les mêmes que ceux permettant de définir des variables ou des fonctions. Pour éviter de les confondre, on décide par convention que les noms de constantes définies par un #define doivent être en majuscules.
L'instruction #define permet d'associer bien plus que de simples valeurs numériques à des identifiants. On peut en fait placer n'importe quel texte à droite de l'identifiant de la constante, après un espace. Le préprocesseur remplacera toutes les occurences de l'identifiant dans la suite du programme, par le texte situé à droite de l'identifiant, dans l'instruction #define.
Par exemple, on peut écrire : #define AGE_MAJORITE age * 2 - 8
Dans ce cas, le programme sera modifié par le préprocesseur de la manière suivante : if (age >= age * 2 - 8) printf("Vous êtes majeur\n"); else printf("Vous êtes mineur\n");
Il est possible de placer des instructions #define à n'importe-quel endroit du programme, à condition qu'elles se trouvent au début de la ligne. Si un même identifiant est utilisé pour plusieurs #define, le préprocesseur remplace les occurences de l'identifiant par la valeur définie lors du dernier #define correspondant rencontré.
Exercice : dans l'exemple suivant, faites à la main le travail du préprocesseur, en remplaçant toutes les occurences de constantes par leur valeur (sans remplacer les #include par le contenu du fichier). Déterminez ensuite (sans l'exécuter) ce que ce programme affichera. #include #define PI 3.1416#define PI_2 PI * 2#define PI_BIS PI_2 - 3.1416 int main(){ printf("PI : %lf\n", PI); printf("PI_2 : %lf\n", PI_2); printf("PI_BIS : %lf\n", PI_BIS); printf("PI_BIS * 2 : %lf\n", PI_BIS * 2); return 0; }
Solution : voici le code obtenu après remplacement par le préprocesseur (sauf #include) : #include int main(){ printf("PI : %lf\n", 3.1416); printf("PI_2 : %lf\n", 3.1416 * 2); printf("PI_BIS : %lf\n", 3.1416 * 2 - 3.1416); printf("PI_BIS * 2 : %lf\n", 3.1416 * 2 - 3.1416 * 2); return 0; }
On peut remarquer que lorsque le texte "PI" apparaît entre guillemets dans le source, il n'est pas remplacé par le préprocesseur : il n'est remplacé que lorsqu'il est utilisé en tant qu'identifiant, là où l'on pourrait placer une variable.
Voici maintenant le résultat de l'exécution de ce programme : PI : 3.141600PI_2 : 6.283200PI_BIS : 3.141600PI_BIS * 2 : 0.000000
L'affichage de la dernière ligne n'est pas très naturel : au lieu d'être le double de la ligne précédente, comme pourrait le suggérer le code PI_BIS * 2, c'est 0 qui est affiché. L'expression obtenue après remplacement est en effet 3.1416 * 2 - 3.1416 * 2. Les priorités des opérateurs font alors que la multiplication par 2 ne se fait pas sur l'ensemble de ce que représente PI_BIS, mais uniquement sur le nombre qui suit le signe -. Pour éviter ce genre de surprises, lorsque l'on définit des constantes qui valent des expressions, on prend donc l'habitude de mettre des parenthèses autour de l'expression : #define PI_BIS (PI_2 - 3.1416)
4.5 Erreurs
4.5.1 Déclaration implicite, warnings
Essayez de compiler le programme suivant : int main(){ test(); return 0;}
Vous obtiendrez un message du type suivant : test.c: In function `main':test.c:3: warning: implicit declaration of function `test'/tmp/cc1pfH2s.o: In function `main':/tmp/cc1pfH2s.o(.text+0x7): undefined reference to `test'collect2: ld returned 1 exit status
La première partie du message n'est pas une erreur, mais un warning, c'est-à-dire un avertissement vous indiquant qu'il y a un risque d'y avoir une erreur dans votre programme, mais que ce n'est pas certain. L'avertissement implicit declaration of function `test' indique que la fonction test a été déclarée implicitement. Cela signifie tout simplement que vous l'avez appelée avant de la déclarer.
Le compilateur C peut en effet accepter d'appeler une fonction non déclarée, en devinant son prototype à partir de l'appel. Par exemple avec l'appel test(), le compilateur considère que test est une fonction qui ne prend aucun argument. Il considère aussi systématiquement que le type de retour est int (il ne peut pas le deviner à partir de l'appel).
L'utilisation des déclarations implicites, si elle peut parfois fonctionner, est fortement déconseillée. En effet, si le type deviné par le compilateur n'est pas le bon, il se produira une erreur à l'exécution, puisque la fonction n'aura pas les paramètres qu'elle attend. Ce warning vous prévient donc qu'il y a un risque, même si ce que vous faites est autorisé.
Il se peut que votre compilateur n'affiche pas cet avertissement. Pour être sûr de l'obtenir, il est important de prendre l'habitude d'activer toutes les options d'avertissements dans les préférences de votre compilateur. Si vous utilisez le compilateur gcc ou g++, il faut simplement ajouter systématiquement l'option -Wall au moment de la compilation (par exemple : gcc -Wall test.c).
La deuxième partie du message est par contre une erreur qui empêche la compilation de se terminer : la fonction test n'est pas trouvée dans votre code ni dans la bibliothèque standard et l'exécutable ne peut donc être généré.
4.5.2 Nombre de paramètres
Essayez de compiler le source suivant : #include int test(int a, int b){ return a + b;} int main(){ printf("%d\n", test(42)); return 0;}
Vous obtiendrez un message du type suivant : test.c: In function `main':test.c:10: too few arguments to function `test'
Le message se traduit en français : "trop peu de paramètres pour la fonction 'test'". Un paramètre peut en effet s'appeler argument de la fonction. L'erreur vient du fait que vous avez appelé la fonction test avec un seul paramètre, alors que son prototype indique qu'il en faut deux. Si vous aviez au contraire mis 3 paramètres lors de l'appel, vous auriez obtenu l'erreur suivante : test.c:10: too many arguments to function `test'
Cette erreur se traduit en "trop de paramètres pour la fonction 'test'".
On peut remarquer au passage qu'il n'est pas possible de déclarer deux fonctions portant le même nom en C, même si ces deux fonctions pourraient se distinguer lors de l'appel par le nombre de paramètres.
4.5.3 Valeur de retour
Essayez de compiler le programme suivant : #include int test(int a, int b){ if (a > b) return a; if (a < b) return b;} int main(){ printf("%d\n", test(42, 43)); return 0;}
Vous obtiendrez un message du type suivant : test.c: In function `test':test.c:9: warning: control reaches end of non-void function
Il s'agit ici d'un avertissement et non d'une erreur, ce qui signifie qu'un exécutable sera tout de même généré et que vous pourrez lancer votre programme. L'avertissement vous indique cependant qu'il y a de grandes chances pour que vous ayez commis une erreur à la ligne 9 de votre programme, c'est-à-dire à la fin de votre fonction test. Le message se traduit en "avertissement : le contrôle atteint la fin d'une fonction non-void". Une fonction non-void est une fonction qui a un type de retour différent de void (ici, int).
L'avertissement indique qu'il est possible que l'exécution atteigne la fin de la fonction. Ceci ne devrait pas arriver avec une fonction qui doit retourner une valeur : une instruction return devrait systématiquement être exécutée avant la fin du bloc d'instructions. Dans la fonction test, il est en effet possible qu'aucun des deux tests ne soit vérifié donc qu'aucune des deux instructions return ne soit exécutée.
4.5.4 Erreurs peu explicites
Essayez de compiler le programme suivant, puis de trouver d'où vient l'erreur : #include int test(int a; int b){ return a + b;} int main(){ printf("%d\n", test(42, 43)); return 0;}
Vous obtiendrez un message du type suivant : test.c:3: parameter `a' has just a forward declaration
L'erreur vient de la manière dont sont déclarés les paramètres de la fonction test : ceux-ci ne devraient pas être séparés par un point virgule, mais par une virgule.
Le message d'erreur en lui-même est difficile à comprendre, même pour des programmeurs expérimentés. On peut le traduire par "le paramètre 'a' a uniquement une déclaration en avance". La "déclaration en avance" permet de déclarer que des éléments existent avant de les utiliser, un peu comme pour les prototypes de fonctions. Ce genre de choses est cependant très peu utilisé et ne correspond en tout cas pas à l'erreur réellement commise, qui est une simple faute de frappe.
Il peut arriver fréquemment que des messages d'erreurs ou des avertissement soient difficiles à comprendre. Ils sont cependant toujours là pour une bonne raison : un compilateur n'invente jamais (ou très très rarement) des erreurs. Regardez donc le numéro de la ligne à laquelle elle se produit et relisez bien le code qui s'y trouve, ou le code concernant des éléments qui y sont utilisés.
4.6 Propreté du code
4.6.1 Décomposer pour simplifier
Nous avions vu au premier chapitre de ce cours qu'écrire des programmes simples à lire et comprendre permet de réduire les risques d'erreurs et facilite leur correction. Cela permet également de simplifier l'ajout et la modification de fonctionalités. Les fonctions sont un outil incontournable pour aller dans cette direction. D'une part elles permettent d'éviter les répétitions de blocs d'instructions similaires, d'autre part elles permettent de décomposer le programme en éléments simples.
Pour résoudre un problème très complexe, un moyen efficace consiste à le décomposer en sous-problèmes avant de résoudre chacun des sous-problèmes indépendamment. Chaque sous-problème est alors plus simple à appréhender que le problème complet. On peut ensuite décomposer à leur tour les sous-problèmes obtenus, jusqu'à obtenir des problèmes simples à résoudre. Décomposer un problème en sous-problèmes n'est pas toujours une tâche facile, mais en le faisant, on réduit considérablement la difficulté de sa résolution.
Les fonctions permettent d'appliquer cette technique à la réalisation de programmes. Prenons par exemple le programme suivant : #include int main(){ int nb_colonnes; int nb_lignes; int ligne; int colonne; scanf("%d", &nb_colonnes); colonne = 0; while (colonne < nb_colonnes) { printf("X"); colonne++; } printf("\n"); scanf("%d", &nb_lignes); ligne = 0; while (ligne < nb_lignes) { colonne = 0; while (colonne < ligne) { printf("*"); colonne++; } printf("\n"); ligne++; } printf("\n"); scanf("%d%d", &nb_lignes, &nb_colonnes); ligne = 0; while (ligne < nb_lignes) { colonne = 0; while (colonne < nb_colonnes) { printf("#"); colonne++; } printf("\n"); ligne++; } return 0;}
Cet exemple montre qu'un source plutôt bien présenté, avec des noms de variables choisis correctement et qui réalise quelque-chose de pourtant très simple, peut malgré tout devenir assez indigeste. On peut bien sûr le comprendre en le lisant entièrement ligne par ligne, mais il faut un peu de temps pour comprendre ce qu'il fait exactement. Cela reste raisonnable, car nous n'allons pas vous imposer la lecture du source d'un véritable projet, mais cela ne fait que s'aggraver avec la taille du programme si l'on ne prend pas le soin d'utiliser les fonctions.
Regardez maintenant le source suivant, qui fait exactement la même chose que l'exemple précédent, mais est cette fois organisé en fonctions : #include void affiche_ligne(int nb_colonnes, char motif){ int colonne = 0; while (colonne < nb_colonnes) { printf("%c", motif); colonne++; } printf("\n");} void affiche_triangle(int nb_lignes, char motif){ int ligne = 0; while (ligne < nb_lignes) { int nb_colonnes = ligne + 1; affiche_ligne(nb_colonnes, motif); ligne++; }} void affiche_rectangle(int nb_lignes, int nb_colonnes, char motif){ int ligne = 0; while (ligne < nb_lignes) { affiche_ligne(nb_colonnes, motif); ligne++; }} int main(){ int nb_colonnes; int nb_lignes; scanf("%d", &nb_colonnes); affiche_ligne(nb_colonnes, 'X'); scanf("%d", &nb_lignes); affiche_triangle(nb_lignes, '*'); scanf("%d%d", &nb_lignes, &nb_colonnes); affiche_rectangle(nb_lignes, nb_colonnes, '#'); return 0;}
Cette nouvelle version du même programme est bien plus facile à comprendre et bien plus agréable à lire, ceci pour plusieurs raisons :
Une fonction a été créée pour chacune des trois choses que fait le programme : afficher une ligne, un triangle et un rectangle. Chaque partie peut donc être lue séparément, ce qui est plus facile que de la lire au milieu d'autres instructions.
Le nom donné à chaque fonction décrit clairement ce qu'elle fait. Le nom des paramètres permet également de comprendre tout de suite à quoi ils correspondent. On peut ainsi lire les instructions de chaque fonction en sachant déjà ce que ces instructions sont supposées faire, ce qui aide beaucoup.
La fonction principale main ne contient quasiment plus que des appels à ces fonctions et est un résumé qui décrit très clairement, en quelques lignes, ce que fait l'ensemble du programme. Les noms des fonctions bien choisis font que ces lignes se comprennent tout de suite.
Les instructions d'affichage d'une ligne de symboles, utilisées dans les trois parties du programme, ne sont plus recopiées à chaque fois mais écrites une fois pour toutes dans la fonction affiche_ligne, qui est ainsi appelée par les deux autres fonctions.
Pour obtenir un source facile à lire et à comprendre, nous vous conseillons donc d'appliquer les mêmes principes à vos programmes :
Consacrer une fonction à chaque action distincte.
Choisir des noms de fonctions qui décrivent ce qu'elles font.
Regrouper les parties de code similaires en fonctions, pour éviter les répétitions.
Ne pas écrire de fonctions trop longues, éviter de dépasser 25 lignes.
Appliquer systématiquement ces conseils peut parfois sembler ennuyeux, surtout lorsque l'on est en plein dans l'écriture de code, que l'on se sent inspiré et a envie d'avancer rapidement. Le temps que l'on économise en ne prenant pas la peine d'appliquer ces divers conseils finit cependant toujours par être largement reperdu plus tard, en recherche de bugs ou modifications. L'effort consistant à bien organiser son code est en outre bien plus gratifiant que celui passé à chercher des bugs dans un source incompréhensible.
4.6.2 Ajout de commentaires
Pour faciliter la compréhension de vos sources, le langage C permet d'insérer des commentaires au sein de votre code. Vous pouvez ainsi placer des explications en français (ou autre) au milieu de votre programme, qui seront ignorées par le compilateur.
La première manière d'insérer des commentaires dans un programme C consiste à placer du texte entre les caractères /* d'un côté, et */ de l'autre. Tout texte placé entre les deux est ignoré par le compilateur, vous pouvez y placer tout ce que vous voulez, sauf les caractères */ qui représentent la fin de la zone commentée.
Voici un exemple de source contenant des commentaires : #include /* La fonction qui suit permet d'afficher une ligne de caractères. Elle prend en paramètre le nombre de caractères à afficher et le caractère à afficher.*/void affiche_ligne(int nb_colonnes, char motif){ int colonne = 0; /* Tant qu'on n'a pas atteint le nombre de colonnes indiqué */ while (colonne < nb_colonnes) { printf("%c", motif); /* On affiche le caractère */ colonne++; /* On passe à la colonne suivante */ } printf("\n"); /* On passe à la ligne */}/* La fonction est terminée */ ...
On notera que contrairement aux blocs d'instructions, les commentaires ne peuvent pas s'imbriquer les uns dans les autres. Par exemple, ceci est invalide et ne compilera pas : /* devenu inutile : printf("\n"); /* On passe à la ligne */*/
Une autre manière de mettre du texte en commentaire vient à l'origine du langage C++. Elle a été officiellement intégrée au langage C en 1999 et fonctionne dans la plupart des compilateurs. Certains compilateurs ne l'autorisent cependant pas et vous devez éviter de l'utiliser si vous souhaitez que votre programme puisse être compilé sur toutes sortes de machines.
Cette deuxième méthode permet de commenter tout ce qui se trouve sur une ligne, à droite d'un endroit donné. Tout ce qui suit les caractères // est en effet ignoré, jusqu'à la fin de la ligne. Voici ce que donne l'utilisation de ce style de commentaires sur l'exemple précédent : #include // La fonction qui suit permet d'afficher une ligne de caractères.// Elle prend en paramètre le nombre de caractères à afficher et// le caractère à afficher. void affiche_ligne(int nb_colonnes, char motif){ int colonne = 0; // Tant qu'on n'a pas atteint le nombre de colonnes indiqué while (colonne < nb_colonnes) { printf("%c", motif); // On affiche le caractère colonne++; // On passe à la colonne suivante } printf("\n"); // On passe à la ligne}// La fonction est terminée ...
Ajouter des commentaires à votre source est un bon moyen pour le rendre plus facilement lisible par d'autres, ou par vous-même plus tard. Avoir mis des commentaires ne doit cependant pas être une excuse pour avoir par ailleurs un code difficile à lire, des noms de variables et de fonctions peu clairs, etc. Avant d'ajouter des commentaires, réfléchissez à la manière dont vous pouvez modifier votre source pour qu'il soit compréhensible de lui-même. Après seulement, envisagez l'ajout de commentaires pour expliquer ce qui n'est pas évident à la lecture du code (et ne peut être rendu évident simplement).
Evitez autant que possible de mettre des commentaires au milieu du code, comme on en voit dans les exemples ci-dessus. En essayant d'expliquer ce que fait le source, ils ont l'inconvénient de le rendre plus long et moins facile à lire. Préférez des commentaires à l'extérieur des fonctions. Si votre fonction fait beaucoup de choses et que vous avez envie de mettre des commentaires entre les différentes parties pour expliquer ce qu'elles font, c'est probablement que votre fonction mérite d'être décomposée en sous-fonctions.
Les commentaires peuvent être très utiles en cours de développement, en particulier pour les fonctions qui ne sont pas terminées. Par exemple, vous pouvez insérer des commentaires aux endroits où il reste des choses à faire, à améliorer, ou à modifier. Plutôt que de remettre simplement quelque-chose à plus tard, au risque de l'oublier, mettez une note en commentaire à l'endroit concerné. Une notation usuelle pour indiquer une chose restant à faire consiste à commencer le commentaire par le texte "TODO:" (qui vient de l'anglais "to do", qui signifie "à faire") : int main(){ int nb_colonnes; scanf("%d", &nb_colonnes); /* TODO: vérifier que l'on n'entre pas une valeur négative ou trop grande */ affiche_ligne(nb_colonnes, 'X'); return 0;}
Une autre utilisation consiste à écrire d'abord en français le pseudo-code d'une fonction, en le laissant en commentaires, pour que le programme compile toujours, en attendant de l'avoir écrite en C.

Commentaires