You are on page 1of 25

Chapitre 8

Les fonctions

Ds lors quun programme commence prendre une certaine importance, sa comprhension peut devenir dlicate si lon ne dispose daucun moyen de lorganiser. Certes, lutilisation judicieuse des commentaires peut permettre de mettre en vidence les diffrentes parties du programme ; il nen reste pas moins que le lecteur de ce programme en a une vision squentielle, la manire du lecteur dun livre qui ne disposerait pas de table des matires. Par ailleurs, dans un gros programme, il est frquent que lon ait raliser en plusieurs endroits un travail comparable, par exemple le tri dun tableau de nombres. Dans ces conditions, il est regrettable davoir introduire, diverses reprises, des instructions identiques ou presque. En fait, la plupart des langages, y compris le C, permettent de raliser ce que lon nomme des sous-programmes. Il sagit dinstructions quon crit une seule fois en leur attribuant un nom. Le sous-programme peut ensuite tre utilis en tout point dun programme en se contentant den crire le nom. Cela permet, en quelque sorte, de travailler avec des lments prfabriqus au lieu dtre rduit, chaque fois, assembler les briques de base que sont les instructions du langage. De surcrot, le sous-programme peut tre paramtr, de faon que son travail puisse sadapter des situations semblables, mais non identiques ; par exemple, un sousprogramme calculant la valeur dun polynme pourra travailler avec diffrentes valeurs des coefcients ou de la variable ; un sous-programme de tri dun tableau pourra travailler avec diffrents tableaux, ventuellement de tailles diffrentes

ditions Eyrolles

135

Le livre du C Premier langage

Dans beaucoup de langages, on traite sparment le cas des sous-programmes qui se contentent de calculer une valeur, partir dun ou plusieurs paramtres quon leur fournit ; on les nomme des fonctions, par analogie avec la notion mathmatique de mme nom. Assez curieusement, le langage C ne fait pas cette distinction et, de plus, il emploie ce terme de fonction pour dcrire toutes les formes de sous-programmes. Dans ce chapitre, nous allons apprendre progressivement mettre en uvre ces fonctions. Nous commencerons par un exemple simple de (vraie) fonction un paramtre, avant daborder des situations plus gnrales : vraie fonction plusieurs paramtres, fonction sans paramtre, fonction sans rsultat

1. Premier exemple de dnition et dutilisation dune fonction en C


Nous allons donc commencer par un exemple simple de fonction, correspondant lide usuelle (mathmatique) quon se fait dune fonction, cest--dire possdant un unique paramtre (on parle aussi dargument) et fournissant une valeur en rsultat. Pour ce faire, nous allons prendre lexemple dune fonction permettant de calculer le cube dune valeur ottante. Il est ncessaire de bien distinguer :
s la dnition de la fonction, cest--dire les instructions qui la constituent ; en mme temps,

on y prcisera le nom (ici, nous choisirons le nom cube), le type de son unique paramtre (ici oat) et le type de la valeur quelle est cense calculer (ici oat) ;
s son utilisation, au sein dun programme.

Par souci de simplicit, nous commencerons par le deuxime aspect ; nous supposons donc que la fonction a t convenablement dnie et nous allons voir comment lutiliser. Il nous sera ensuite plus facile de voir comment la dnir.

1.1 Utilisation de notre fonction cube


Si, par exemple, la variable a est de type oat, la notation :
cube (a)

dsigne simplement le rsultat (de type oat) obtenu en excutant les instructions de la fonction cube, laquelle on fournit en paramtre la valeur de a. On dit galement que cette notation dsigne un appel de la fonction cube. Cette notation peut apparatre au sein dune expression arithmtique quelconque ; par exemple, si c est galement de type oat, vous pourrez crire laffectation suivante :
c = cube(a) * 3.0 ;

136

ditions Eyrolles

chapitre n 8

Les fonctions

Pour traduire convenablement une telle instruction, le compilateur doit connatre les caractristiques de la fonction cube (nom, type du paramtre, type de la valeur fournie en rsultat) ; on lui fournit cette information laide dune dclaration approprie, nomme prototype, laquelle, dans notre cas, se prsente ainsi :
float cube (float) ;/* prototype de notre fonction cube */

Voici un exemple de programme utilisant notre fonction (attention, il est incomplet, car il y manque la dnition de la fonction) :
main() { float cube (float) ; float a, c ; a = 1.5 ; c = cube(a) ; printf (c : %f\n, c) ; c = cube(a) * 3.0 ; /* prototype de la fonction cube */

/* premire utilisation de cube */ /* seconde utilisation dans une expression arithmtique */ /* troisime utilisation */

printf (c : %f\n, c) ; printf (cube de 2.2 : %f, cube(2.2)) ; }

N e confondez pas la dclaration de la fonction avec sa dnition (que nous tudions dans le
paragraphe suivant).

1.2 Dnition de notre fonction cube


Voici ce que pourrait tre la dnition de notre fonction cube, cest--dire les instructions dcrivant le calcul quelle doit effectuer :
float cube (float x) { float y ; y = x * x * x ; return y ; } /* en-tte de la fonction cube (attention, pas de ;) */ /* variable locale la fonction cube */

Vous constatez que sa structure est voisine de celle dun programme, savoir : un en-tte suivi dun corps form dun bloc (suite dinstructions entre { et }). Len-tte :
float cube (float x)

ditions Eyrolles

137

Le livre du C Premier langage

prcise ici, outre le nom de la fonction (ici cube), le nom du paramtre (ici x), son type (oat), ainsi que le type de la valeur que nous fournira la fonction (ici oat). Son rle peut tre schmatis ainsi : oat cube (oat x) | | | type du nom de la type et nom du premier rsultat fonction (et, ici, unique) paramtre Notez bien que le nom x dsigne la valeur du paramtre qui sera reu par cube, lorsquon lappellera ; il est choisi librement comme nimporte quel nom de variable. Il va nous servir, dans le corps de la fonction, expliciter lusage qui sera fait de ce paramtre. Si lon considre le corps de notre fonction, on y trouve une instruction de dclaration : oat y ; Elle prcise que, pour effectuer son travail, la fonction a besoin dune variable de type oat que nous nommons y. Il sagit l dune dclaration analogue celle que lon pouvait trouver dans un programme ; la seule diffrence est que cette variable y nest connue que dans les instructions de dnition de notre fonction : on dit que la variable y est locale la fonction cube. Linstruction daffectation qui suit est classique. Enn, linstruction :
return y ;

prcise la valeur qui sera fournie (on dit aussi renvoye ou retourne) par la fonction, la n de son travail (on dit aussi de son excution).

Remarques E n fait, on emploie parfois le terme de programme principal pour dsigner ce que nous avions
appel jusquici programme ; le terme de programme tant plus gnral et pouvant, le cas chant, correspondre au regroupement dun programme principal et de une ou plusieurs fonctions.

L a valeur fournie par une fonction se nomme aussi rsultat ou valeur de retour. E n C, le programme principal nest rien dautre quune fonction comme les autres dont seul le
nom est impos : main (qui signie principal(e) en anglais). Les variables que nous avions dclares jusquici taient en fait des variables locales cette fonction main.

L e prototype se dduit de len-tte de la dnition de la fonction, en liminant les noms des


paramtres et en ajoutant un point-virgule.

1.3 Mise en uvre de notre fonction cube


Jusquici, nous vous avons prsent sparment la dnition de notre fonction cube et la dnition du programme principal (fonction main) utilisant cette fonction. Pour mettre tout cela en uvre, il vous suft de faire compiler ces deux parties en les juxtaposant dans un ordre quelconque ; on peut donc placer la dnition du programme principal (fonction main) avant

138

ditions Eyrolles

chapitre n 8

Les fonctions

ou aprs la dnition de la fonction cube. Voici un exemple complet de ce que pourrait tre notre programme (main + cube), accompagn dun exemple dexcution :
/* exemple de programme utilisant notre fonction cube */ main() { float cube (float) ; /* prototype de la fonction cube */ float a, c ; a = 1.5 ; c = cube(a) ; /* premire utilisation de la fonction cube */ printf (c : %f\n, c) ; c = cube(a) * 3.0 ; /* seconde utilisation dans une exp. arith. */ printf (c : %f\n, c) ; printf (cube de 2.2 : %f, cube(2.2)) ; /* troisime utilisation */ } /* dfinition de notre fonction cube */ float cube (float x) /* en-tte de la fonction cube */ { float y ; /* variable locale la fonction cube */ y = x * x * x ; return y ; }

c : 3.375000 c : 10.125000 cube de 2.2 : 10.648001

D ans la dnition de notre fonction cube, nous pouvons utiliser pour le paramtre et pour la
variable locale, des noms quelconques, sans nous proccuper de ceux qui risquent dapparatre dans un programme amen lutiliser. Ainsi, bien que la fonction main dispose de variables locales nommes a et c, nous aurions pu, sans problme, dnir cube de cette faon : float cube (float a) { float c ; c = a * a * a ; return c ; } On dit que la porte des variables locales (comme c) et des paramtres (comme a) est limite la fonction o ils sont dnis.

ditions Eyrolles

139

Le livre du C Premier langage

Remarques L orsque

la dnition de la fonction est fournie en premier, sa dclaration (prototype) est facultative ; ceci provient de ce que, au moment o le compilateur rencontre des instructions utilisant la fonction, il a dj effectu la traduction de sa dnition et, donc, il dispose des informations ncessaires. Nous vous conseillons cependant de conserver lhabitude de dclarer votre fonction, mme dans cette situation.

E n fait, il est possible de compiler sparment le programme principal (main) et la fonction cube. Dans ce cas, les deux rsultats de compilation (quon nomme des modules objet) doivent tre runis par la suite, dans une opration nomme dition de liens. Ces possibilits dites de compilation spare constituent un atout majeur du langage C ; elles sortent cependant du cadre de cet ouvrage dinitiation.

VIII.1 Quels seront les rsultats fournis par ce programme :


main() { int arrondi (float) ; /* prototype de la fonction arrondi */ float v1 = 1.6, v2 = 2.8 ; int p ; p = arrondi (v1) ; printf (%d\n, p) ; p = arrondi (v2) ; printf (%d\n, p) ; printf (%d %d\n, arrondi(v1+v2), arrondi(v1) + arrondi(v2) ) ; } int arrondi (float r) { float y ; int n ; y = r + 0.5 ; n = y ; return n ; }

2. Dautres exemples de fonctions


Lexemple prcdent tait particulier pour diverses raisons :
s notre fonction comportait exactement un paramtre ; en C, une fonction peut en comporter

plusieurs ou aucun ;
s il sagissait dune vraie fonction, cest--dire que son rle tait limit un calcul ;

comme nous lavons dit en introduction, en C, une fonction peut raliser nimporte quelles actions et, ventuellement, ne pas fournir de rsultat.

140

ditions Eyrolles

chapitre n 8

Les fonctions

2.1 Exemple de vraie fonction plusieurs paramtres


Voici la dnition dune fonction, nomme max, qui fournit en rsultat la plus grande des trois valeurs entires reues en paramtres :
int max (int a, int b, int c) { int m ; m = a ; if ( b>m ) m = b ; if ( c>m ) m = c ; return m ; }

Cette fois, len-tte mentionne trois paramtres et correspond au schma suivant : int | type du rsultat max | nom de la fonction (int a, | premier paramtre (nom a, type int) int b, | deuxime paramtre (nom b, type int) int c) | troisime paramtre (nom c, type int)

Ici encore, notre fonction comporte une variable locale, nomme m, qui servira dterminer la valeur maximale cherche. Voici un exemple de programme complet comportant la fois la dnition et lutilisation de cette fonction (notez que, l encore, le prototype de la fonction se dduit facilement de son en-tte, en liminant les noms de paramtres) :
main() { int max (int, int, int) ; /* prototype de notre fonction max */ int n, p, q, m ; n = 3 ; p = 5 ; q = 2 ; m = max (n, p, q) ; printf (max de %d %d %d : %d\n, n, p, q, m) ; m = max (5*n, n+p, 12) ; printf (valeur : %d\n , m) ; } int max (int a, int b, int c) { int m ; m = a ; if ( b>m ) m = b ; if ( c>m ) m = c ; return m ; }

ditions Eyrolles

141

Le livre du C Premier langage

max de 3 5 2 : 5 valeur : 15

VIII.2

crivez la dnition dune fonction calculant la valeur de lexpression : ax2 + bx + c lorsquon lui transmet, en paramtre, les valeurs de a, b, c et x (supposes de type oat, ainsi que le rsultat de la fonction).

2.2 Exemple de fonction sans rsultat


Voyez cette dnition de fonction nomme optimist :
void optimist (int nfois) { int i ; for (i=0 ; i<nfois ; i=i+1) printf (il fait beau\n) ; }

Son en-tte montre quelle comporte un paramtre entier (nomm nfois) ; cette fois, il est prcd du mot void qui indique que la fonction ne fournit pas de rsultat. Si lon examine les instructions du corps de la fonction, on constate quelles afchent un certain nombre de fois (correspondant la valeur transmise en paramtre) le mme texte : il fait beau. L encore, pour effectuer son travail, notre fonction a eu besoin dune variable locale (i). Notez quaucune instruction return ne gure dans la dnition de notre fonction. En ce qui concerne lutilisation de notre fonction, compte tenu de ce quelle ne fournit aucun rsultat, il nest plus possible den mentionner un appel au sein dune expression arithmtique, comme, par exemple :
y = optimist (k) ;/* incorrect */

En fait, il suft de se contenter den provoquer lappel laide dune instruction de la forme :
optimist (k) ; /* instruction simple provoquant lappel de optimist laquelle on transmet en paramtre, la valeur de k */

142

ditions Eyrolles

chapitre n 8

Les fonctions

Voici un exemple complet de dnition et dutilisation de notre fonction optimist :


main() { void optimist (int) ; /* prototype de la fonction optimist */ int n = 2, p = 1 ; optimist (n) ; optimist (p) ; optimist (n+p) ; } void optimist (int nfois) { int i ; for (i=0 ; i<nfois ; i=i+1) printf (il fait beau\n) ; }

il il il il il il

fait fait fait fait fait fait

beau beau beau beau beau beau

N ous avons rencontr des situations extrmes, savoir :


soit ce que lon pourrait nommer une fonction calcul, cest--dire une fonction effectuant un calcul dont elle fournit le rsultat, soit une fonction action, cest--dire une fonction ne fournissant aucun rsultat mais ralisant une action. Mais rien ne vous empche de mlanger les genres (si ce nest, parfois, le manque de lisibilit qui peut en dcouler) en ralisant une fonction qui accomplit certaines actions, tout en effectuant un calcul quelle fournit en rsultat.

VIII.3
a) crivez les instructions permettant dafcher un triangle dastrisques se prsentant ainsi (le nombre de lignes, ici 4, tant indiqu dans une variable nomme nl ) : * ** *** ****

ditions Eyrolles

143

Le livre du C Premier langage

b) Transformez ces instructions en une fonction nomme triangle recevant en paramtres le nombre de lignes souhaites. c) crivez un programme principal utilisant la fonction triangle ainsi obtenue, de faon afcher le motif suivant : * ** * ** *** * ** *** ****

3. Quelques rgles
3.1 Les paramtres formels (muets) et les paramtres effectifs
Les paramtres gurant dans len-tte dune fonction se nomment des paramtres muets (ou encore paramtres formels). Leur rle est de permettre, au sein du corps de la fonction, de dcrire ce quelle doit faire. Comme nous lavons dj dit, leur porte est limite la dnition de la fonction concerne ; ils nentrent donc pas en conit avec dventuelles variables locales dautres fonctions (y compris main). Les paramtres fournis lors de lutilisation (lappel) de la fonction se nomment des paramtres effectifs. Comme le montrent nos prcdents exemples, on peut utiliser nimporte quelle expression comme paramtre effectif ; au bout du compte, cest la valeur de cette expression qui sera transmise la fonction lors de son appel. Notez quune telle libert naurait aucun sens dans le cas des paramtres formels : il serait impossible dcrire un en-tte de fexple sous la forme oat fexple (oat a+b, ), pas plus quen mathmatiques, vous ne dniriez une fonction f par f(x+y)=5 !

3.2 Linstruction return


Linstruction return peut mentionner, non seulement un nom de variable, mais en fait nimporte quelle expression. Par exemple, notre fonction cube du paragraphe 1 aurait pu tre dnie de faon plus brve :
float cube (float x) { return (x * x * x) ; } 144
ditions Eyrolles

/* les parenthses ne sont pas indispensables */

chapitre n 8

Les fonctions

Si le type de lexpression gurant dans return est diffrent du type du rsultat tel quil a t dclar dans len-tte, le compilateur nindiquera pas derreur ; il mettra automatiquement en place des instructions de conversion appropries, par exemple :
float f (...) { int n ; ..... return n ; }

/* la valeur de n sera convertie en float pour constituer le rsultat */

Le rle de linstruction return est double : dune part, il prcise la valeur qui sera fournie en rsultat, dautre part, il met n lexcution des instructions de la fonction. Jusquici, notre instruction return apparaissait comme la dernire de la dnition de notre fonction, mais il est thoriquement possible de placer plusieurs instructions return dans une mme fonction. Par exemple, une fonction dterminant le maximum de deux valeurs qui scrit ainsi avec une seule instruction return :
float max (float a, float b) { float m ; if (a>b) m = a ; else m = b ; return m ; }

pourrait scrire ainsi, si lon accepte dutiliser plusieurs instructions return :


float max (float a, float b) { if (a>b) return a ; else return b ; }

Lorsquune fonction ne fournit aucun rsultat :


s soit on ne place aucun return dans sa dnition ; s soit on utilise linstruction return sans la faire suivre dune expression ;

I l est toujours possible de ne pas utiliser le rsultat dune fonction, mme si elle en produit un.
Bien entendu, cela na dintrt que si la fonction fait autre chose que de calculer un rsultat.

3.3 Le cas des fonctions sans paramtres


Si une fonction ne possde aucun paramtre, son en-tte et, donc, sa dclaration (prototype) doivent comporter le mot void, la place de la liste des paramtres, par exemple :
int fexple1 (void)

ditions Eyrolles

145

Le livre du C Premier langage

est len-tte dune fonction ne recevant aucun paramtre et fournissant un rsultat de type entier. Sa dclaration (prototype) serait, bien sr :
int fexple1 (void) ;

Ici, elle est identique len-tte, au point-virgule prs, puisque la fonction na aucun paramtre. De mme :
void fexple2 (void)

est len-tte dune fonction ne recevant aucun paramtre et ne fournissant aucun rsultat. Son prototype serait simplement :
void fexple2 (void) ;

Lappel dune fonction sans paramtres doit quand mme comporter des parenthses vides.Par exemple, lappel de fexple1 scrira fexple1() et non simplement fexple1. ce propos, notez bien que ce nest pas parce quune fonction ne reoit aucun paramtre que son comportement ne peut pas diffrer dun appel un autre ; en effet, elle peut trs bien lire des informations en donnes, effectuer ce que lon nomme des calculs alatoires, ou encore utiliser des variables globales (que nous tudierons un peu plus loin).

VIII.4
a) crivez la dnition dune fonction, nomme bonjour, qui se contente dafcher le message bonjour chaque fois quon lappelle. b) crivez le prototype corrrespondant.

3.4 Le rle du prototype et son emplacement


Nous avons dj vu que linstruction de dclaration quest le prototype sert fournir au compilateur les informations ncessaires la traduction des diffrents appels de la fonction (sans quil dispose des instructions de dnition de cette fonction). Voici un premier exemple, assez naturel, o apparat une conversion du rsultat fourni par une fonction :
int fct (int) ; int n, p ; float y ; ..... p = fct (n) ; y = fct (n) ;

/* le rsultat fourni par fct est affect directement p */ /* le rsultat fournit par fct, de type int, doit tre converti en float avant dtre affect y */

146

ditions Eyrolles

chapitre n 8

Les fonctions

Mais, dune faon gnrale, le prototype permet de mettre en place dventuelles conversions des valeurs des paramtres transmettre la fonction, par exemple :
float fct (float) ; int n ; float y ; ..... y = fct (n) ;

/* cest le rsultat de la conversion de la valeur de n en float qui sera transmis en paramtre fct */

En ce qui concerne son emplacement, le prototype peut gurer :


s soit parmi les diffrentes dclarations situes au dbut dune fonction (y compris main) ; il

nest alors connu que dans ladite fonction ; ctait le cas dans les exemples rencontrs jusquici ;
s soit avant toutes les dnitions de fonctions, un niveau global (comme les variables glo-

bales dont nous parlerons un peu plus loin) ; dans ce cas, il est utilisable par toutes les fonctions du programme.

3.5 Initialisation des variables locales


Nous avons vu que les variables dclares dans une fonction, dites locales cette fonction, ne sont utilisables que depuis les instructions de la fonction. Ces variables locales peuvent tre initialises lors de leur dclaration. Dans ce cas, il faut savoir que la valeur indique est place dans la variable, non pas au moment de la compilation, mais chaque appel de la fonction. Par exemple, avec cette dnition :
void affiche (void) { int n = 10 ; printf (%d, n) ; n = n + 1 ; }

on obtiendra lafchage de la valeur 10, chaque appel de afche. Notez que les variables dclares dans le programme principal (fonction main) sont dans ce cas mais on ne sen rend pas vraiment compte, dans la mesure o, de par sa nature mme, la fonction main nest appele quune seule fois. Tout se passerait donc de la mme manire si ces variables taient effectivement initialises au moment de la compilation (cest dailleurs ce que nous vous avions dit dans le chapitre III, par souci de simplicit).

ditions Eyrolles

147

Le livre du C Premier langage

3.6 Une fonction peut en appeler une autre


Rien nempche quune fonction appelle, son tour, une autre fonction, comme dans ce canevas :
main() { int f1 (float) ; ..... f1 (...) ; ..... } int f1 (float) { void f2 (int) ; ..... f2 (...) ; ..... } /* prototype de f1 */ /* appel de f1 */

/* prototype de f2 */

Naturellement, il est ncessaire que les prototypes voulus soient connus aux endroits ncessaires (ici, les deux prototypes gurent au sein des uniques fonctions en ayant besoin ; ils auraient pu tre regroups avant la dnition du main). Notez cependant que, si les appels de fonctions peuvent ainsi simbriquer, il nen va pas de mme de leurs dnitions ; chaque dnition de fonction est totalement indpendante des autres dnitions de fonctions.

4. En langage C, les paramtres sont transmis par valeur


Jusquici, nous avons parl de transmission des paramtres effectifs une fonction, sans trop insister sur la manire dont cette transmission tait effectivement ralise. Lorsque vous crivez, par exemple :
m = max (5*n,p , 12) ;

il parat gnralement vident que la fonction max va recevoir les valeurs des expressions 5*n, p et 12. Mais les choses peuvent devenir plus subtiles, comme le montre lexemple suivant :
main() { void echange (int a, int b) ; int n=10, p=20 ; printf (avant appel : %d %d\n, n, p) ; echange (n, p) ; printf (aprs appel : %d %d, n, p) ; }

148

ditions Eyrolles

chapitre n 8

Les fonctions

void echange (int a, int b) { int c ; printf (dbut echange : %d %d\n, a, b) ; c = a ; a = b ; b = c ; printf (fin echange : %d %d\n, a, b) ; }

avant appel dbut echange fin echange aprs appel

: : : :

10 10 20 10

20 20 10 20

La fonction echange reoit deux valeurs correspondant ses deux paramtres muets a et b. Elle effectue un change de ces deux valeurs. Mais, lorsque lon est revenu dans le programme principal, aucune trace de cet change ne subsiste sur les paramtres effectifs n et p. En effet, lors de lappel de echange, il y a eu transmission de la valeur des expressions n et p. On peut dire que ces valeurs ont t recopies localement dans la fonction echange dans des emplacements nomms a et b. Cest effectivement sur ces copies qua travaill la fonction echange, de sorte que les valeurs des variables n et p nont, quant elles, pas t modies. Cest ce qui explique le rsultat constat. On traduit cela en disant quen langage C : les paramtres dune fonction sont toujours transmis par valeur Ce mode de transmission semble donc interdire a priori quune fonction produise une ou plusieurs valeurs en retour, autres que celle de la fonction elle-mme. Or, il ne faut pas oublier quen C tous les sous-programmes doivent tre crits sous forme de fonction. Autrement dit, ce simple problme dchange des valeurs de deux variables doit pouvoir se rsoudre laide dune fonction. Nous verrons que ce problme possde plusieurs solutions, savoir :
s Transmettre en paramtre la valeur de ladresse dune variable. La fonction recevra, cer-

tes, toujours une copie de cette adresse, mais elle pourra ventuellement agir sur ce qui se trouve cette adresse. Cest prcisment ce que nous faisons lorsque nous utilisons la fonction scanf. Nous examinerons cette technique en dtail dans le chapitre consacr aux pointeurs.
s Utiliser des variables globales, comme nous le verrons dans le prochain paragraphe ;

cette deuxime solution devra toutefois tre rserve des cas exceptionnels, compte tenu des risques quelle prsente.

ditions Eyrolles

149

Le livre du C Premier langage

5. Les variables globales


5.1 Notion de variable globale
En langage C, il est possible de dnir ce que lon nomme des variables globales. Il sagit de variables qui sont simultanment accessibles toutes les fonctions du programme (y compris le programme principal). Le terme de variable doit tre pris ici au sens gnral, cest--dire de variables scalaires ou de structures de donnes, telles que les tableaux. Voyez cet exemple de programme :
int nfois ; /* attention lemplacement de cette dclaration */ main () { void optimist (void) ; nfois = 2 ; optimist () ; nfois = 3 ; optimist () ; } void optimist (void) { int i ; /* i est locale la fonction optimist */ for (i=0 ; i<nfois ; i=i+1) printf (il fait beau\n) ; }

il il il il il

fait fait fait fait fait

beau beau beau beau beau

Vous constatez que la variable nfois a t dclare avant la dnition du programme principal et de la fonction optimist. Dans ces conditions, elle est accessible la fois au programme principal et la fonction optimist. Ainsi, le programme principal affecte nfois des valeurs qui se trouvent utilises par la fonction optimist. En revanche, la variable i est reste locale la fonction optimist. Elle nest pas accessible depuis le programme principal. Notez quici la fonction optimist se contente dutiliser la valeur de nfois mais rien ne lempcherait de la modier. Cest prcisment ce genre de remarque qui doit vous inciter nutiliser les variables globales que dans des cas limits. En effet, toute variable globale peut tre modie insidieusement par nimporte quelle fonction ; on parle dans ce cas deffets de bord.

150

ditions Eyrolles

chapitre n 8

Les fonctions

Lorsque vous aurez crire des fonctions susceptibles de modier la valeur de certaines variables, il sera beaucoup plus judicieux de prvoir den transmettre ladresse en paramtre (comme vous apprendrez le faire dans le prochain chapitre). En effet, dans ce cas, lappel de la fonction montrera explicitement quelle est la variable qui risque dtre modie et, de plus, ce sera la seule qui pourra ltre.

5.2 Une variable globale peut tre cache


Nous avons dit que les variables locales ou les paramtres muets avaient une porte limite la dnition de la fonction et quil nexistait donc pas de risques de conit. En revanche, les variables globales, de par leur nature mme, ont une porte qui stend lensemble du programme. Le langage C vous autorise utiliser un nom dj attribu une variable globale pour une variable locale ou un paramtre ; mais, dans ce cas, ce nom (local) cache la variable globale qui ne peut alors plus tre utilise. En gnral, ce genre de choses est dconseill. Voici un exemple qui vous prsente deux manires dont une variable globale peut tre cache (volontairement ou non !) au sein dune fonction ou du programme principal.
int n ; int p ; main () { int n ; /* ici, n correspond la principal tandis que p } void fct(float x, float p) { /* ici, n correspond la correspond au deuxime }

variable n locale au programme correspond la variable globale */

variable globale tandis que p paramtre formel de fct */

Remarques U ne variable globale peut, comme une variable locale, tre initialise lors de sa dclaration. Mais,
cette fois, lintialisation est ralise une seule fois, au moment de la compilation (alors que, rappelons-le, les variables locales sont initialises chaque appel de la fonction o elles apparaissent). De plus, une variable globale non initialise lors de sa dclaration est automatiquement initialise zro par le compilateur (alors que les variables locales non initialises sont indnies).

L es dclarations de variables ou de fonctions peuvent tre soit globales, soit locales. Une telle
distinction nexiste pas pour la directive #dene ; rappelons, en effet, quelle sapplique (avant la compilation proprement dite), tout le texte qui la suit, sans que la structure du programme nintervienne en diffrentes fonctions.

ditions Eyrolles

151

Le livre du C Premier langage

VIII.5

crivez une fonction sans paramtres ni valeur de retour, nomme init, plaant la valeur 1 dans les 10 lments dun tableau dentiers nomm t, dclar de faon globale. Peut-on utiliser la fonction init pour initialiser un autre tableau que t ?

6. Les fonctions prdnies


Le langage C dispose de beaucoup de fonctions toutes prtes, quon nomme souvent fonctions prdnies ou encore fonctions de la bibliothque standard. Parmi ces fonctions, nous avons dj utilis printf et scanf, sans toutefois insister sur le fait quil sagissait de fonctions. Nous avons galement brivement parl de fabs (fonction valeur absolue). Dune manire gnrale, toutes ces fonctions sont directement utilisables au sein dun programme, sans que vous ayez en fournir la dnition ; il suft simplement quelles aient t convenablement dclares. Pour ce faire, vous pouvez bien sr introduire leur prototype, comme vous le feriez pour une fonction que vous avez vous-mme crite. Mais il existe une dmarche plus simple ; en effet, tous les prototypes de ces fonctions prdnies sont placs dans un certain nombre de chiers quon nomme chiers en-tte (en anglais header). Plus prcisment, ces prototypes sont regroups par famille de fonctions ; par exemple, dans le chier en-tte de nom stdio.h (stdio est labrviation de STanDard Input Output), on trouve tous les prototypes des fonctions lies aux oprations dentres-sorties. Pour introduire les prototypes dun chier en-tte, il suft de mentionner son nom dans une instruction #include, par exemple :
#include <stdio.h>

On pourrait ventuellement placer une telle instruction au dbut de chaque fonction ayant besoin des prototypes correspondants. Cependant, il vaut mieux la placer avant toute dnition de fonction ou du programme principal (comme les variables globales) ; dans ce cas, en effet, les protoypes ainsi incorpors sont accessibles toutes les fonctions qui suivent. Bien entendu, pour utiliser une fonction prdnie, il est ncessaire de connatre le type de ses paramtres, celui de son ventuel rsultat et le nom du chier en-tte qui en contient le prototype. titre indicatif, voici quelques fonctions mathmatiques prdnies, dont les prototypes gurent dans math.h ; elles possdent toutes un seul paramtre ottant et elles fournissent un rsultat ottant : sin (sinus), cos (cosinus), tan (tangente), exp (exponentielle), log (logarithme nprien), log10 (logarithme base 10), sqrt (racine carre), fabs (valeur absolue).

152

ditions Eyrolles

chapitre n 8

Les fonctions

Labsence de dclaration dune fonction telle que printf ou scanf na gnralement pas dincidence sur la compilation du programme qui les utilise ; lexplication est assez technique et lie au fait que ces fonctions ont des paramtres dont le type, hormis le premier qui est le format, nest pas connu la compilation. Cest la raison pour laquelle nous avons pu nous passer jusqu maintenant de linstruction #include <stdio.h>. Il nen va pas de mme pour la plupart des autres fonctions.

7. Cas des tableaux une dimension transmis en paramtre dune fonction


Le langage C vous permet de transmettre un tableau en paramtre dune fonction. Dans ce cas, il faut savoir que le mcanisme de transmission de linformation associe permet la fonction dagir directement sur le tableau, cest--dire den modier les valeurs, ce qui peut paratre contraire ce que nous avons dit propos de la transmission par valeur. En fait, nous verrons plus tard que cette contradiction nest quapparente et lie la manire dont le langage C considre les noms de tableaux. Par ailleurs, ds lors quon sait raliser une fonction travaillant sur un tableau, on a fort envie de pouvoir utiliser la mme fonction pour des tableaux dont la taille peut varier dun appel un autre ; en fait, si cela ne pose gure de problmes pour des tableaux une dimension, il nen va plus tout fait de mme pour les autres. Nous tudierons donc sparment le cas des tableaux une dimension des tableaux plusieurs dimensions ; de plus, dans le premier cas, nous commencerons par le cas des fonctions prvues pour des tableaux ayant toujours la mme taille avant daborder le cas plus gnral des fonctions prvues pour des tableaux dont la taille peut changer dun appel un autre.

7.1 Cas des paramtres tableau une dimension de taille xe


a) Exemple
Considrons la situation suivante, dans laquelle :
s nous dnissons une fonction nomme raz, charge de placer des zros dans un tableau de

5 entiers quon lui fournit en paramtre :


void raz (int v[5]) { int i ; for (i=0 ; i<5 ; i=i+1) v[i] = 0 ; }

ditions Eyrolles

153

Le livre du C Premier langage

s nous lutilisons pour un tableau nomm t1. main() { ..... int t1[5] ; ..... void raz (int [5]) ; ..... raz (t1) ; }

/* prototype de la fonction raz */ /* appel de raz, laquelle on transmet en paramtre le tableau t1 */

Notez que le prototype de raz doit spcier le type de son paramtre ; ici, il sagit de int[5] ; cette notation peut surprendre mais, en fait, elle dcoule, comme dhabitude, de len-tte de la fonction, en liminant le nom du paramtre. Cette fois, lors de lappel de raz, il ny a pas recopie des valeurs de t1 au sein de la fonction ; le mcanisme utilis est tel que tout se passe comme si, au bout du compte, la fonction raz travaillait directement avec le tableau mentionn lors de lappel. Voici un exemple complet reprenant le canevas prcdent, montrant comment les valeurs du tableau ont bien t modies par la fonction :
main() { int i ; int t1[5] = { 1, 2, 3, 4, 5 } ; /* initialisation du tableau */ void raz (int [5]) ; /* prototype de la fonction raz */ printf (tableau t1 avant appel de raz : ) ; for (i=0 ; i<5 ; i=i+1) printf (%d , t1[i]) ; printf (\n) ; raz (t1) ; /* appel de raz, laquelle on transmet en paramtre le tableau t1 */ printf (tableau t1 aprs appel de raz : ) ; for (i=0 ; i<5 ; i=i+1) printf (%d , t1[i]) ; } void raz (int v[5]) { int i ; for (i=0 ; i<5 ; i=i+1) v[i] = 0 ; }

tableau t1 avant appel de raz : tableau t1 aprs appel de raz :

1 2 3 4 5 0 0 0 0 0

154

ditions Eyrolles

chapitre n 8

Les fonctions

b) Le mcanisme
Si lon sintresse de plus prs la manire dont le tableau est transmis en paramtre la fonction, il faut savoir que :
s pour le compilateur, un nom de tableau (par exemple t1) est identique son adresse, cest-

-dire ladresse de son premier lment (ici, &t1[0]) ;


s lappel raz (t1) provoque la transmission la fonction raz, de la valeur du paramtre t1,

cest--dire en fait de ladresse du tableau t1 (il y a bien toujours mcanisme de transmission par valeur, mais cette valeur se trouve tre celle dune adresse) ;
s dans la fonction raz, chaque appel, le symbole t est remplac par sa valeur, cest--dire

en loccurrence ladresse reue en paramtre ; ainsi, une affectation telle que :


v[i] = 0 ; s est traduite en : affecter au i-me entier, partir de ladresse v, la valeur 0.

En dnitive, on voit bien quil ny a quun seul mcanisme de transmission des paramtres, savoir par recopie de la valeur au sein de la fonction.

c) Variantes possibles dans len-tte et le prototype de la fonction


En fait, au sein de la fonction raz, le compilateur na pas rserver de place pour le paramtre tableau quest v ; en effet :
s dune part, le tableau sur lequel travaillera effectivement la fonction aura vu son emplace-

ment rserv au sein du programme principal (ou dune autre fonction appelant raz),
s dautre part, il nest pas prvu de recopier les valeurs dudit tableau au sein de la fonction raz.

En fait, au sein de la fonction raz, le compilateur doit simplement tre en mesure de localiser correctement lemplacement de v[i] ; pour ce faire, il na nullement besoin de connatre la dimension exacte du tableau ; il lui suft de savoir quil sagit dun tableau dentiers. Cest la raison pour laquelle vous ntes pas oblig, dans un en-tte ou un prototype de fonction, de prciser la taille des tableaux une dimension. Notre fonction prcdente pourrait tout aussi bien scrire :
void raz (int v[]) { int i ; for (i=0 ; i<5 ; i=i+1) v[i] = 0 ; }

et son prototype pourrait tre lun des suivants :


void raz (int []) ; void raz (int [5] ) ;

ditions Eyrolles

155

Le livre du C Premier langage

7.2 Cas des paramtres tableau une dimension de taille variable


Compte tenu du mcanisme utilis pour la transmission des tableaux en paramtre, vous voyez quune fonction peut, sans problme, traiter des tableaux de taille quelconque, pour peu quelle connaisse effectivement cette taille ; la meilleure faon dy parvenir tant de transmettre galement cette taille en paramtre. Voici comment adapter notre fonction raz prcdente dans ce sens :
void raz (int v[], int nelem) { int i ; for (i=0 ; i<nelem ; i=i+1) v[i] = 0 ; }

Et voici quelques exemples dutilisation (notez, dans le prototype de raz, le type int [ ] qui correspond un tableau une dimension dentiers ; l encore, il dcoule de len-tte correspondant en liminant de int v[ ], le nom de tableau v) :
main() { int t1[10], t2[15], t3[100] ; void raz (int [], int) ; /* ..... raz (t1, 10) ; /* mise raz (t2, 15) ; /* mise raz (t3, 100) ; /* mise ..... }

prototype de raz */ zro des 10 lments de t1 */ zro des 15 lments de t2 */ zro des 100 lments de t3 */

Remarques R ien ne vous interdit, par mgarde, de demander raz de traiter plus dlments que vous nen
avez rservs, comme dans : int t_petit [5] ; raz (t_petit, 200) ; dans ce cas, il y aura crasement de 195 (200-5) valeurs situes au-del du tableau t_petit ; on retrouve l les risques habituels inhrents au dbordement dindice.

R ien ne vous empche de ne traiter quune partie des lments dun tableau, en crivant, par exemple :
raz (t1, 4) ;/* mise zro des 4 premiers lments de t1 */

R ien ne vous empche dcrire :


raz (&t1[0], 10) ; au lieu de : raz (t1, 10) ;

156

ditions Eyrolles

chapitre n 8

Les fonctions

Qui plus est, il serait possible de procder ainsi pour mettre zro 5 lments conscutifs de t1, partir du troisime (dindice 2) : raz (&t1[2], 5) ; /* mise zro de 5 lments de t1, partir du troisime */

N otre fonction raz avait deux particularits : elle ne fournissait pas de rsultat et elle modiait les
valeurs du tableau reu en paramtre. Lexercice suivant vous propose une situation diffrente.

VIII.6

crivez une fonction nomme max_tab fournissant en rsultat la valeur maximale dun tableau dentiers ; on prvoira en paramtres la fois le tableau concern et son nombre dlments. crivez un petit programme principal utilisant cette fonction.

8. Cas des tableaux deux dimensions transmis en paramtres dune fonction


Voici un exemple de dnition dune fonction qui place la valeur 1 dans chacun des lments dun tableau deux dimensions de 20 lments (5 fois 4) :
raun (int t[5][4]) { int i, j ; for (i=0 ; i<5 ; i=i+1) for (j=0 ; j<4 ; j=j+1) t[i][j] = 1 ; }

Voici quelques exemples dutilisation de cette fonction :


main () { int tab [5] [4] ; int truc [5] [4] ; ..... raun (tab) ; raun (truc) ; }

Bien entendu, le mcanisme dcrit prcdemment pour les tableaux une dimension sapplique toujours ici : la fonction raun recevra, en fait, ladresse de dbut dun tableau ; les deux appels pourraient tre remplacs par :
raun (&tab[0][0]) ; raun (&truc[0][0]) ;

ditions Eyrolles

157

Le livre du C Premier langage

Mais si, pour localiser tous les lments dun tableau une dimension, il suft au compilateur de connatre son adresse et son type, pour un tableau deux dimensions, en revanche, les choses sont moins simples. En effet, il faut tenir compte de la manire dont ses lments sont rangs en mmoire, savoir en faisant se succder ses diffrentes lignes ; par exemple, pour un tableau dclar ainsi :
float t[5] [3] ;/* 5 lignes de 3 lments */

les lments se succderont ainsi, partir de ladresse de dbut du tableau :


t[0][0] t[0][1] t[0][2] t[1][0] t[1][1] t[1][2] .... t[4][0] t[4][1] t[4][2]

Ainsi, pour localiser convenablement un lment donn, il faudra connatre, en plus de ladresse de dbut du tableau, le nombre dlments dune ligne (le nombre dlments des colonnes tant, en revanche, inutile). Dans ces conditions, on voit que len-tte de raun pourra ventuellement ne pas prciser le nombre de lignes :
raun (int t[][3])

En revanche, il nest pas possible de lcrire :


raun (int t[][])/* en-tte incorrect */

On voit que si lon souhaite raliser une fonction traitant des tableaux deux dimensions de taille variable, les choses ne resteront possibles que si seule la premire dimension est susceptible de varier, la seconde restant xe ; cela enlve manifestement beaucoup dintrt la chose. On peut toutefois traiter une telle situation en considrant un tableau deux dimensions comme un tableau une seule dimension et en effectuant soi-mme les calculs dadresse permettant den localiser les diffrents lments ; nous ninsisterons pas davantage sur ces techniques qui sortent du cadre de cet ouvrage.

158

ditions Eyrolles

chapitre n 8

Les fonctions

Rsum
Une fonction est un ensemble dinstructions, dot dun nom et pouvant ventuellement comporter des paramtres. On peut lappeler (en provoquer lexcution) depuis une autre fonction (ventuellement de la fonction main correspondant au programme principal) en citant son nom, suivi, entre parenthses, dune liste de paramtres, condition davoir pralablement dclar cette fonction par un prototype (qui peut gurer, soit un niveau local, soit un niveau global). Il existe deux grandes classes de fonctions : s celles qui ne renvoient pas de rsultat (fonction action) ; leur appel ne peut se faire que sous forme dune instruction simple comme : fct (n, a) ; s celles qui fournissent un rsultat ; leur appel peut se faire, soit comme prcdemment (leur rsultat nest alors pas utilis), soit au sein dune expression arithmtique, comme dans : z = fct(n, x) * 2 + s ; Dans la dnition dune fonction : s Len-tte prcise son nom, le type des paramtres (dits alors muets) et le type de sa valeur de retour ; on utilise le mot void pour indiquer labsence de paramtres ou de valeur de retour. s On peut dclarer des variables qui sont alors dites locales la fonction, utilisables uniquement dans les instructions de la fonction. Dans lutilisation dune fonction, les paramtres mentionns (dits alors effectifs) peuvent ltre sous la forme dune expression quelconque. Cest leur valeur qui est transmise la fonction. Dans un programme (ensemble de fonctions), on peut fournir les fonctions dans lordre de son choix ; en particulier, la fonction main ne gure pas obligatoirement en premier. On peut dnir des variables globales, en les dclarant avant toute fonction, y compris la fonction main. Ces variables sont accessibles toute fonction qui peut ventuellement en modier la valeur. Il existe un grand nombre de fonctions prdnies quon peut utiliser en incorporant le prototype laide dune instruction #include approprie. Lorsquun tableau est mentionn en paramtre effectif, cest en fait la valeur de son adresse qui est transmise la fonction. Cette dernire peut alors, contrairement ce qui se passe pour les variables scalaires, modier le contenu de ce tableau. Dans le cas dun tableau une dimension mentionn en paramtre muet, la dimension est sans importance et elle peut ventuellement tre omise de len-tte et du prototype. Il est facile de raliser une fonction travaillant avec des tableaux une dimension dont le nombre dlments peut diffrer dun appel un autre. Dans le cas dun tableau deux dimensions indiqu en paramtre muet, seule la premire dimension est sans importance et peut, ventuellement, tre omise de len-tte et du prototype. Si lon souhaite quune fonction modie la valeur dune variable scalaire, il faudra utiliser un pointeur, ce que nous apprendrons faire dans le prochain chapitre.

ditions Eyrolles

159

You might also like