• Aucun résultat trouvé

Le fonctionnement de cette instruction est le suivant : expression est évaluée ;

exploitation de la donnée n ...

}

Lorsque l'abandon de la boucle correspond aussi à l'abandon de la procédure courante, break est avanta- geusement remplacée par return comme dans :

char *adresse_de_fin(char *ch) {

/* renvoie adresse du caractère qui suit la chaine ch */

for (;;)

if (*ch++ == 0) return ch;

}

III-B-7 - Instruction switch

Format :

switch ( expression ) corps

Le corps de l'instruction switch prend la forme d'un bloc f...g renfermant une suite d'instructions entre lesquelles se trouvent des constructions de la forme

case expression-constante :

ou bien

default :

Le fonctionnement de cette instruction est le suivant : expression est évaluée ;

• s'il existe un énoncé case avec une constante qui égale la valeur de expression, le controle est transféré à l'instruction qui suit cet énoncé ;

• si un tel case n'existe pas, et si l'énoncé default existe, alors le controle est transféré à l'instruction qui suit l'énoncé default ;

• si la valeur de expression ne correspond à aucun case et s'il n'y a pas d'énoncé default, alors aucune instruction n'est exécutée.

Attention. Lorsqu'il y a branchement réussi à un énoncé case, toutes les instructions qui le suivent sont exécutées, jusqu'à la fin du bloc ou jusqu'à une instruction de rupture (break). Autrement dit, l'instruction switch s'apparente beaucoup plus à une sorte de goto

« paramétré » (par la valeur de l'expression) qu'à l'instruction case...of... de Pascal.

Exemple (idiot) :

Si on suppose que i ne peut prendre que les valeurs 0 ... 3, alors l'instruction ci-dessus a le même effet que l'affectation j = i.

Pour obtenir un comportement similaire à celui du case...of... de Pascal, on doit utiliser l'instruction break, comme dans l'exemple suivant, dans lequel on compte la fréquence de chaque chiffre et des caractères blancs dans un texte :

nb_blancs = nb_autres = 0;

III-B-8 - Instructions break et continue

Formats :

break;

continue;

Dans la portée d'une structure de controle (instructions for, while, do et switch), l'instruction break provoque l'abandon de la structure et le passage à l'instruction écrite immédiatement derrière. L'utilisation de break ailleurs qu'à l'intérieur d'une de ces quatre instructions constitue une erreur (que le compilateur signale).

Par exemple la construction

for (expr1 ; expr2 ; expr3) { ...

break;

...

}

équivaut à

{

for (expr1 ; expr2 ; expr3) { ...

goto sortie;

...

}

sortie: ; }

L'instruction continue est moins souvent utilisée. Dans la portée d'une structure de controle de type boucle (while, do ou for), elle produit l'abandon de l'itération courante et, le cas échéant, le démarrage de l'itération suivante. Elle agit comme si les instructions qui se trouvent entre l'instruction continue et la fin du corps de la boucle avaient été supprimées pour l'itération en cours : l'exécution continue par le test de la boucle (précédé, dans le cas de for, par l'exécution de l'expression d'incrémentation).

Lorsque plusieurs boucles sont imbriquées, c'est la plus profonde qui est concernée par les instructions break et continue. Dans ce cas, l'emploi de ces instructions ne nous semble pas ¾uvrer pour la clarté des programmes.

III-B-9 - Instruction return

Formats :

return expression ;

et

return ;

Dans un cas comme dans l'autre l'instruction return provoque l'abandon de la fonction en cours et le retour à la fonction appelante.

Dans la première forme expression est évaluée ; son résultat est la valeur que la fonction renvoie à la fonction appelante ; c'est donc la valeur que l'appel de la fonction représente dans l'expression ou il figure. Si nécessaire, la valeur de expression est convertie dans le type de la fonction (déclaré dans l'en-tête), les conversions autorisées étant les mêmes que celles faites à l'occasion d'une affectation.

Dans la deuxième forme, la valeur retournée par la fonction reste indéterminée. On suppose dans ce cas que la fonction appelante n'utilise pas cette valeur ; il est donc prudent de déclarer void cette fonction. Absence d'instruction return dans une fonction. Lorsque la dernière instruction (dans l'ordre de l'exécution) d'une fonction est terminée, le controle est rendu également à la procédure appelante. Tout se passe comme si l'accolade fermante qui termine le corps de la fonction était en réalité écrite sous la forme

...

return;

}

IV - Fonctions

Beaucoup de langages distinguent deux sortes de sous-programmes 24 : les fonctions et les procédures. L'appel d'une fonction est une expression, tandis que l'appel d'une procédure est une instruction. Ou, si on préfère, l'appel d'une fonction renvoie un résultat, alors que l'appel d'une procédure ne renvoie rien.

En C on retrouve ces deux manières d'appeler les sous-programmes, mais du point de la syntaxe le langage ne connait que les fonctions. Autrement dit, un sous-programme est toujours supposé renvoyer une valeur, même lorsque celle-ci n'a pas de sens ou n'a pas été spécifiée. C'est pourquoi on ne parlera ici que de fonctions. C'est dans la syntaxe et la sémantique de la définition et de l'appel des fonctions que résident les principales différences entre le C original et le C ANSI. Nous expliquons principalement le C ANSI, rassemblant dans une section spécifique (cf.

section 4.2) la manière de faire du C original.

*

24La notion de sous-programme (comme les procedures de Pascal, les subroutines de Fortran, etc.) est supposée ici connue du lecteur.

IV-A - Syntaxe ANSI ou \avec prototype"

IV-A-1 - Définition

Une fonction se définit par la construction :

typeopt ident ( déclaration-un-ident , ... déclaration-un-ident ) instruction-bloc

Notez qu'il n'y a pas de point-virgule derrière le \)" de la première ligne. La présence d'un point-virgule à cet endroit provoquerait des erreurs bien bizarres, car la définition serait prise pour une déclaration (cf. section 4.1.4).

La syntaxe indiquée ici est incomplète ; elle ne convient qu'aux fonctions dont le type est défini simplement, par un identificateur. On verra ultérieurement (cf. section 5.4.1) la manière de déclarer des fonctions rendant un résultat d'un type plus complexe.

La première ligne de la définition d'une fonction s'appelle l'en-tête, ou parfois le prototype, de la fonction. Chaque formule déclaration-un-ident possède la même syntaxe qu'une déclaration de variable 25.

Exemple.

int extract(char *dest, char *srce, int combien) {

/* copie dans dest les combien premiers caractères de srce */

/* renvoie le nombre de caractères effectivement copiés */

int compteur;

for (compteur = 0; compteur < combien && *srce != '\0'; compteur++) *dest++ = *srce++;

*dest = '\0';

return compteur;

}

Contrairement à d'autres langages, en C on ne peut pas définir une fonction à l'intérieur d'une autre : toutes les fonctions sont au même niveau, c'est-à-dire globales. C'est le cas notamment de main, qui est une fonction comme les autres ayant pour seule particularité un nom convenu.

*

25Restriction : on n'a pas le droit de « mettre en facteur » un type commun à plusieurs arguments. Ainsi, l'en-tête de la fonction extract donnée en exemple ne peut pas s'écrire sous la forme char *extract(char *dest, *srce, int n).

IV-A-2 - Type de la fonction et des arguments

L'en-tête de la fonction définit le type des objets qu'elle renvoie. Ce peut être :

• tout type numérique ;

• tout type pointeur ;

• tout type struct ou union.

Si le type de la fonction n'est pas indiqué, le compilateur suppose qu'il s'agit d'une fonction à résultat entier. Ainsi, l'exemple précédent aurait aussi pu être écrit de manière équivalente (mais cette pratique n'est pas conseillée) :

extract(char *dest, char *srce, int combien) etc.

Fonctions sans résultat. Lorsqu'une fonction ne renvoie pas une valeur, c'est-à-dire lorsqu'elle corres- pond plus à une procédure qu'à une vraie fonction, il est prudent de la déclarer comme rendant un objet de type voidvoid. Ce type est garanti incompatible avec tous les autres types : une tentative d'utilisation du résultat de la fonction provoquera donc une erreur à la compilation. Le programmeur se trouve ainsi à l'abri d'une utilisation intempestive du résultat de la fonction. Exemple :

void extract(char *dest, char *srce, int combien) {

/* copie dans dest les combien premiers caractères de srce */

/* maintenant cette fonction ne renvoie rien */

int compteur;

for (compteur = 0; compteur < combien && *srce != '\0'; compteur++) *dest++ = *srce++;

*dest = '\0';

}

Fonctions sans arguments. Lorsqu'une fonction n'a pas d'arguments, sa définition prend la forme

typeopt ident ( void ) instruction-bloc

On notera que, sauf cas exceptionnel, on ne doit pas écrire une paire de parenthèses vide dans la déclaration ou la définition d'une fonction. En effet, un en-tête de la forme

type ident()

ne signifie pas que la fonction n'a pas d'arguments, mais (cf. section 4.2.2) que le programmeur ne souhaite pas que ses appels soient controlés par le compilateur.

IV-A-3 - Appel des fonctions

L'appel d'une fonction se fait en écrivant son nom, suivi d'une paire de parenthèses contenant éventuellement une liste d'arguments effectifs.

Notez bien que les parenthèses doivent toujours apparaitre, même si la liste d'arguments est vide : si un nom de fonction apparait dans une expression sans les parenthèses, alors il a la valeur d'une constante adresse (l'adresse de la fonction) et aucun appel de la fonction n'est effectué.

Passage des arguments. En C, le passage des arguments se fait par valeur. Cela signifie que les arguments formels 26 de la fonction représentent d'authentiques variables locales initialisées, lors de l'appel de la fonction, par les valeurs des arguments effectifs 27 correspondants.

Supposons qu'une fonction ait été déclarée ainsi

type fonction ( type1 arg formel1 , ... typek arg formelk ) etc.

alors, lors d'un appel tel que

fonction ( arg effectif 1 , ... arg effectif k )

la transmission des valeurs des arguments se fait comme si on exécutait les affectations :

arg formel1 = arg effectif 1 ...

arg formelk = arg effectif k