2. Instructions
Le langage C est un langage structuré.
Un programme écrit en C est composé d'une suite d'instructions pouvant être une instruction simple, une bloc d'instructions, une répétitive ou une alternative. Le C permet également l'écriture de fonctions.
Les instructions simples
-
Le C accepte l'usage de l'instruction nulle (ou instruction vide), marquée par le caractère ';', le terminateur d'instructions.
; /* instruction nulle */ -
Il permet également l'utilisation de l'instruction-expression, qui évalue l'expression pour ses effets de bord.
20 + 2*b; /* instruction sans effet de bord: évaluation puis oubli du résultat */a = 20 + 2*b; /* instruction avec effet de bord: évaluation puis affectation du résultat à une variable */Nous rencontrerons essentiellement trois cas d'utilisation :
-
Les affectations
a = b * c; -
Les pré ou post incrémentations et décrémentations
a++; a--; ++a; --a; -
Et les appels de fonction de type void ou dont la valeur de retour n'est pas exploitée
printf(...);
-
-
break : instruction qui permet de quitter une branche du switch ou de sortir d'une répétitive directement, sans réévaluer la condition.
-
continue : instruction utilisée uniquement dans les répétitives, elle permet de sauter la suite du corps de la répétitive et de passer directement à une nouvelle vérification de la condition; dans un for, elle permet l'exécution de l'adaptation.
Le bloc d'instructions
Lorsque plusieurs instructions doivent être traitées conjointement, elles sont regroupées en un bloc d'instructions encadrés par une paire d'accolades '{' et '}'.
{// instr1// instr2...}
Du point de vue de la syntaxe, un bloc se comporte comme une instruction unique et peut figurer en tout endroit où une instruction simple est permise. Il n'est pas suivi par le ';' car les accolades servent de délimiteurs.
Un bloc d'instructions peut débuter par la définition d'un certain nombres de variables, locales à ce bloc d'instructions (i.e. leur existence se termine à la fin du bloc).
{// définition de variables// liste d'instructions}
Attention: La norme ANSI-C (C90) oblige les variables à être toujours déclarées avant la première instruction du bloc. Cette contrainte a été levée depuis la norme C99. Cependant le mélange de déclarations et d'instructions n'est pas recommandé afin de préserver la lisibilité du code.
Si un identificateur redéfinit un variable déjà existante, la nouvelle variable occulte l'ancienne, définie dans un bloc englobant. Dans l'exemple suivant,
{// bloc englobantint i = 5;{// sous-blocint i = 7;printf("i vaut %d\n", i++);}printf("i vaut %d\n", i);}
l'affichage donnera:
i vaut 7 i vaut 5
Notez que les instructions imbriquées (que ce soit dans un bloc ou dans une structure de contrôle de type alternative ou répétitive) sont indentées à l’aide de tabulations ou d'espaces, de façon homogène dans tous les fichiers sources d'un projet. Cette convention d'écriture facilite la maintenance et la relecture des sources.
Les répétitives
Le langage C connaît différents type de répétitives: le while, le for et le do ... while. Toutes ces répétitives utilisent une condition de continuation, c-à-d que l'itération suivante est réalisée si la condition testée vaut VRAI. En d'autres mots, la boucle se termine lorsque la condition devient FAUX.
while
Cette répétitive est utilisée lorsque l'on désire répéter plusieurs fois une instruction (ou un bloc d'instructions). La condition est vérifiée avant d'entamer l'itération et donc si la condition est directement FAUX, l'instruction n'est pas exécutée.
while (cdt)// instruction
Ou
while (cdt) {// instruction1// instruction2}
Pour éviter toute ambiguité, une répétitive sans instruction s'écrira comme suit (en utilisant une instruction nulle):
while (cdt);
Dans l'exemple suivant, nous effectuons une lecture sur l'entrée standard stdin et quittons la répétitive lorsque l'utilisateur introduit la fin des données.
char ligne[256];while (fgets(ligne, 256, stdin) != NULL) {printf("%s", ligne);}
do ... while
Cet autre type de répétitive s'emploie très rarement, uniquement lorsque l'on est certain que la répétitive doit s'exécuter au moins une fois. Pour éviter toute confusion avec le while, il est recommandé de respecter les conventions suivantes :
-
On mettra toujours les instructions entre accolades, même si il n'y a qu'une seule instruction dans le bloc.
-
L'accolade fermante sera écrite sur la même ligne que le while.
-
On placera toujours le point virgule directement après la condition, sur la même ligne, pour éviter toute confusion avec le while instruction nulle.
do {// instruction} while (cdt);
for
Le for est une écriture condensée du while. Il est constitué de 3 parties : l'initialisation, la condition et l'adaptation.
L'initialisation est exécutée à l'entame de la répétitive, une seule fois au début de l'exécution de la répétitive; si plusieurs expressions doivent composer cette initialisation, elles sont séparées par une virgule ','.
La condition est une condition de continuation, elle est exécutée une première fois juste après l'initialisation (et donc le corps de la répétitive peut ne pas etre exécuté) et à chaque nouvelle répétition de la boucle.
L'adaptation est exécutée à la fin de chaque itération, juste avant une nouvelle évaluation de la condition.
for (int i = 0; i < 5; i++) {printf("%d\n", i);}
Bien que l'ANSI-C l'interdise, la définition de variables locales dans l'initialisation d'un for est autorisée dans les normes suivantes. Cette fonctionnalité entraîne la possibilité de créer une variable de boucle temporaire pour l'instruction for:
{for (int i = 0; i < 10; i++){// variable i utilisable uniquement dans la boucle for}// variable i inexistante}
Ce code restreint l'utilisation de la variable compteur au corps de la boucle for, contrairement à l'écriture:
{int i;for (i = 0; i < 10; i++){// variable i utilisable}// variable i utilisable}// variable i inexistante
Remarque: D'un point de vue algorithmique, il est interdit de modifier la variable compteur ('i' dans l'exemple précédent) dans le corps d'une boucle for car cette structure de contrôle applique automatiquement l'adaptation du compteur (ici l'incrémentation i++) à chaque tour de boucle.
Remarque: Une boucle for peut s'écrire de manière parfaitement équivalente à l'aide d'une boucle while. Ainsi, si l'on reprend la boucle for du dernier exemple:
int i = 0; // initialisationwhile (i<5) { // condition de continuationprintf("%d\n", i);i++; // adaptation}
Dans ce cas, quand doit-on utiliser un for et quand doit-on utiliser un while? Simple: on utilise la boucle for quand on connaît à l'avance le nombre de répétitions de la boucle et on utilise le plus souvent la boucle while quand on ne sait pas combien de fois la boucle va être exécutée.
Les alternatives
C connaît deux types de traitements conditionnels: les alternatives, qui permettent de réaliser ou pas un traitement en fonction d'une condition, et les switch, qui réalisent un traitement en fonction du contenu d'une variable dénombrable (entière).
if
Plusieurs syntaxes sont possibles pour marquer une alternative: soit l'alternative simple
if (cdt)// instruction
soit la forme avec accolades (indispensables si le bloc est composé de plusieurs instructions ; optionnelles si le bloc est composé d'une instruction simple)
if (cdt) {// instruction}
et les if else
if (cdt) {// instruction1} else {// instruction2}
Attention au problème du dangling else: le else se rapporte toujours au dernier if libre qui le précède et dans le même bloc (quelle que soit l'indentation utilisée par le programmeur).
if (a>b) {if (c>d) {if (c>0)c++;} elsed++;} elsea++;
switch
Le switch peut être considéré comme un branchement multiple en fonction d'une valeur entière. Dès que l'on est aiguillé vers une branche, le traitement se poursuit en séquence. Cela signifie que toutes les instructions qui suivent le case sont exécutées, jusqu'à la fin du bloc ou jusqu'à une instruction de rupture break qui permet de quitter la structure de contôle.
switch (exp) {case val1 :case val2 :... // instructions exécutées si exp = val1 ou val2break;case val3 :... // instructions exécutées si exp = val3case val4 :... // instructions exécutées si exp = val4 ou val3break;default :... // instructions exécutées si exp <> val1, val2, val3, val4}
Remarque: Si l'expression du switch ne vaut aucune des valeurs case ou qu'aucun break n'a été exécuté, c'est la clause default qui sera exécutée.
Remarquez qu'une instruction switch classique, telle que:
switch (exp) {case val1 :// instructions1break;case val2 :// instructions2break;case val3 :// instructions3break;default :// instructions4}
peut être exprimée à l'aide d'instructions if imbriquées:
if (exp == val1) {// instructions1} else if (exp == val2) {// instructions2} else if (exp == val3) {// instructions3} else {// instructions4}
Notez que pareille imbrication ne nécessite pas d'augmenter l'indentation de chaque nouveau if. Cette convention d'écriture exprime simplement le fait qu'un seul traitement du branchement conditionnel sera exécuté. Tous les traitements sont donc écrits sur le même niveau d'indentation. Bien que ces deux écritures peuvent être considérées comme équivalentes, l'utilisation d'un switch sera préférée car plus simple et plus lisible.