A quoi ca sert ?
Les fonctions ne
sortent pas de nulle part : si on les a inventées, c'est qu'elles
peuvent servir à quelque chose. Reste à comprendre pourquoi. Car les
fonctions sont des inventions qui répondent à des besoins bien précis :
grandement faciliter la vie du programmeur !
Lorsque vous créez un programme, le résultat sera une grosse suite
d'instructions placées les unes à la suite des autres. Et parmi cette
gigantesque suite d'instructions, il y a souvent des "sous-suites", des
paquets d'instructions, des morceaux de code qui reviennent
régulièrement et qui sont présents en plusieurs exemplaires dans le
programme final. Ces sous-suites servent pratiquement toujours à
exécuter une tâche bien précise et ont presque toujours une
signification importante pour le programmeur. Par exemple, il va exister
une de ces sous-suites qui va servir à calculer un résultat bien
précis, communiquer avec un périphérique, ou autre chose encore.
Sans fonctionsSans utiliser de fonctions, ces suites d'instructions sont présentes en
plusieurs exemplaires dans le programme. Le programmeur doit donc
recopier à chaque fois ces suites d'instructions, ce qui ne lui facilite
pas la tâche. Et dans certains programmes, devoir recopier plusieurs
fois la séquence d'instruction qui permet d'agir sur un périphérique ou
de faire une action spéciale est franchement barbant !
De plus, ces suites d'instructions sont présentes plusieurs fois dans le
programme final, exécuté par l'ordinateur. Et elles prennent de la
place inutilement ! Mine de rien, à chaque fois qu'on recopie une de ces
suites d'instructions récurrentes, on réinvente la roue. On perd ainsi
beaucoup de temps à faire du copier-coller où à réécrire ce qui a déjà
été fait. Les informaticiens ont donc inventé un moyen qui permet à ces
suites d'instructions d'être présentes une seule fois dans le programme
et d'être réutilisables au besoin. On a donc inventé les
fonctions.
Avec les fonctionsLa technique du sous-programme consiste à n'écrire qu'un seul exemplaire
de ces suites d'instructions, et lui donner un nom. Au lieu de recopier
cet exemplaire à chaque fois qu'on veut le réutiliser, il suffira tout
simplement de placer son nom à la place. On appellera cette suite
d'instruction une
fonction.
Cet exemplaire sera écrit en dehors du programme principal, le fameux
main que l'on utilise depuis le début. On peut remarquer que la fonction
main est déjà une fonction, qui regroupe tout le programme.
Les fonctions, qui sont des bouts de code réutilisables au besoin, présentent ainsi de gros avantages :
- Elles sont réutilisables. Plus besoin de recopier
bêtement du code plusieurs fois de suite ! Avec une fonction, il suffit
d'écrire une seule fois la fonction et ensuite de l'appeler autant de
fois que l'on veut.
- Elles sont plus facilement maintenables : si votre
fonction est inadaptée, il suffit d'en changer le code. C'est beaucoup
plus rapide que de modifier plusieurs exemplaires d'une même suite
d'instruction, surtout s'ils sont disséminés n'importe comment dans tout
le programme.
- Elles permettent de mieux s'organiser : en divisant
le code en fonctions, on peut ainsi séparer les différentes opérations
et mieux s'y retrouver lors de la relecture du code. C'est quand même
plus agréable de lire un code bien aéré qu'une gigantesque suite
d'instructions de 2000 lignes dans laquelle tout est mélangé n'importe
comment.
Déclarer une fonction
Une fonction n'est donc qu'un
vulgaire bloc de code, un morceau de programme. Mais ce morceau de
programme a tout de même quelques caractéristiques.
Par exemple, imaginons que je veuille créer une fonction qui calcule une
opération mathématique complexe. On va prendre le logarithme d'un
nombre, par exemple (si vous ne savez pas ce que c'est, ce n'est pas
important). Notre fonction va donc devoir manipuler un nombre : celui
dont on veut calculer le logarithme. De même, elle va fournir un
résultat : le logarithme du nombre. Avec cet exemple, on voit qu'une
fonction doit être définie par trois éléments.
Elle a parfois besoin de
paramètres : ce sont toutes
les informations que l'on donne à la fonction pour qu'elle puisse
travailler. Ces informations vont donner des données que notre fonction
va devoir manipuler afin d'obtenir un résultat. Dans notre exemple, le
nombre dont on veut calculer le logarithme est un paramètre de la
fonction.
Elle contient aussi du
code : ce code va dire ce que va
faire la fonction. C'est tout ce qui compose l'intérieur d'une
fonction, une fonction sans code, une fonction vide entre autres est une
fonction inutile.
Et enfin, notre fonction peut renvoyer un
résultat. Ce n'est pas obligatoire d'en renvoyer un, mais la majorité des fonctions renvoient un résultat.
Déclarer une fonctionMaintenant que vous avez vu la partie "théorique", regardons comment
tout cela s'applique en C. On vient de voir qu'une fonction est
constituée de plusieurs éléments. Tous ces éléments seront donc indiqués
dans notre fonction, à des endroits différents. Mais commençons par le
commencement : nous allons d'abord apprendre à déclarer une fonction :
cela consiste à indiquer à notre langage qu'on veut créer une fonction.
Une fois celle-ci déclarée, il ne restera plus qu'à dire ce qu'elle
fait, en écrivant son code et en spécifiant le résultat.
Pour déclarer une fonction, nous allons devoir donner quelques
informations sur notre fonction, et notamment sur ses arguments et sur
son résultat. Ces fameuses informations sont :
- le type de retour : il s'agit du type du résultat
de la fonction. Après tout, notre fonction pourrait aussi bien renvoyer
un nombre entier, qu'un flottant ou un caractère, aussi préciser le type
du résultat est obligatoire.
- le nom de la fonction : c'est vous qui le choisissez. Les règles sont les mêmes que pour les variables.
- les paramètres : les paramètres sont ce avec quoi la fonction va travailler. Vous pouvez en mettre autant que vous voulez.
Pour déclarer une fonction, il va falloir préciser ces trois détails.
Voici comment procéder pour préciser ces trois paramètres et ainsi
déclarer une fonction :
Code : Autre -
1 2 3 4
| type identificateur (paramètres) { /* corps de la fonction */ } |
À l'intérieur de notre fonction (dans ce qui est nommé le corps de la
fonction dans l'exemple du dessus), on va y placer le code de notre
fonction.
Pour illustrer ce concept, prenons un exemple tout banal :
Code : C -
1 2 3 4 | int ma_fonction(int parametre) { /* Instructions */ }
|
J'ai ici défini une fonction appelée ma_fonction. Elle prend un int comme paramètre, et a pour résultat un int.
voidIl se peut que l'on est besoin de coder une fonction qui ne retourne
aucun résultat. C'est un cas courant en C. Ce genre de fonction est
appelé
procédure. Pour écrire une procédure, il faut
indiquer à la fonction en question qu'elle ne doit rien retourner. Pour
ce faire, il existe un "type de retour" spécial : void. Ce type signifie "vide", et sert à indiquer que la fonction n'a pas de résultat.
Ce mot-clef sert aussi à indiquer qu'une fonction ne prend aucun
paramètre. C'est assez rare, mais cela arrive. Dans ce cas, il suffit de
définir la fonction en mettant void dans la liste des paramètres.
ParamètresUn paramètre sert à fournir des informations à la fonction lors de son exécution. La fonction printf par exemple récupère ce qu'elle doit afficher dans la console à l'aide de paramètres.
Vous pouvez envoyer autant de paramètres à une fonction que vous voulez,
il suffit de les séparer à l'aide d'une virgule. Cependant, ils doivent
avoir des noms différents, tout comme les variables. Il est aussi
possible de ne pas mettre d'arguments dans notre fonction, comme indiqué
plus haut.
ExemplesPour vous faire bien saisir toutes ces notions, entrainez vous à déclarer des fonctions. Essayez de déclarer une fonction :
- retournant un double et prenant un char et un int en argument ;
- retournant un unsigned short et ne prenant aucun paramètre ;
- retournant un float et prenant un int, un long et un double en paramètres ;
- retournant un int et prenant un int constant et un unsigned long en paramètre ;
- ne retournant rien et ne prenant aucun paramètre ;
Je pense qu'avec tous ces exemples vous commencez à bien saisir comment déclarer une fonction.
Le corps d'une fonctionIntéressons-nous maintenant au corps de la fonction, le code qu'il y a à l'intérieur. Comme pour la fonction main,
le code est à l'intérieur des accolades. Et ce code, c'est nous qui
allons l'écrire. Alors que dois t'on écrire ? En bref, ce que vous
voulez que la fonction fasse.
ReturnÀ ce stade, vous savez comment déclarer une fonction sans problème. Il
vous manque juste une dernière information : comment faire pour préciser
quel est le résultat de la fonction ? Comment lui dire : "Le résultat
que tu dois renvoyer, c'est ça" ?
Pour cela, on doit utiliser le mot-clef return. Une fois que vous avez une variable qui contient le résultat que vous voulez, il suffit d'écrire return,
suivi du nom de la variable, le tout suivi d'un point-virgule. À ce
moment-là, la fonction s’arrêtera et renverra son résultat
immédiatement. Cela signifie que tout le code qui est écrit après le return ne sera pas exécuté : notre fonction a déjà son résultat de disponible, pourquoi faire quoi que ce soit de plus ?
Petite remarque : un return peut parfaitement renvoyer une valeur qui est une constante.
Pour donner un exemple, on va prendre une fonction assez simple, qu'on
nommera valueSign. Notre fonction va prendre un argument de type int en entrée et va renvoyer :
- 0 si cet argument est nul ;
- 1 si celui-ci est positif ;
- et -1 si celui-ci est négatif.
Une version naïve de cette fonction s'écrirait comme ceci :
Code : C -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int valueSign (int a) { if ( a > 0 ) { return 1 ; } else if ( a < 0 ) { return -1 ; } else { return 0 ; } }
|
Variables localesAutre détail, qui concerne les variables que vous déclarez à l'intérieur
du corps d'une fonction. Autant vous prévenir tout de suite : n'essayez
pas d’accéder à une variable qui est déclarée dans une fonction en
dehors de celle-ci. Si vous faites cela, vous allez au-devant de graves
ennuis.
En effet, sauf cas exceptionnels, il faut savoir que ces variables ne
sont accessibles que dans notre fonction, et pas de l'extérieur. C'est
ainsi : les variables déclarées à l'intérieur de la fonction sont des
données temporaires qui lui permettent de faire ce qu'on lui demande.
Ces données sont des données internes à notre fonction, qu'elle seule
doit manipuler et qui ne doivent généralement pas être accessibles à
d’autres programmes ou d'autres fonctions. Si ce n'est pas le cas, c'est
que cette variable doit être passée en paramètre ou qu'elle doit être
renvoyée en tant que résultat.
En fait, vous pouvez considérer que dans la majorité des cas, ces
variables déclarées dans une fonction sont créées quand on commence
l’exécution de la fonction, et qu'elles sont enlevées de la mémoire une
fois que la fonction renvoie son résultat. Si je dis la majorité des
cas, c'est qu'il y a une exception. Mais laissons cela de côté pour le
moment : le temps de parler des variables statiques n'est pas encore
arrivé.
ExemplePrenons un exemple tout bête. Vous voulez faire une fonction qui renvoie
le carré d'un nombre passé en paramètre. Commençons déjà par traduire
notre fonction en pseudo-code :
Code : Autre -
1 2 3 4 5
| Entrée : nombre
Carré : Multiplier nombre par lui-même Retourner nombre |
Maintenant, exerçons-nous en codant cet algorithme. Je vous encourage à
le faire avant de regarder la solution, cela vous fera progresser.
Dans notre code, on donne comme valeur à la variable nombre fois elle-même (c'est à dire nombre * nombre), puis on retourne cette valeur grâce à l'instruction return.
Petite remarque. Dans cet exemple, on peut raccourcir le code comme suit :
Code : C -
1 2 3 4 5 | int carre(int nombre) { return nombre * nombre; /* ou encore "return nombre *= nombre;" */ }
|
Utiliser une fonction
Nous avons déjà utilisé quelques fonctions, notamment printf et scanf.
Pour les utiliser, il suffit de taper le nom de la fonction suivi des
paramètres entre parenthèses. Eh bien, pour nos fonctions c'est
exactement la même chose. Prenons pour illustration la fonction carre vue dans la partie précédente. Voici le programme complet :
Code : C -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h>
int carre(int nombre) { return nombre * nombre; }
int main(void) { int nombre, nombre_au_carre;
printf("Entrez un nombre\n"); scanf("%d", &nombre); nombre_au_carre = carre(nombre);
printf("Voici le carre de %d : %d\n", nombre, nombre_au_carre);
return 0; }
|
On demande à l'utilisateur de rentrer un nombre entier. Une fois ceci fait, on appelle la fonction avec cette ligne :
Code : C -
1 | nombre_au_carre = carre(nombre);
|
On dit que nombre est un argument de la fonction carre.
Paramètres et arguments sont très liés ; la différence entre les deux
est que les premiers apparaissent lors que la définition de la fonction
alors que les seconds apparaissent lors de son l'appel.
On demande ensuite à attribuer à la variable nombre_au_carre la valeur retournée par la fonction carre. Ainsi, si nombre vaut 4, on appelle la fonction, et celle-ci retournera alors 16 (car
).
Un petit code commenté ?
Code : C -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <stdio.h>
/* d) le nombre passé en paramètre en c) est récupéré */
int carre(int nombre) { /* e) on fait le calcul et on renvoie la valeur */ return nombre * nombre ; }
int main(void) { /* a) on déclare nos variables */ int nombre, nombre_au_carre;
printf("Entrez un nombre\n"); /* b) on récupère la valeur de nombre */ scanf("%d", &nombre); /* c) on appelle la fonction carre */ nombre_au_carre = carre(nombre); /* f) nombre_au_carre vaut maintenant la valeur retournée par la fonction carre */ /* g) on affiche le résultat */ printf("Voici le carre de %d : %d\n", nombre, nombre_au_carre);
return 0; }
|
Ça va vous suivez ? C'est simple : lors de l'appel de la fonction, on
lui donne des arguments et le programme s'occupe du reste. C'est lui qui
fera les calculs et qui renverra la valeur à afficher.
Sachez qu'il est possible d'optimiser notre code en se passant de cette
variable intermédiaire qui stocke le résultat. En effet, on peut très
bien appeler la fonction directement dans le printf, comme ceci :
Code : C -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h>
int carre(int nombre) { return nombre * nombre; }
int main(void) { int nombre;
printf("Entrez un nombre\n"); scanf("%d", &nombre);
printf("Voici le carre de %d : %d\n", nombre, carre(nombre));
return 0; }
|
Ce code revient au même que le précédant, car le deuxième paramètre de printf
sera la valeur retournée par la fonction. Autrement dit, c'est un
nombre dans les deux cas, et affichera bien la même chose à l'écran :
Code : Console -
Entrez un nombre 10 Voici le carre de 10 : 100 |
La fonction main appelle la fonction printf, qui elle-même appelle la fonction carre.
C'est une imbrication de fonctions. Ainsi, une fonction peut en appeler
une autre ; c'est ainsi que tout programme écrit en C fonctionne.
Entrainez-vous à appeler des fonctions en utilisant toutes les fonctions
que nous avons vues au cours de ce chapitre. Nous verrons d'autres
exercices en fin de chapitre:
Appel de fonctionsIl faut aussi préciser une chose importante sur les arguments : si on
passe une variable en argument d'une fonction, la variable en elle-même
ne sera pas modifiée. La fonction utilisera à la place une copie de la
variable ! C'est très important, et c'est source de comportements
bizarres si on ne fait pas attention. Retenez bien :
les arguments d'une fonction sont copiés et c'est cette copie qui est manipulée par notre fonction.
Considérons l'exemple suivant.
Code : C -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h>
void fonction(int nombre) { ++nombre; printf("Variable nombre dans la fonction : %d\n", nombre); }
int main(void) { int nombre = 5;
fonction(nombre);
printf("Variable nombre dans le main : %d\n", nombre);
return 0; }
|
Code : Console -
Variable nombre dans la fonction : 6 Variable nombre dans le main : 5 |
Vous avez vu ? La fonction manipule bien une copie de la variable, car lorsque l'on revient dans la fonction main,
la valeur de la variable est toujours la même : l'original n'a pas été
modifié. Nous verrons néanmoins dans quelques chapitres comment modifier
l'original dans la fonction et non une copie.
Les prototypes
Avez-vous remarqué qu'à chaque fois je mets ma fonction avant la fonction main ? En effet, mettre la fonction après le main provoquera un comportement indéterminé. La compilation pourrait très bien marcher comme elle pourrait planter.
En effet, lorsque la fonction est placée avant, le compilateur connait
ses paramètres et sa valeur de retour. Du coup, quand on appelle la
fonction, le compilateur vérifie que les arguments qu'on lui donne sont
bons. Si au contraire la fonction est après, le compilateur ne connait
pas la fonction. Du coup, il lui fixe arbitrairement des
caractéristiques : la fonction retourne un int
et prend un nombre indéterminé de paramètres. Et quand on tente
d'appeler la fonction, la compilation plante, car les arguments ne
correspondent pas aux yeux du compilateur.
Heureusement, il existe une sorte de mode d'emploi qui permet d'indiquer
toutes les caractéristiques d'une fonction au compilateur. Avec cette
indication, on peut placer la fonction où on veut dans le code. Et ce
mode d'emploi a un nom : un
prototype. Un prototype se déclare quasiment comme une fonction :
Code : Autre -
1
| type nom_de_la_fonction(arguments); |
Voilà à quoi ressemble un prototype. Placez-le simplement tout en haut
de votre fichier et c'est bon ! votre fonction est utilisable partout
dans le code. Essayez donc d'appliquer ça à la fonction carre :
Secret
Ce code marche parfaitement, vous pouvez tester si vous voulez. La seule
chose à retenir c'est le point-virgule après le prototype. Il est
obligatoire, sinon la compilation plantera.
Si vous mettez toutes vos fonctions avant le main, les prototypes
peuvent sembler inutiles, mais je vous encourage à les utiliser. Dès que
vous aurez des projets conséquents, vous serez obligés de les déclarer.
Avant de conclure sur cette partie, je tiens à préciser quelque chose :
dans les paramètres du prototype, seuls les types sont vraiment
nécessaires, les identificateurs sont facultatifs. Ainsi le prototype
précédant peut s'écrire :
Code : C -
Exemples
Comme le dit le vieil adage : "C'est
en forgeant qu'on devient forgeron". C'est donc en vous entrainant que
vous devenez petit à petit des programmeurs C. Dans cette partie, je
vais vous proposer des algorithmes de fonctions, ce sera à vous de les
traduire en C. Je mets la solution au cas où vous auriez vraiment du
mal, mais je vous encourage à le faire avant de regarder la solution.
C'est comme ça que vous progresserez le plus.
Afficher un rectangleLe premier exercice que je vous propose est d'afficher un rectangle.
C'est très simple vous allez voir. Voici l'algorithme que je vous
propose :
Code : Autre -
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Entrée : longueur, largeur
Afficher Déclarer deux variables i (longueur) et j (largeur);
Pour (i = 0 ; i < longueur ; i++) { Pour (j = 0; j < largeur ; j++) { Afficher le symbole '*' }
Sauter une ligne } |
Le code devra afficher ceci dans la console :
Code : Console -
Donnez la longueur : 5 Donnez la largeur : 3
*** *** *** *** *** |
Un solution :
Secret
Essayez aussi d'afficher le rectangle dans l'autre sens si vous voulez, cela vous servira d'entrainement.
Afficher un triangleCette fonction est similaire à la précédente, mais pas tout à fait
identique, sauf que cette fois on veut afficher un triangle.
Rassurez-vous, l'exercice est plus simple qu'il n'y parait. Pour bien
faire cet exercice, on va utiliser d'abord le pseudo-code pour écrire
notre algorithme. En voici un tout simple que je vous propose :
Code : Autre -
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Entrée : nombre_de_lignes
Afficher Déclarer deux variables i (nombre de lignes) et j (nombre de colonnes);
Pour (i = 0 ; i <= nombre_de_lignes ; i++) { Pour (j = 0; j <= i ; j++) { Afficher le symbole '*' }
Sauter une ligne } |
Ce code, une fois traduit, devrait afficher la sortie suivante dans la console :
Code : Console -
Donnez un nombre : 5
* ** *** **** ***** ****** |
Bien entendu, la taille du triangle variera en fonction du nombre que l'on donne.
Secret
CoupureImaginez le scénario suivant. Vous êtes un agent dans une banque et
aujourd'hui vous recevez votre client. Celui-ci vous demande de lui
livrer une somme avec la coupure qu'il vous a indiquée. Par exemple, il
vous dit qu'il souhaite récupérer 300 000 € uniquement en billets de
500€ et de 200€. Dans ce cas vous lui donnerez le plus de billets de
500€ possible puis vous continuerez avec des billets de 200€.
Ici, la coupure sera la suivante :
- Des billets de 100€.
- Des billets de 50€.
- Des billets de 20€.
- Des billets de 10€.
- Des pièces de 2€.
- Des pièces de 1€.
Votre client vous indique la somme qu'il souhaite et vous la lui fournissez en tenant compte de la coupure spécifique.
Code : Console -
Quelle somme voulez-vous ? 285 2 billet(s) de 100. 1 billet(s) de 50. 1 billet(s) de 20. 1 billet(s) de 10. 2 pièce(s) de 2. 1 pièce(s) de 1. |
Exemple de prototype pour la fonction :
Code : C -
1 | void coupure (const int somme);
|
Code : Autre -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| Entrée : somme
Afficher Déclarer deux variables n (nombre de billet) = somme et tmp (variable temporaire) = somme; Si n /= 100 > 0 { Afficher "n billet(s) de 100." }
tmp -= n * 100; n = tmp / 50;
Si (tmp / 50) > 0 { Afficher "n billet(s) de 50." }
tmp -= n * 50; n = tmp / 20;
Si (tmp / 20) > 0 { Afficher "n billet(s) de 20." }
tmp -= n * 20; n = tmp / 10;
Si (tmp / 10) > 0 { Afficher "n billet(s) de 10." }
tmp -= n * 10; n = tmp / 2;
Si (tmp / 2) > 0 { Afficher "n piece(s) de 2." }
Si ((tmp -= n * 2) > 0) { Afficher "tmp piece(s) de 1." } |