Les contrôles des blocs d'instructions
Blocs d’instructions et conditions
1. Qu’est-ce qu’un bloc d’instructions ?
a. Définition
-
Un bloc d’instructions est UNE instruction composée de plusieurs instructions qui se suivent.
-
En C (et tous les langages dérivés) il est délimité avec les opérateurs { } (accolades ouvrante et fermante).
-
Un bloc peut contenir d’autres blocs imbriqués.
-
Dans un fichier source, il ne peut pas y avoir d’instruction en dehors d’un bloc (sauf directives macro-processeur et déclarations de variables globales ou de fonctions). Pour être valides, toutes les instructions doivent être localisées dans un bloc. Le bloc au sommet est celui de la fonction main() qui réunit in fine toutes les instructions du programme.
b. Exemple
#include <stdio.h>
#include <stdlib.h>
int main()
{ //------------------------- ouv bloc main B1
int x,pasx; // déclarations de variables locales au bloc main
// elles sont visibles (accessibles) dans ce bloc
// et tous les sous-blocs
{ //----------ouv B2
int c;
x=0;
c=rand()%256;
pasx=rand()%5;
} // ---------ferm B2
x=640;
{ // ---------ouv B3
//c=10; // provoque erreur, c non visible dans ce bloc
x/=2;
pasx=15;
} // ---------ferm B3
x+=pasx;
printf("x vaut : %d\n",x); // affichage ?
Return 0;
} //-------------------------ferm bloc main
Les blocs posent la question de la visibilité des variables. En fait la durée de vie des variables en générale est associée au bloc dans lequel elles sont déclarées. C’est pourquoi elles sont visibles dans tous les sous-blocs imbriqués et invisibles en revanche dans les blocs de niveau supérieur ou de même niveau mais séparés.
c. Utilité d’un bloc d’instructions
Dans l’exemple ci-dessus les blocs sont inutiles : ils n’influent en rien...
Sauts conditionnels
1. L’instruction if
SI et seulement si l’expression est vraie ALORS le bloc des instructions associées au if est exécuté. Il ne peut y avoir qu’un seul bloc associé au if.
if ( expression1 vraie){
bloc instructions;
}
Par exemple, soit une variable a, dans un programme on peut écrire :
if ( a >=100 ){ // test
printf( "a est supérieur ou égal à 100 \n"); // instruction
}
Derrière un if il ne peut y avoir qu’un seul bloc c’est-à-dire une seule instruction. S’il y a plusieurs instructions il faut ouvrir et fermer le bloc mais s’il n’y en a qu’une, c’est inutile. L’exemple précédent peut s’écrire sans erreur à la compilation :
if ( a >=100 )
printf( "a est supérieur ou égal à 100 \n");
Attention à l’indentation, c’est inutile pour la machine, en revanche c’est absolument nécessaire professionnellement pour rendre son code lisible.
Dans le cas d’une succession de if, chacun fait l’objet d’une évaluation :
if ( expression1 vraie){
bloc instructions 1;
}
if ( expression2 vraie){
bloc instructions 2;
}
if ( expression3 vraie){
bloc instructions 3;
}
Seuls les blocs dont la condition est vraie seront effectués : aucun, quelques-uns ou tous.
Des if peuvent être imbriqués, dans ce cas ils ne seront peut-être pas tous effectués, la série s’arrêtera au premier test faux rencontré :
if ( expression1 vraie){
bloc instructions 1;
if ( expression2 vraie){
bloc instructions 2;
if ( expression3 vraie){
bloc instructions 3;
}
}
}
Si (et seulement si) expression1 est vraie, alors les instructions 1 du bloc sont exécutées et expression2 est évaluée.
Si (et seulement si) expression2 est vraie, alors les instructions 2 du bloc sont exécutées et expression3 est évaluée.
Si (et seulement si) expression3 est vraie, alors les instructions 3 du bloc sont exécutées.
2. Le couple d’instructions if-else
SI l’expression est vraie, le bloc des instructions associées...
Branchements
1. Branchement sélectif : switch, case et break
Une suite de if, else if, else comme :
if (i==0){
instructions0;
}
else if (i==1){
instructions1;
}
else if (i==7){
instructions7;
}
else if (i==55){
instructions55;
}
else{
instructions_n;
}
peut être remplacée par un switch qui est un aiguillage. Il fonctionne de la façon suivante :
switch(valeur_expression){
case expression_constante_1 :
instructions1;
break;
case expression_constante_2 :
instructions2;
break;
case expression_constante_3 :
instructions3;
break;
default :
instructions_n;
break;
}
Le bloc des instructions à exécuter est décidé à partir de la valeur de l’expression en paramètre du switch :
-
Si ce paramètre vaut expression_constante_1, instructions1 sont exécutées.
-
Si ce paramètre vaut expression_constante_2, instructions2 sont exécutées.
-
Si ce paramètre vaut expression_constante_3, instructions3 sont exécutées.
-
Si ce paramètre a une autre valeur que les différents cas proposés, instructions_n sont exécutées.
Il n’y a pas de limite au nombre de cas possibles. Mais chaque cas est identifié par une valeur constante, c’est-à-dire non variable après le mot-clé case.
L’instruction break provoque une sortie du bloc du switch. Sans le break, toutes les instructions des cas qui suivent le cas d’entrée sont exécutées.
Notre suite de if, if else peut s’écrire :
switch( i ){
case 0 :
instructions0;
break;
case 7 :
instructions7;
break;
case 55 :
instructions55;
break;
default :
instructions_n;
}
Selon la valeur de la variable i les instructions du cas correspondant sont exécutées jusqu’au break. Si i ne correspond à aucun des cas, alors c’est le cas par défaut qui est exécuté et s’il n’y a pas de cas par défaut, aucune instruction n’est exécutée....
Les tests multiconditions (ET/OU)
1. Conjonction ET : opérateur &&
a. ET avec deux expressions membres
Soient deux expressions, E1, E2.
l'expression E1 && E2 :
est vraie (vaut 1) si E1 ET E2 sont vraies
est fausse sinon (vaut 0)
Exemple :
int a = rand()%300;
if (a >= 100 && a<=200)
printf("a compris entre 100 et 200\n");
Le test vaut 1 si la valeur de a est comprise dans la fourchette 100-200 bornes comprises, 0 sinon.
Autre exemple, à la douane :
#include <stdio.h>
#include <stdlib.h>
int main()
{
int papier, declarer;
printf("Vous avez vos papiers ? (o/n)\n");
scanf("%c",&papier);
// lorsqu'il y a plusieurs appels successifs de scanf() il est
// nécessaire de réinitialiser le buffer d'entrée (stdin) avec
// la fonction rewind()
rewind(stdin);
printf("Quelque chose a declarer ? (o/n)\n");
scanf("%c",&declarer);
if( papier=='o' && declarer=='n')
printf ("C'est bon, vous pouvez passer\n");
else
printf("Attendez la s'il vous plait\n");
return 0;
}
Si le voyageur a ses papiers et n’a rien à déclarer, il peut passer.
b. ET avec plus de deux expressions membres
S’il y a plus de deux expressions membres :
E1 && E2 && E3 && E4 est vraie si TOUTES sont vraies, faux sinon
Exemple :
if (x>0 && x<1024 && y>0 && y<768)
printf("(x,y) dans ecran en 1024-768\n");
Dès qu’elle trouve une expression membre fausse, l’expression entière donne alors faux et les expressions restantes ne sont pas regardées.
Autre exemple, liste pour pâte à crêpes :
#include <stdio.h>
#include <stdlib.h>
int main()
{
char farine, sel, oeufs, lait, gruyere;
printf("Dans le frigo y a-t-il des oeufs (o/n), du lait (o/n)"
"du gruyere (o/n)\n ?");
scanf("%c%c%c",&oeufs, &lait, &gruyere);
rewind(stdin);
printf("Dans le placard y a-t-il de la farine (o/N)"
"et du sel (o/n) ?\n");
scanf("%c%c",&farine...
Boucles
1. Boucle TANT QUE : le while
La boucle while a la forme suivante :
while (expression vraie){
instructions;
}
Tant que la valeur de l’expression est vraie, c’est-à-dire non nulle et différente de 0, les instructions du bloc lié à la boucle sont répétées. Pour que la boucle puisse s’arrêter, il faut que l’expression devienne fausse et pour ce faire une des composantes du test doit être modifiée dans le bloc des instructions. Par exemple :
int a=3,i=0;
while (i<a){
i++;
printf("i vaut %d\n",i);
}
printf("Fin de la boucle avec i=%d \n",i);
Tant que i est inférieur à a, les instructions sont exécutées :
au départ a vaut 3 et i vaut 0 :
i < a : le test est vrai, les instructions sont effectuées, i est
augmenté de 1 et i vaut 1.
i < a : le test est vrai, les instructions sont effectuées, i est
augmenté de 1 et i vaut 2.
i < a : le test est vrai, les instructions sont effectuées, i est
augmenté de 1 et i vaut 3.
i < a : le test est faux, fin de la boucle, poursuite de l'exécution
avec les instructions qui suivent le bloc de la boucle.
Le test peut être faux dès le départ et dans ce cas les instructions du bloc de la boucle ne sont pas exécutées, par exemple :
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
printf("entrer une valeur entre 50 et 100\n");
scanf("%d",&a);
while( a < 50 || a>100 ){
printf("le nombre doit etre compris entre 50 et 100\n");
scanf("%d",&a);
rewind(stdin);
}
printf("valeur entree : %d\n",a);
return 0;
}
Si l’utilisateur entre au départ une valeur entre 50 et 100 le test de la boucle est faux et il n’y a pas de boucle. Il y a boucle tant que la valeur entrée par l’utilisateur est inférieure à 50 ou supérieure à 100.
2. Boucle FAIRE {...} TANT QUE : le do-while
La boucle do-while a la forme suivante :
do {
instructions;
} while (expression) ; // à noter...
Utilisations typiques de boucles
1. Créer un menu utilisateur
Un programme qui se termine du fait d’une action de l’utilisateur repose toujours sur une boucle, c’est la base de sa dynamique. Pour créer un menu, le principe est de donner un choix de commandes à l’utilisateur via une interface et de mettre fin à la boucle si l’utilisateur le demande avec une commande spécifique. L’interface simple que nous proposons offre à l’utilisateur quatre choix :
printf ( "1 : Affiche bonjour\n"
"2 : Affiche il fait beau\n"
"3 : Entrer un nombre entier\n"
"0 : Quitter\n");
Ensuite le programme capture le choix de l’utilisateur et applique les traitements correspondants. Si l’utilisateur entre d’autres nombres que 0, 1, 2 ou 3, le programme signale qu’ils ne correspondent pas à des commandes :
#include <stdio.h>
#include <stdlib.h>
int main()
{
int choix, res;
do{ // 1
printf ( "1 : Affiche bonjour\n" // 2
"2 : Affiche il fait beau\n"
"3 : Entrer un nombre entier\n"
"0 : Quitter\n");
scanf("%d",&choix); // 3
rewind(stdin);
switch(choix){ // 4
case 0 :
break;
case 1 :
printf("bonjour\n");
break;
case 2 :
printf("il fait beau\n");
break;
case 3 :
scanf("%d",&res);
rewind(stdin);
printf("le nombre est : %d\n",res);
break;
default :
printf("Pas de commande %d\n",choix);
break;
}
}while(choix !=0); // 5
return 0;
}
(1) (5) Instruction de boucle do-while. La boucle a lieu tant que la variable choix est différente de 0, elle se termine si la variable choix est égale à 0.
(2) Affichage du menu utilisateur.
(3) Première instruction exécutée au moins une fois, l’utilisateur entre une valeur pour la variable choix. L’appel de la fonction scanf() est suivi d’un appel à la fonction rewind() afin de réinitialiser le buffer des entrées clavier stdin et d’éviter une boucle infinie en cas d’erreur.
(4) À...
Fonctions
1. Qu’est-ce qu’une fonction ?
-
La fonction est une unité de traitement en vue de l’accomplissement d’une action ou d’une étape dans l’action. La conception de programmes à actions multiples va s’appuyer sur un découpage judicieux en multiples fonctions.
-
Elle permet de factoriser (généraliser) du code qui se répète. Lorsque dans un programme une séquence d’actions se répète, en général il y a une fonction à écrire pour éclaircir et alléger le code.
-
Techniquement la fonction est un bloc d’instructions doté d’un nom et de deux mécanismes :
-
un mécanisme d’entrée de valeur : ce sont les paramètres en entrée.
-
un mécanisme de sortie de valeur : la valeur de retour (unique).
-
Une fois écrite la fonction est "appelée". L’appel de la fonction permet d’insérer le bloc des instructions de la fonction dans le déroulement du programme à l’endroit où elle est appelée. À ce moment nous donnons pour son exécution des valeurs aux paramètres et s’il y a lieu nous pouvons aussi récupérer la valeur de retour.
Une fonction prend la forme suivante :
<type du retour > <nom fonction> < ( liste de paramètres ) >
{ ouverture bloc
(...) // les instructions du bloc
return (...) //instruction de retour si sortie de valeur
} fermeture bloc
La première ligne constitue ce que l’on nomme "l’en-tête de la fonction". Le bloc d’instructions constitue le corps de la fonction.
L’en-tête comprend le type de la valeur de retour, le nom de la fonction et une liste de paramètres entre parenthèses. Les paramètres sont simplement des variables locales à la fonction qui ont la particularité de pouvoir recevoir des valeurs au moment de l’appel de la fonction. S’il n’y a pas de paramètre, la liste est vide ce qui est précisé avec le mot-clé void. La valeur de retour n’est pas obligatoire non plus. S’il n’y a pas de valeur de retour, le type du retour est void. Une fonction qui ne retourne pas de valeur...
Gestion des variables
L’écriture des fonctions nécessite de mieux connaître quelle est la visibilité et la durée de vie d’une variable dans un programme.
1. Visibilité des variables
La visibilité et la durée de vie d’une variable est relative au lieu de sa déclaration dans le programme. Le niveau d’imbrication de bloc est appelé la profondeur de sa déclaration.
a. Profondeur de la déclaration
La déclaration est dite de profondeur 0 lorsqu’elle est en dehors de tout bloc d’instructions et de profondeur n avec n supérieur ou égal à 1 lorsqu’elle est dans un bloc ; n correspond au niveau d’imbrication du bloc concerné, par exemple :
#include<stdlib.h> // niveau 0 en global, hors bloc
int x=0;
void test(int a);
int main()
{ // bloc niveau 1
int i=0;
{ // bloc niveau 2
int y=9;
{ // bloc niveau 3
int w=rand();
test(w);
}
}
test(i);
}
void test(int a)
{ // autre bloc niveau 1
int b;
...// instructions de la fonction
}
Le paramètre int a de la fonction test() est considéré comme de niveau 1, local à la fonction test().
b. Portée des variables
Quel que soit son type (char, short, int, float, double, struct, tableau, pointeur), une variable est visible dans le bloc où elle est déclarée à partir de sa déclaration et dans tous les sous-blocs (niveau d’imbrication supérieur). En revanche, elle n’est pas accessible dans les blocs au-dessus (niveau d’imbrication inférieur) ou d’autres blocs...
Style, commentaires et indentations
1. Pourquoi soigner le style ?
Le but du style de la programmation est de s’assurer que le code sera facile à lire et partageable au sein d’une équipe. Un bon style est indispensable à toute bonne programmation.
Un programme sans rigueur est peu lisible, peu fiable, difficile à corriger et à comprendre. Il devient vite impossible à développer et se trouve finalement figé dans son incompréhensibilité. Plus un programme est rendu incompréhensible par l’absence de rigueur de sa présentation moins il est possible de continuer son développement, d’apporter des modifications ou des nouvelles fonctionnalités, voire tout simplement de le déboguer. Le style est finalement en relation avec l’efficacité de la conception. Aujourd’hui le style s’est normalisé et il est associé à des conseils et des recommandations qui varient très peu d’une communauté de développement à une autre. Le style n’est pas secondaire, c’est une question de discipline, de rigueur et d’efficacité dans le travail. Montrez votre code et rien qu’à son style tout employeur saura si vous êtes professionnel ou non.
Le créateur du langage C, Brian Kernighan nous dit ceci à propos du style : "le code sera simple et limpide - logique claire et évidente, expressions naturelles, utilisation d’un langage conventionnel, noms compréhensibles et significatifs, formatage impeccable, commentaires utiles - et évitera les spécificités soi-disant astucieuses ainsi que les constructions inhabituelles. Il est important de rester cohérent car les tierces personnes trouveront ainsi votre code plus lisible - et vous aurez le même avis pour le leur - si vous faites tout sur le même modèle." (KERNIGHAN, La programmation en pratique, p.2).
2. Typographie et choix des noms
L’idéal est un nom concis, facile à retenir, si possible prononçable et qui donne la bonne information concernant une variable, une fonction ou tout objet que vous créez dans le programme. De façon cohérente donnez aux objets apparentés des noms similaires avec éventuellement précision sur les relations qui existent...