# Cours 3 - Flux de contrôle, décisions et boucles

Où est Charlie le Bug ?

Trouvez tous les bugs dans ce code :

int health = 100;
float damage = 25.5;
int remaining = health - damage;
cout << "Health: " health << endl;
int x = 10 / 3.0;
const int MAX = 100;
MAX = 200;

Solution :

  1. remaining devrait être double, ou utilisez static_cast<int>(health - damage) intentionnellement
  2. Il manque le std:: devant le cout et devant le endl
  3. Il manque << entre "Health: " et health
  4. Utilisez double x = 10 / 3.0; si vous voulez le résultat décimal
  5. Supprimez const ou ne réassignez pas — const signifie constant !

Prise de décisions

Opérateurs de comparaison

OpérateurSignificationExemple
==Égal àx == 5
!=Différent dex != 0
<Inférieur àx < 10
>Supérieur àx > 0
<=Inférieur ou égal àx <= 100
>=Supérieur ou égal àx >= 1

Opérateurs logiques

OpérateurSignificationExemple
&&ANDx > 0 && x < 100
``
!NOT!gameOver

Comparer des valeurs

// Comparing data values
#include <iostream>
#include <format>
int main()
{
  char first {};      // Stores the first character
  char second {};     // Stores the second character
 
  std::cout << std::format("Enter a character: ");
  std::cin >> first;
 
  std::cout << std::format("Enter a second character: ");
  std::cin >> second;
 
  std::cout << std::format("The value of the expression {} < {} is {}",
               first, second, first < second) << std:endl;
  std::cout << std::format("The value of the expression {} == {} is {}",
               first, second, first == second) << std::endl;
}

Point clé : Les caractères sont comparés selon leurs valeurs ASCII. 'A' (65) < 'B' (66) est true.

if

#include <iostream>
#include <format>
int main()
{
  std::cout << std::format("Enter an integer between 50 and 100: ");
  int value {};
  std::cin >> value;
 
  if (value)
    std::cout << std::format("You have entered a value that is different from zero.") << std::endl;
 
  if (value < 50)
    std::cout << std::format("The value is invalid - it is less than 50.") << std::endl;
 
  if (value > 100)
    std::cout << std::format("The value is invalid - it is greater than 100.") << std:endl;
 
  std::cout << std::format("You entered {}.", value) << std::endl;
}

Mieux avec else if et else En C++, else if permet d'enchaîner plusieurs conditions en évitant d'évaluer les suivantes dès qu'une est vraie. C'est plus efficace et plus lisible qu'une série de if indépendants.

if (value < 50)
{
    std::cout << std::format("Too low!") << std::endl;
}
else if (value > 100)
{
    std::cout << std::format("Too high!") << std::endl;
}
else
{
    std::cout << std::format("Perfect! You entered {}.", value) << std::endl;
}
// Avec else if : dès qu'une condition est vraie, on sort
if (score >= 90)
    grade = 'A';
else if (score >= 80)
    grade = 'B';
else if (score >= 70)
    grade = 'C';
else
    grade = 'F';
 
// Avec des if séparés : chaque condition est testée même si une précédente était vraie
if (score >= 90)
    grade = 'A';
if (score >= 80)
    grade = 'B';  // écrase 'A' si score >= 90 !
if (score >= 70)
    grade = 'C';  // écrase encore...

Les avantages principaux sont la performance (moins de tests inutiles), la correction logique (pas d’écrasement accidentel comme dans le deuxième exemple), et la lisibilité (l’intention “une seule branche parmi plusieurs” est claire).

Instruction switch Pour un grand nombre de valeurs discrètes, un switch peut être préférable,

switch (choice) {
  case 1:
    std::cout << std::format("You chose option 1") << std::endl;
    break;        // Don't forget break!
  case 2:
    std::cout << std::format("You chose option 2") << std::endl;
    break;
  default:
    std::cout << std::format("Invalid choice") << std::endl;
    break;
}
int main()
{
    int choice = 1;
 
    switch (choice)
    {
    case 1:
        std::cout << std::format("You chose option 1") << std::endl;
        choice++;
        // j'ai oublié le break ici !
    case 2:
        std::cout << std::format("You chose option 2") << std::endl;
        break;
    default:
        std::cout << std::format("Invalid choice") << std::endl;
        break;
        
    choice = 1;
        
        // Ici, est-ce que les deux std::cout vont être affichées ?
    if (choice == 1)
    {
        std::cout << std::format("You chose option 1") << std::endl;
        choice++;
    }
    else if (choice == 2)
        std::cout << std::format("You chose option 2") << std::endl;
    else
        std::cout << std::format("Invalid choice") << std::endl;
    }

Opérateur ternaire

// condition ? value_if_true : value_if_false
int max = (a > b) ? a : b;
std::string status = (health > 0) ? "Alive" : "Dead";

Comparaison tripartite (opérateur Spaceship C++20)

// Three-way comparison of integers
#include <iostream>
#include <format>
#include <compare>
int main()
{
  std::cout << std::format("Please enter a number: ");
  int value;
  std::cin >> value;
 
  std::strong_ordering ordering{ value <=> 0 };
 
  std::cout << std::format("value < 0: {}", ordering == std::strong_ordering::less) << std::endl;
  std::cout << std::format("value > 0: {}", ordering == std::strong_ordering::greater) << std::endl;
  std::cout << std::format("value == 0: {}", ordering == std::strong_ordering::equal) << std::endl;
}

L’opérateur spaceship <=>, introduit en C++20, effectue une comparaison tripartite en une seule opération. Au lieu d’écrire séparément <, ==, >, il retourne un résultat qui encode les trois cas à la fois.

#include <compare>
 
auto result = a <=> b;
// result < 0  → a est inférieur à b
// result == 0 → a est égal à b
// result > 0  → a est supérieur à b

L’intérêt principal est qu’en le définissant une seule fois, le compilateur génère automatiquement les six opérateurs de comparaison (<, >, <=, >=, ==, !=).

struct Point {
    int x, y;
    auto operator<=>(const Point&) const = default;
};
 
Point a{1, 2}, b{3, 4};
// Tout ceci fonctionne automatiquement :
a < b;
a >= b;
a == b;
// etc.

Les trois types de retour reflètent la nature de la comparaison :

  • std::strong_ordering — pour les types où l’égalité signifie interchangeabilité (comme les entiers)
  • std::weak_ordering — pour les types où des valeurs “équivalentes” ne sont pas forcément identiques (comme un tri insensible à la casse)
  • std::partial_ordering — pour les types où certaines valeurs sont incomparables (comme les flottants avec NaN)
// Exemple concret avec un tri custom
struct Student {
    std::string name;
    int grade;
 
    // Trier par note décroissante, puis nom croissant
    auto operator<=>(const Student& other) const {
        if (auto cmp = other.grade <=> grade; cmp != 0)
            return cmp;
        return name <=> other.name;
    }
    bool operator==(const Student&) const = default;
};

Avant C++20, il fallait écrire manuellement chacun des six opérateurs (ou au minimum < et == avec std::rel_ops), ce qui était répétitif et source d’erreurs. Le spaceship simplifie tout ça en une seule déclaration.

Boucles

Boucle while La boucle while répète un bloc de code tant qu'une condition est vraie. La condition est évaluée avant chaque itération.

int count {1};
while (count <= 10)
{
    std::cout << std::format("{}", count) << std::endl;
    ++count;
}

Le piège classique est la boucle infinie — si la condition ne devient jamais fausse, le programme tourne indéfiniment :

while (true) {
    // tourne pour toujours, sauf si on utilise break
}

Boucle do-while (s'exécute au moins une fois) Il existe aussi la variante do...while, qui garantit au moins une exécution car la condition est testée après :

int input {};
do {
    std::cout << std::format("Enter a positive number: ");
    std::cin >> input;
} while (input <= 0);  // Keep asking until valid

Boucle for La boucle for en C++ existe sous trois formes principales.

1. La boucle **for** classique

Trois parties : initialisation, condition, incrémentation.

// Traditional for loop
for (int i {0}; i < 10; ++i)
{
    std::cout << std::format("Iteration {}", i) << std::endl;
}

2. La boucle range-based **for** (depuis C++11)

Parcourt directement les éléments d’un conteneur sans manipuler d’index.

std::vector<int> nums = {10, 20, 30};
 
for (int n : nums)              // par copie
    std::cout << n << std::endl;
    
for (const auto& n : nums)     // par référence constante (plus efficace)
    std::cout << n << std::endl;

3. Les nouveautés C++20

C++20 apporte deux ajouts importants à la boucle for :

L’instruction d’initialisation dans un range-based for, qui permet de déclarer une variable locale au scope de la boucle :

std::vector<int> getVector() {
    return {10, 20, 30};
}
 
for (auto v = getVector(); const auto& elem : v) {
    std::cout << elem << std::endl;
}
// v n'existe plus ici — pas de pollution du scope

Et les ranges de la bibliothèque <ranges>, qui permettent de composer des transformations de manière fluide :

#include <ranges>
#include <vector>
 
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
// Filtrer les pairs, les multiplier par 3, prendre les 4 premiers
for (int n : nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * 3; })
        | std::views::take(4))
{
    std::cout << n << " ";
}
// Affiche : 6 12 18 24

L’avantage des ranges est qu’ils sont lazy — chaque élément est traité à la demande sans créer de collections intermédiaires. C’est à la fois plus lisible et plus performant qu’enchaîner des boucles séparées.

Les ranges (<ranges>) sont une abstraction introduite en C++20 qui représente une séquence d’éléments sur laquelle on peut itérer. L’idée est de remplacer le pattern classique “passer deux itérateurs” par un objet unique, et de permettre la composition d’opérations avec l’opérateur pipe |.

Avant les ranges (style STL classique) :

std::vector<int> nums = {1, 2, 3, 4, 5, 6};
std::vector<int> pairs;
std::vector<int> resultat;
 
// Étape 1 : filtrer → crée un vecteur intermédiaire
std::copy_if(nums.begin(), nums.end(), std::back_inserter(pairs),
    [](int n) { return n % 2 == 0; });
 
// Étape 2 : transformer → crée un autre vecteur intermédiaire
std::transform(pairs.begin(), pairs.end(), std::back_inserter(resultat),
    [](int n) { return n * 3; });

Chaque étape alloue de la mémoire et parcourt toute la collection. Avec un million d’éléments, c’est deux allocations et deux parcours complets.

Avec les ranges :

#include <ranges>
 
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
 
auto vue = nums
    | std::views::filter([](int n) { return n % 2 == 0; })
    | std::views::transform([](int n) { return n * 3; });

À ce stade, vue ne contient aucun résultat. C’est juste une description du pipeline. Rien ne s’exécute.

Pourquoi c’est “lazy” :

Les views ne calculent rien au moment de leur création. Le travail se fait uniquement quand on demande un élément, typiquement dans une boucle :

for (int n : vue) {
    std::cout << n << " ";
}
// Affiche : 6 12 18

À chaque itération, le mécanisme est le suivant : on prend l’élément suivant de nums, on applique le filtre, s’il passe on applique la transformation, et on retourne le résultat. Tout se fait élément par élément, en un seul parcours et sans aucune allocation intermédiaire.

C’est particulièrement puissant avec std::views::take :

// Sur un vecteur d'un million d'éléments
for (int n : nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * 3; })
        | std::views::take(3))
{
    std::cout << n << " ";
}

Ici, dès que trois éléments ont été produits, le parcours s’arrête. Avec l’approche classique STL, on aurait filtré et transformé le million d’éléments avant de n’en garder que trois.

En résumé, le lazy evaluation évite les allocations intermédiaires, fait tout en un seul parcours, et permet de court-circuiter dès qu’on a assez de résultats.

Portée, durée de vie et variables globales

// Demonstrating scope, lifetime, and global variables
#include <iostream>
#include <format>
 
long count1{999L};         // Global count1
double count2{3.14};       // Global count2
int count3;                // Global count3 - default initialization
 
int main()
{
  int count1{10};          // Hides global count1
  int count3{50};          // Hides global count3
  std::cout << std::format("Value of outer count1 = {}",  count1) << std::endl;
  std::cout << std::format("Value of global count1 = {}", ::count1) << std::endl;
  std::cout << std::format("Value of global count2 = {}", count2) << std::endl;
 
  { // New block scope starts here
    int count1{20};        // Hides the outer count1
    int count2{30};        // Hides global count2
    std::cout << std::format("\nValue of inner count1 = {}", count1) << std::endl;
    std::cout << std::format("Value of global count1 = {}",  ::count1) << std::endl;
    std::cout << std::format("Value of inner count2 = {}",   count2) << std::endl;
    std::cout << std::format("Value of global count2 = {}",  ::count2) << std::endl;
  } // Block scope ends here
 
  std::cout << std::format("\nValue of outer count1 = {}",  count1) << std::endl;
  std::cout << std::format("Value of outer count3 = {}",    count3) << std::endl;
  std::cout << std::format("Value of global count3 = {}", ::count3) << std::endl;
}

Concept clé : ::count1 accède à la variable globale même quand une variable locale porte le même nom. Les variables sont détruites quand leur portée (le bloc {}) se termine.

Vous avez remarqué le suffixe L dans la déclaration de la variable count1? Celui-ci indique au compilateur que le littéral 999 doit être traité comme un long plutôt que comme un int.

Dans ce cas précis, ça ne change pas grand-chose puisque 999 tient largement dans un int. Mais c’est une bonne habitude pour la cohérence de type, et ça devient important avec de grandes valeurs :

long big = 3000000000L;  // Sans le L, le compilateur pourrait interpréter
                          // 3000000000 comme un int et provoquer un overflow

Les autres suffixes courants sont U pour unsigned, LL pour long long, et on peut les combiner (ULL pour unsigned long long).

break et continue

for (int i {0}; i < 100; ++i)
{
    if (i % 2 == 0) continue;  // Skip even numbers
    if (i > 20) break;         // Stop after 20
    std::cout << std::format("{}", i) << std::endl;     // Only prints odd numbers 1-19
}

break sert à sortir immédiatement du bloc dans lequel il se trouve.

Dans une boucle, il interrompt le parcours dès qu’une condition est remplie :

for (int i = 0; i < 100; i++) {
    if (i == 5)
        break;  // sort de la boucle
    std::cout << i << " ";
}
// Affiche : 0 1 2 3 4

**Dans un ****switch**, son rôle est différent et essentiel. Sans break, l’exécution tombe en cascade dans les case suivants — c’est le fall-through :

int jour = 2;
switch (jour) {
    case 1:
        std::cout << "Lundi";
        break;
    case 2:
        std::cout << "Mardi";
        break;  // sans ce break, on continuerait dans case 3
    case 3:
        std::cout << "Mercredi";
        break;
}
// Affiche : Mardi

Sans les break :

int jour = 2;
switch (jour) {
    case 1:
        std::cout << "Lundi ";
    case 2:
        std::cout << "Mardi ";
    case 3:
        std::cout << "Mercredi ";
}
// Affiche : Mardi Mercredi  ← fall-through non voulu (contrairement au else if)

Parfois le fall-through est intentionnel, par exemple pour grouper des cas :

switch (jour) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        std::cout << "Jour de semaine";
        break;
    case 6:
    case 7:
        std::cout << "Weekend";
        break;
}
 
// Avec des if, ça ressemblerait à ceci
if (jour >= 1 || jour <= 5)
	std::cout << "Jour de semaine";
else if ( jour <= 7)
  std::out << "Weekend";

En C++17, si le fall-through est voulu, on peut le rendre explicite avec l’attribut [[fallthrough]] pour éviter les warnings du compilateur :

switch (niveau) {
    case 3:
        std::cout << "Bonus niveau 3\n";
        [[fallthrough]];
    case 2:
        std::cout << "Bonus niveau 2\n";
        [[fallthrough]];
    case 1:
        std::cout << "Bonus niveau 1\n";
        break;
}

En résumé, dans une boucle break veut dire “j’arrête de boucler”, et dans un switch il veut dire “j’arrête de tomber dans les cases suivants”.

Exemple : jeu de devinette de nombre

#include <iostream>
#include <format>
 
int main()
{
    const int secret {42};
    int guess {};
    int attempts {0};
 
    std::cout << std::format("I'm thinking of a number between 1 and 100...") << std::endl;
 
    do {
        std::cout << std::format("Your guess: ");
        std::cin >> guess;
        ++attempts;
 
        if (guess < secret)
            std::cout << std::format("Too low!") << std::endl;
        else if (guess > secret)
            std::cout << std::format("Too high!") << std::endl;
        else
            std::cout << std::format("Correct! You got it in {} attempts!", attempts) << std::endl;
    } while (guess != secret);
}

Exercices

Exercice 1 : Menu textuel avec validation d'entrée

// Create a menu system that:
// 1. Shows options (1-4) (1 = print a message, 2 = count until 10, 3 state current year - Use #include <ctime>, 4: Quit)
// 2. Reads user choice
// 3. Validates input (reject anything not 1-4)
// 4. Executes the chosen option
// 5. Loops until user selects "Quit"

Exercice 2 : Calculateur de factorielle Écrivez un programme qui calcule n! (n factorielle) en utilisant une boucle for.

  • 5! = 5 × 4 × 3 × 2 × 1 = 120

Exercice 3 : Suite de Fibonacci Affichez les 20 premiers nombres de Fibonacci : 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Solutions

Solution de l'exercice 1 : Menu textuel avec validation

#include <iostream>
#include <ctime>
 
void _print_menu() {
    std::cout << "\n=== MENU ===\n";
    std::cout << "1. Afficher un message\n";
    std::cout << "2. Compter jusqu'à 10\n";
    std::cout << "3. Afficher la date\n";
    std::cout << "4. Quitter\n";
    std::cout << "Votre choix (1-4) : ";
}
 
int main() {
    int choice{};
 
    do
    {
        _print_menu();
        std::cin >> choice;
 
        switch (choice)
        {
            case 1: {
                std::cout << "\nCoucou, ceci est un message !\n";
                break;
            }
            case 2: {
                for (int i{1}; i <= 10; ++i)
                    std::cout << i;
                std::cout << std::endl;
                break;
            }
            case 3: {
                std::time_t now = std::time(0);
                std::cout << "Il est : " << std::ctime(&now) << std::endl;
                break;
            }
            case 4: {
                std::cout << "Au revoir !\n";
                break;
            }
            default: {
                std::cout << "Choix invalide. Entrez un nombre entre 1 et 4.\n";
                break;
            }
        }
    }
    while (choice != 4);
}

Solution de l'exercice 2 : Calculateur de factorielle

#include <iostream>
#include <format>
 
int main()
{
    int n {};
    std::cout << "Entrez un nombre entier positif : ";
    std::cin >> n;
 
    long long factorial {1};
    for (long long i {2}; i <= n; ++i)
    {
        factorial *= i;
    }
 
    std::cout << std::format("{}! = {}", n, factorial) << std::endl;
}

Solution de l'exercice 3 : Suite de Fibonacci

#include <iostream>
#include <format>
 
int main()
{
    int count {20};
    long long a {0}, b {1};
 
    std::cout << std::format("Les {} premiers nombres de Fibonacci :", count) << std::endl;
    for (int i {0}; i < count; ++i)
    {
        std::cout << a;
        if (i < count - 1) std::cout << ", ";
 
        long long next {a + b};
        a = b;
        b = next;
    }
    std::cout << std::endl;
}

Travail personnel

Lecture

  • Lire Beginning C++23 Chapitres 3-4

Exercices

Exercice 1 : Menu textuel avec validation d’entrée

// Create a menu system that:
// 1. Shows options (1-4)
// 2. Reads user choice
// 3. Validates input (reject anything not 1-4)
// 4. Executes the chosen option
// 5. Loops until user selects "Quit"

Exercice 2 : Calculateur de factorielle
Écrivez un programme qui calcule n! (n factorielle) en utilisant une boucle for.

  • 5! = 5 × 4 × 3 × 2 × 1 = 120

Exercice 3 : Vérificateur de nombres premiers
Écrivez une fonction qui vérifie si un nombre est premier.

  • Un nombre premier n’est divisible que par 1 et par lui-même
  • Testez avec : 2, 7, 13, 15, 97

Exercice 4 : Suite de Fibonacci
Affichez les 20 premiers nombres de Fibonacci : 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Solutions

Solution de l’exercice 1 : Menu textuel avec validation

#include <iostream>
#include <format>
int main()
{
  int choice {};
 
  do {
    std::cout << std::format("\\n=== MENU ===") << std::endl;
    std::cout << std::format("1. Afficher un message") << std::endl;
    std::cout << std::format("2. Compter jusqu'à 10") << std::endl;
    std::cout << std::format("3. Afficher la date") << std::endl;
    std::cout << std::format("4. Quitter") << std::endl;
    std::cout << std::format("Votre choix (1-4) : ");
    std::cin >> choice;
 
    switch (choice) {
      case 1:
        std::cout << std::format("Bonjour, Tech Artist !") << std::endl;
        break;
      case 2:
        for (int i {1}; i <= 10; ++i)
          std::cout << std::format("{} ", i);
        std::cout << std::endl;
        break;
      case 3:
        std::cout << std::format("Nous sommes en 2025.") << std::endl;
        break;
      case 4:
        std::cout << std::format("Au revoir !") << std::endl;
        break;
      default:
        std::cout << std::format("Choix invalide ! Entrez un nombre entre 1 et 4.") << std::endl;
        break;
    }
  } while (choice != 4);
}

Solution de l’exercice 2 : Calculateur de factorielle

#include <iostream>
#include <format>
int main()
{
  int n {};
  std::cout << std::format("Entrez un nombre entier positif : ");
  std::cin >> n;
 
  long long factorial {1};
  for (int i {2}; i <= n; ++i)
  {
    factorial *= i;
  }
 
  std::cout << std::format("{}! = {}", n, factorial) << std::endl;
}

Solution de l’exercice 3 : Vérificateur de nombres premiers

#include <iostream>
#include <format>
bool is_prime(int n)
{
  if (n <= 1) return false;
  if (n <= 3) return true;
  if (n % 2 == 0 || n % 3 == 0) return false;
 
  for (int i {5}; i * i <= n; i += 6)
  {
    if (n % i == 0 || n % (i + 2) == 0)
      return false;
  }
  return true;
}
 
int main()
{
  int test_values[] {2, 7, 13, 15, 97};
  for (int val : test_values)
  {
    std::cout << std::format("{} est {}", val, is_prime(val) ? "premier" : "non premier") << std::endl;
  }
}

Solution de l’exercice 4 : Suite de Fibonacci

#include <iostream>
#include <format>
int main()
{
  const int count {20};
  long long a {0}, b {1};
 
  std::cout << std::format("Les {} premiers nombres de Fibonacci :", count) << std::endl;
  for (int i {0}; i < count; ++i)
  {
    std::cout << std::format("{}", a);
    if (i < count - 1) std::cout << std::format(", ");
 
    long long next {a + b};
    a = b;
    b = next;
  }
  std::cout << std::endl;
}