Dans tous les exemples de programmes que l’on a vu jusqu’à présent, les instructions étaient toutes effectuées, les unes après les autres, dans l’ordre de la lecture.
Cependant, ce n’est pas une manière très flexible de retranscrire des algorithmes. En effet, la plupart des algorithmes nécessitent qu’on répète plusieurs fois les mêmes opérations, avec très peu de différence à chaque fois. Aussi, il y a certaines opérations qu’on n’a envie d’effectuer que dans certaines conditions. Par exemple, on n’a pas très envie que le programme effectue une division par 0 en plein milieu du calcul !
C’est donc pour changer l’ordre d’exécution des instructions qu’on a besoin de mettre en place les structures de contrôle dans un programme.
On va passer en revue dans cette partie les structures de contrôle les plus fréquemment rencontrées en C++.
if
if... else
Commençons par les blocs if
. Ces blocs permettent
d’effectuer des instructions uniquement si une condition est
remplie.
Ils se présentent comme ceci:
if (expression booleenne)
instruction ou bloc d'instructions;
L’expression booléenne est évaluée au moment où l’exécution arrive au
if
.
Si cette expression vaut true
(ou est un entier
différent de 0, souvenez-vous) alors l’instruction (ou le bloc, entre
accolades) est réalisée.
Si l’expression vaut false
(ou est un entier égal à 0),
les instructions ne sont pas effectuées, et on passe à la suite.
if
if... else
On peut rajouter une instruction ou un bloc d’instructions à
effectuer si l’instruction est fausse. Le bloc est alors précédé du mot
clé else
:
if (expression booleenne)
instruction ou bloc d'instructions 1;
else
instruction ou bloc d'instructions 2;
Si l’expression est vraie, on effectue uniquement les instructions 1.
Si l’expression est fausse, on effectue uniquement les instructions 2.
Nous allons voir quelques exemples.
if
if... else
Exemple: ne pas diviser par 0
int a=0,b=0,d;
// imaginez du code qui peut modifier les valeurs de a et b
// On teste si b est égal à 0
if (b==0) {
// Cet affichage est fait si b est egal a 0
std::cout << "Erreur: division par 0 impossible!" << std::endl;
}
else {
// Dans le cas contraire, on peut diviser par b
d = a/b;
}
Rappel : pour tester si l’entier b
est nul, il faut écrire
b==0
, pas b=0
! Le code compilera même si vous
oubliez d’écrire ==
.
“Mais alors, pourquoi ça compile ?” Eh bien, il se trouve que
l’assignation est elle-même une opération qui en plus de mettre à jour
la variable à gauche du signe =
, retourne la valeur
assignée.
Dans cet exemple, b=0
retourne donc 0, quelle
que soit la valeur précédente de b
. Comme 0 est équivalent
à false
, on ne fera jamais l’affichage d’erreur, et on
rentrera dans le else
. On divisera par 0, et il y aura très
certainement un bug dans la suite du programme. Juste pour ce petit
caractère =
en moins ! Faites attention !
if
if... else
Deuxième exemple: utiliser directement un booléen.
bool isOk = true;
// Imaginez du code, et si quelque chose se passe mal, isOk est changé à false
if (not isOk) {
std::cout << "Impossible de faire X, car..." << std::endl;
}
else {
// X
}
C’est assez pratique d’utiliser des booléens dont les noms décrivent une
propriété. Ils commencent souvent par is...
ou
has...
. Cela permet une lecture très facile des conditions
de if
.
if
if... else
Dernier exemple : utiliser directement un entier
int count=0;
// ici des operations qui peuvent augmenter la valeur de count
if (count) {
// est equivalent a
// if (count !=0)
}
Cette utilisation est moins rencontrée, car moins claire à comprendre à la lecture.
if
if... else
Quelques remarques sur l’écriture des if
.
if
ou
un else
, on n’est pas obligé de mettre des accolades.
Cependant certains préfèrent en mettre quoi qu’il arrive.
if
, ou une suite if ... else
, est
considéré comme une seule instruction. On peut alors faire des “cascades
de if
”. Sans les accolades qui devraient suivre
immédiatement les mots else
, cela peut se présenter comme
ça:switch
Le switch
est une autre façon d’exécuter des
instructions sous certaines conditions. On peut notamment s’en servir à
la place des casacades de if
. Le bloc se présente comme
ceci:
switch (expression entiere) {
case valeur1:
// instructions A
break;
case valeur2:
// instructions B
break;
case valeur3:
// instructions C
case valeur4:
case valeur5:
// instructions D
break;
default:
// instructions E
}
On ne peut utiliser le switch
qu’avec des entiers ou des
types associés aux entiers. Pas de réels, ni de chaînes de caractères,
malheureusement !
switch
La règle de fonctionnement est la suivante: la valeur de l’expression
entière est évaluée. Ensuite, on va à la ligne case
qui
correspond à la valeur trouvée. On effectue alors toutes les
instructions depuis cette ligne jusqu’à rencontrer un
break
, même si on recontre d’autres lignes
case
ou la ligne default
.
switch
Prenons un exemple (j’ai maintenant précisé les valeurs des
case
).
int a;
// imaginez qu'on modifie a avant le switch ...
switch (a) {
case 0:
// instructions A
break;
case 1:
// instructions B
break;
case 2:
// instructions C
case 10:
case 11:
// instructions D
break;
default:
// instructions E
}
a
vaut 0, alors on effectue les instructions A, et
seulement ces instructions, à cause du break
à la ligne
6.a
vaut 1, alors on effectue les instructions B, et
seulement ces instructions, à cause du break
à la ligne
9.switch
int a;
// imaginez qu'on modifie a avant le switch ...
switch (a) {
case 0:
// instructions A
break;
case 1:
// instructions B
break;
case 2:
// instructions C
case 10:
case 11:
// instructions D
break;
default:
// instructions E
}
a
vaut 2, alors on effectue les instructions C et D,
car le break
suivant n’est qu’à la ligne 15 !a
vaut 10 ou 11, on effectue les
instructions D.a
a une valeur différente de 0, 1, 2, 10 ou 11,
alors on effectue les instructions E.switch
Dernière remarque sur les switch
. Ils ont une syntaxe un
peu particulière par rapport aux blocs if
et à tous ceux
qu’on verra par la suite.
Même si il y a plusieurs instructions qui suivent un
case
, on n’est pas obligé de mettre des accolades.
(Peut-être car ce sont les case
qui servent de délimiteur
?). SAUF si on doit déclarer des nouvelles variables
dans ces instructions. Dans ce cas il faut des accolades:
C’est tout pour les instructions conditionnelles, on va maintenant voir plusieurs structures de contrôle qui permettent de réaliser plusieurs itérations d’un algorithme sans devoir réécrire les instructions. Ces structures, on les appelle de manière générale des boucles.
while
et do while
La première boucle qu’on va voir est le while
(“tant
que” en français).
Le principe est le même que pour le if
, à la différence
près qu’on ne sort jamais de la boucle tant que l’expression booléenne
est vraie ! Cela veut notamment dire que si la valeur que donne
l’expression ne change jamais, on est coincé dans ce qu’on appelle une
boucle infinie.
Essayez vous même avec ce programme !
Vous pouvez interrompre un programme à tout moment en faisant Ctrl+C.
while
et do while
Comme pour le bloc if
, s’il y a qu’une seule instruction à
effectuer dans le while
, on peut retirer les accolades.
C’est très rare pour les while
.
Voici un exemple plus sérieux, on calcule les \(N\) premiers termes de la série harmonique:
double somme = 0.;
int n = 1;
int nMax = 100;
while (n<=nMax) {
somme += 1./n;
n++;
}
std::cout << "Somme des " << nMax << " premiers termes de la série harmonique : " << somme << std::endl;
Avant d’entrer dans le while
, on
intialise les variables qui serviront à contrôler la
sortie de la boucle. Ici, n
commence à la valeur 1. Comme 1
est inférieur ou égal à nMax
qui vaut 100, la
condition est remplie, on effectue les
instructions.
Les instructions consistent à rajouter \(1/n\) à la somme (notez la division
réelle!), et ensuite incrémenter la variable
n
de 1. somme
vaut maintenant 1., et
n
vaut 2.
n
est toujours inférieur à nMax
, on
effectue donc un passage supplémentaire dans la boucle, et ainsi de
suite…
Quand n
passe à 101, la condition n’est plus remplie, et
on sort de la boucle, en passant à la suite du code.
while
et do while
La boucle do... while
est une variante de la boucle
while
. La seule différence entre les deux boucles est qu’un
do... while
effectuera toujours un premier passage dans la
boucle avant d’évaluer si la condition autorise à répéter les
passages.
Cela se retrouve dans l’écriture:
for
Le deuxième type de boucles est le plus rencontré dans les codes C++.
Il s’agit des boucles for
.
Les boucles while
permettent d’effectuer une grande
variété de tâches, mais leur syntaxte est un peu lourde et piégeuse:
La syntaxe du for
permet de regrouper les éléments
nécessaires à la boucle en une ligne:
Ce sont des points-virgules qui séparent les trois éléments, pas des virgules.
for
while
.Il est toujours possible de convertir une boucle
for
en une boucle while
et
inversement.
L’exemple précédent de la série harmonique, mais écrit avec une
boucle for
, cela donne ça:
for
double somme = 0.;
int nMax = 100;
for (int n=1; n<nMax; n++)
somme += 1./n;
std::cout << "Somme des " << nMax << " premiers termes de la série harmonique : " << somme << std::endl;
Ici, on peut voir que j’ai déclaré l’entier n
dans la
partie initialisation du for
. Cet entier n’existe que dans
la boucle: une fois sorti du for
, n
n’est plus
connu.
On peut cependant utiliser une variable qui a déjà été déclarée avant
le for
. Il suffit de lui donner une valeur de départ.
for
Quelques remarques pour finir:
if
et while
, une boucle
for
avec une seule instruction n’a pas besoin
d’accolades.for
peuvent être vides, mais ce n’est pas très utile.continue
permet de passer directement à
l’itération suivante d’un for
break
, déjà recontré dans les
switch
, permet aussi de sortir directement des boucles
for
, while
et do while
. Je
déconseille de l’utiliser cependant, un break
est très
souvent synonyme d’une boucle mal écrite.while
et un for
?Il n’y a pas vraiment de règle pour cela, les deux sont équivalents.
Cela dit, on voit que la syntaxe du for
ne permet pas
d’écrire des choses compliquées dans chacun des trois éléments qui le
définissent.
J’ai souvent tendance à conseiller les for
pour un
nombre d’itérations connu, et les while
dans le cas
contraire. En général, les codes produits en suivant cette façon de
faire sont assez clairs. (Mais c’est de toute façon toujours le cas,
puisque vous commentez très bien vos codes, n’est-ce-pas ? ;) )
On va écrire un programme qui va valider ou non une date rentrée par l’utilisateur sous forme de trois entiers. La date est valide si le mois est compris entre 1 et 2, si le numero du jour correspond au mois donné, et on verra si l’année est bissextile ou non pour le 29 février.
Commençons déjà par demander à l’utilisateur de rentrer ces trois entiers.
On démarre toujours avec une fonction main
:
Ensuite demandons les valeurs des entiers:
Nous allons maintenant tester le jour en fonction du mois. Il y a les mois à 31 jours (1,3,5,7,8,10,12), les mois à 30 jours (4,6,9,11), le mois à 28 ou 29 jours (2), et les mois qui n’existent pas (le reste des entiers).
Le choix le plus indiqué est une structure switch
qui
comparera la valeur de la variable mois
. Pensez à bien
mettre des break
à la fin de chaque cas.
(Faites défiler pour voir tout le code)
#include <iostream>
int main() {
int jour = -1;
int mois = -1;
int annee = 0;
std::cout << "Entrez trois entiers correspondant au jour, mois et année " << std::endl;
std::cin >> jour >> mois >> annee;
// On effectue le test suivant le mois
// mois a 31 jours : jan,mar,mai,juil,aout,oct,dec
// mois a 30 jours : avr,juin,sept,nov
// mois a 28/29 jours : fev
switch (mois) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
// instructions
break;
case 4:
case 6:
case 9:
case 11:
// instructions
break;
case 2:
// en fevrier c'est complique
break;
default:
// non valide
}
return 0;
}
Dans tous les cas, il faut déterminer si le jour est strictement plus
grand que 0, et inférieur à un nombre max qui dépend du mois. C’est
celui qu’on va modifier dans le switch
.
Il y a les cas faciles à 30 et 31 jours:
(Faites défiler pour voir tout le code)
#include <iostream>
int main() {
int jour = -1;
int mois = -1;
int annee = 0;
std::cout << "Entrez trois entiers correspondant au jour, mois et année " << std::endl;
std::cin >> jour >> mois >> annee;
int jourMax = -1;
// jourMax dépend du mois
// mois a 31 jours : jan,mar,mai,juil,aout,oct,dec
// mois a 30 jours : avr,juin,sept,nov
// mois a 28/29 jours : fev
switch (mois) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
jourMax = 31;
break;
case 4:
case 6:
case 9:
case 11:
jourMax = 30;
break;
case 2:
// en fevrier c'est complique
break;
default:
;
// on ne fait rien jourMax est a -1, le test ci-dessous sera faux
}
if (jour>0 and jour<=jourMax)
std::cout << "La date est valide." << std::endl;
else
std::cout << "La date n'est pas valide." << std::endl;
return 0;
}
Il ne reste plus que février, et ses années bisextiles. Ce sont les années qui sont
On peut mettre toutes ces conditions dans le test du if
,
en faisant attention aux opérateurs logiques et aux parenthèses.
Quand un entier \(n\) est un
multiple de \(p\), le reste de la
division par \(p\) vaut 0. On utilise
alors l’opérateur %
.
Voilà donc le programme complet (Faites défiler pour voir tout le code)
#include <iostream>
int main() {
int jour = -1;
int mois = -1;
int annee = 0;
std::cout << "Entrez trois entiers correspondant au jour, mois et année " << std::endl;
std::cin >> jour >> mois >> annee;
int jourMax = -1;
// On effectue le test suivant le mois
// mois a 31 jours : jan,mar,mai,juil,aout,oct,dec
// mois a 30 jours : avr,juin,sept,nov
// mois a 28/29 jours : fev
switch (mois) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
jourMax = 31;
break;
case 4:
case 6:
case 9:
case 11:
jourMax = 30;
break;
case 2:
// annees bisextiles, multiples de 400 ou multiple de 4 execpte multiple de 100
if (annee%400==0 or (annee%4==0 and annee%100!=0))
jourMax = 29;
else
jourMax = 28;
break;
default:
;
// on ne fait rien, jourMax est a -1, le test ci-dessous sera faux
}
if (jour>0 and jour<=jourMax)
std::cout << "La date est valide." << std::endl;
else
std::cout << "La date n'est pas valide." << std::endl;
return 0;
}
Règle d’or : toujours tester ses programmes !
Ceci est un exemple de mauvais test, j’aurai dû conscieusement tester tous les cas possibles !
Ce second programme lira des notes rentrées par l’utilisateur, entre 0 et 20. Dès l’instant que l’utilisateur aura rentré une note qui n’est pas entre 0 et 20, le programme affichera la moyenne des notes rentrées précédemment, ainsi que la note minimum et la note maximum.
Petit problème. Nous n’avons pas encore vu les tableaux, on ne pas encore stocker toutes les variables que l’utlisateur va rentrer, d’autant plus qu’on ne sait pas combien il va en rentrer ! Mais on va pouvoir mettre à jour au fur et à mesure les informations dont on a besoin.
Pour la moyenne, on initialisera une somme, et on ajoutera chaque saisie à cette somme. Il nous faudra aussi un compteur pour diviser le total à la fin.
C’est parti.
Comme d’habitude, on commence avec un main
. On met aussi
l’affichage basique dont on aura ici besoin. On déclare aussi un réel
qui contiendra la note actuellement saisie.
Ensuite, on peut déclarer et initialiser le compteur de notes valides. On peut aussi déjà traiter le cas pathologique où aucune note correcte n’a été saisie. N’oubliez jamais de traiter ces cas dans vos programmes. Une règle de prudence est de ne jamais faire confiance à l’utilisateur! À vous de voir jusqu’où vous poussez ces tests.
#include <iostream>
int main() {
double note;
int nNotes = 0;
std::cout << "Entrez svp une serie de notes entre 0 et 20. Une note en dehors de l'intervalle finit la serie." << std::endl;
std::cin >> note;
// ici on testera les notes
// ....
if (nNotes==0)
std::cout << "Aucune note entre 0 et 20 n'a ete rentree! Impossible d'afficher les resultats." << std::endl;
else {
// affichage des resultats
}
return 0;
}
Initialisons maintenant les autres valeurs qui serviront au suivi des notes. Si la première note est valide, alors elle est plus petite que 20. Initialisons donc la note minimale à une valeur plus grande. Et inversement pour la note max. Pour la moyenne, on peut initialiser la somme à 0. (Les sommes s’initialisent à 0, les produits à 1). (Faites défiler pour voir tout le code)
#include <iostream>
int main() {
double note;
int nNotes = 0;
double noteMin = 21.;
double noteMax = -1.;
double somme = 0.;
std::cout << "Entrez svp une serie de notes entre 0 et 20. Une note en dehors de l'intervalle finit la serie." << std::endl;
std::cin >> note;
// ici on testera les notes
// ....
if (nNotes==0)
std::cout << "Aucune note entre 0 et 20 n'a ete rentree! Impossible d'afficher les resultats." << std::endl;
else {
// affichage des resultats
}
return 0;
}
Maintenant testons les notes valides à l’aide d’un bloc
if
. (Faites défiler pour voir tout le code)
#include <iostream>
int main() {
double note;
int nNotes = 0;
double noteMin = 21.;
double noteMax = -1.;
double somme = 0.;
std::cout << "Entrez svp une serie de notes entre 0 et 20. Une note en dehors de l'intervalle finit la serie." << std::endl;
std::cin >> note;
if (note>0 and note<20.) {
// on fait des trucs
}
if (nNotes==0)
std::cout << "Aucune note entre 0 et 20 n'a ete rentree! Impossible d'afficher les resultats." << std::endl;
else {
// affichage des resultats
}
return 0;
}
Dans ce bloc if
, la note est valide. Mettons à jour les
infos. Pour le min et le max, on compare à la valeur précédente du min
et du max:
#include <iostream>
int main() {
double note;
int nNotes = 0;
double noteMin = 21.;
double noteMax = -1.;
double somme = 0.;
std::cout << "Entrez svp une serie de notes entre 0 et 20. Une note en dehors de l'intervalle finit la serie." << std::endl;
std::cin >> note;
if (note>0 and note<20.) {
nNotes++;
somme += note;
if (note < noteMin)
noteMin = note;
if (note > noteMax)
noteMax = note;
}
if (nNotes==0)
std::cout << "Aucune note entre 0 et 20 n'a ete rentree! Impossible d'afficher les resultats." << std::endl;
else {
// affichage des resultats
}
return 0;
}
La première note est rentrée, on lit maintenant la suivante, et on a
envie de répéter les opérations tant que la note est
valide. C’est une indication qu’il faut utiliser un while
!
On peut en fait transformer le if
qu’on a écrit portant sur
la valeur de la note, et faire mettre à jour la valeur de celle-ci dans
la boucle:
#include <iostream>
int main() {
double note;
int nNotes = 0;
double noteMin = 21.;
double noteMax = -1.;
double somme = 0.;
std::cout << "Entrez svp une serie de notes entre 0 et 20. Une note en dehors de l'intervalle finit la serie." << std::endl;
std::cin >> note;
// On continue tant que la note est correcte
while (note>0 and note<20.) {
// On met a jour les infos
nNotes++;
somme += note;
if (note < noteMin)
noteMin = note;
if (note > noteMax)
noteMax = note;
// On lit une nouvelle valeur de la note
std::cin >> note;
}
if (nNotes==0)
std::cout << "Aucune note entre 0 et 20 n'a ete rentree! Impossible d'afficher les resultats." << std::endl;
else {
// affichage des resultats
}
return 0;
}
Il n’y a plus qu’à finaliser l’affichage des résultats dans le cas où il y a quelque chose à calculer. (Faites défiler pour voir tout le code)
#include <iostream>
int main() {
double note;
int nNotes = 0;
double noteMin = 21.;
double noteMax = -1.;
double somme = 0.;
std::cout << "Entrez svp une serie de notes entre 0 et 20. Une note en dehors de l'intervalle finit la serie." << std::endl;
std::cin >> note;
// On continue tant que la note est correcte
while (note>0 and note<20.) {
// On met a jour les infos
nNotes++;
somme += note;
if (note < noteMin)
noteMin = note;
if (note > noteMax)
noteMax = note;
// On lit une nouvelle valeur de la note
std::cin >> note;
}
if (nNotes==0)
std::cout << "Aucune note entre 0 et 20 n'a ete rentree! Impossible d'afficher les resultats." << std::endl;
else {
std::cout << "Les notes sont comprises entre " << noteMin << " et " << noteMax << std::endl;
std::cout << "et la note moyenne est de " << somme/nNotes << std::endl; // somme est un double: division reelle
}
return 0;
}
Passons au test:
Maintenant quelques petites QCM.
Dans la parenthèse suivant for
, on trouve les éléments
suivants:
Dans la parenthèse suivant for
, on trouve les éléments
suivants:
Que fait le code ci-dessus ?
Que fait le code ci-dessus ?
Attention au piège de l’indentation. Il n’y a pas d’accolades après
le for
, donc seule l’instruction k++
est
effectuée dans la boucle.
int nStars = 0;
for (int nStars=0; nStars<20; nStars++)
std::cout << "*";
std::cout << std::endl << "Number of stars "
<< nStars << std::endl;
Pourquoi cette partie de programme me dit-elle qu’aucune étoile n’a été affichée ?
nStars
.
for
.
int nStars = 0;
for (int nStars=0; nStars<20; nStars++)
std::cout << "*";
std::cout << std::endl << "Number of stars "
<< nStars << std::endl;
Pourquoi cette partie de programme me dit-elle qu’aucune étoile n’a été affichée ?
nStars
.
for
.
Le bloc d’initialisation du for
contient la déclaration
d’une variable. Même si une variable avec le même nom existait déjà,
c’est une nouvelle variable qui sert pendant la boucle. Cette nouvelle
variable est détruite à la sortie du bloc. L’ancienne est restée
inchangée.