Flux de contrôle et boucles
Maîtrisez toutes les structures de flux de contrôle C++ incluant if/else, switch, toutes les formes de boucles, le for basé sur les intervalles, les initialiseurs C++17, et les liaisons structurées.
Le flux de contrôle en C++
Les instructions de flux de contrôle déterminent l'ordre dans lequel votre programme s'exécute. Le C++ fournit un riche ensemble de structures de flux de contrôle qui vont bien au-delà des bases héritées du C. Le C++ moderne (C++17 et au-delà) ajoute des fonctionnalités comme le if avec initialiseur et le constexpr if qui rendent le flux de contrôle plus expressif et plus sûr.
Un principe clé : minimiser la portée des variables. Chaque variable devrait être déclarée aussi près que possible de sa première utilisation, et dans la portée la plus étroite possible. Les fonctionnalités C++17 comme le if avec initialiseur vous aident à suivre ce principe.
if/else et switch
Les instructions if et switch sont les mécanismes principaux de branchement. C++17 ajoute des clauses d'initialisation aux deux :
#include <iostream>
#include <map>
#include <string>
#include <optional>
std::optional<int> find_value(const std::string& key) {
std::map<std::string, int> data = {{"alpha", 1}, {"beta", 2}, {"gamma", 3}};
if (auto it = data.find(key); it != data.end()) { // C++17 if with initializer
return it->second;
}
return std::nullopt;
}
int main() {
// C++17: if with initializer — the variable's scope is limited to the if/else
if (auto val = find_value("beta"); val.has_value()) {
std::cout << "Found: " << *val << '\n';
} else {
std::cout << "Not found\n";
}
// 'val' is no longer in scope here — cleaner!
// switch with C++17 initializer
enum class Color { Red, Green, Blue };
Color c = Color::Green;
switch (auto name = "unknown"; c) { // C++17 switch with initializer
case Color::Red: name = "red"; std::cout << name << '\n'; break;
case Color::Green: name = "green"; std::cout << name << '\n'; break;
case Color::Blue: name = "blue"; std::cout << name << '\n'; break;
}
// [[fallthrough]] attribute (C++17) — document intentional fallthrough
int priority = 2;
switch (priority) {
case 1:
std::cout << "Critical: ";
[[fallthrough]]; // Tells the compiler this is intentional
case 2:
std::cout << "Important: ";
[[fallthrough]];
case 3:
std::cout << "Handle it\n";
break;
default:
std::cout << "Unknown priority\n";
}
}Cliquez sur Exécuter pour compiler et lancer cet exemple.
Boucles : for, while, do-while et for basé sur les intervalles
Le C++ offre plusieurs formes de boucles. La boucle for basée sur les intervalles (C++11) est préférée pour itérer sur les conteneurs car elle élimine les erreurs de un et est plus claire :
#include <iostream>
#include <vector>
#include <string>
#include <map>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// Traditional for loop — use when you need the index
for (std::size_t i = 0; i < names.size(); ++i) {
std::cout << i << ": " << names[i] << '\n';
}
// Range-based for loop (C++11) — preferred for iteration
for (const auto& name : names) { // const auto& avoids copying
std::cout << name << '\n';
}
// Range-based for with structured bindings (C++17)
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
for (const auto& [name, score] : scores) { // Destructure the pair
std::cout << name << " scored " << score << '\n';
}
// C++20: range-based for with init-statement
for (auto v = std::vector{1, 2, 3, 4, 5}; auto elem : v) {
std::cout << elem << ' ';
}
std::cout << '\n';
// while loop — use when the number of iterations is unknown
int n = 1;
while (n <= 1024) {
std::cout << n << ' ';
n *= 2;
}
std::cout << '\n';
// do-while — body executes at least once
int input;
do {
std::cout << "Enter a positive number: ";
input = 42; // Simulated input for example
} while (input <= 0);
}Cliquez sur Exécuter pour compiler et lancer cet exemple.
Liaisons structurées (C++17)
Les liaisons structurées (C++17) vous permettent de décomposer un objet en ses parties constituantes. Elles fonctionnent avec les tableaux, les structs avec tous les membres publics, std::pair, std::tuple, et tout type supportant le protocole de liaisons structurées.
La syntaxe auto [a, b] = expr; crée des références nommées vers les composants de expr. Vous pouvez ajouter les qualificateurs const et & : const auto& [a, b] = expr; crée des références constantes sans copie.
Les liaisons structurées sont particulièrement puissantes avec l'itération sur std::map (décomposition de std::pair), les retours de fonctions utilisant std::pair ou std::tuple, et la valeur de retour de la méthode insert sur les conteneurs associatifs.
Utilisez break pour sortir d'une boucle prématurément quand vous avez trouvé ce que vous cherchez. Utilisez continue pour sauter le reste de l'itération courante et passer à la suivante. Les deux rendent les boucles plus faciles à comprendre lorsqu'ils sont utilisés judicieusement.
Préférez le for basé sur les intervalles au for basé sur les indices quand vous n'avez pas besoin de l'indice. Il élimine des classes entières de bogues : erreurs de un, utilisation de la mauvaise variable comme indice, et avertissements de comparaison signé/non signé.
Quand vous avez besoin de l'indice, envisagez d'utiliser std::size_t comme type de variable de boucle pour correspondre au type de retour de .size(), ou utilisez std::ssize() (C++20) qui retourne une taille signée.
Évitez les boucles profondément imbriquées — si vous vous retrouvez à imbriquer trois boucles ou plus, extrayez les boucles internes dans des fonctions. Cela améliore la lisibilité et la testabilité.
- Préférez les boucles for basées sur les intervalles aux boucles basées sur les indices quand l'indice n'est pas nécessaire
- Utilisez const auto& dans les boucles for basées sur les intervalles pour éviter les copies inutiles
- Utilisez les liaisons structurées pour décomposer les paires et les tuples dans les variables de boucle
- Extrayez les boucles profondément imbriquées dans des fonctions nommées pour plus de clarté
constexpr if (C++17) : branchement à la compilation
if constexpr évalue la condition à la compilation et élimine la branche non prise. C'est essentiel dans la programmation par templates où différentes branches peuvent ne pas être valides pour tous les types :
#include <iostream>
#include <type_traits>
#include <string>
template <typename T>
void describe(T value) {
std::cout << "Value: " << value;
if constexpr (std::is_integral_v<T>) {
// This branch only compiles when T is an integer type
std::cout << " (integer, " << (value % 2 == 0 ? "even" : "odd") << ")";
} else if constexpr (std::is_floating_point_v<T>) {
// This branch only compiles when T is a floating-point type
std::cout << " (floating-point)";
} else {
std::cout << " (other type)";
}
std::cout << '\n';
}
int main() {
describe(42); // Value: 42 (integer, even)
describe(3.14); // Value: 3.14 (floating-point)
describe("hello"); // Value: hello (other type)
}Cliquez sur Exécuter pour compiler et lancer cet exemple.
- Le if/switch avec initialiseur C++17 limite la portée des variables à l'instruction — utilisez-les pour garder le code propre
- Les boucles for basées sur les intervalles éliminent les erreurs de un et les problèmes de comparaison signé/non signé
- Les liaisons structurées (C++17) décomposent les paires, tuples et structs en variables nommées
- Utilisez [[fallthrough]] pour documenter le passage intentionnel entre les cas d'un switch
- if constexpr (C++17) évalue les conditions à la compilation — la branche éliminée n'est pas compilée
- Minimisez la portée des variables : déclarez les variables aussi près que possible de leur première utilisation
Quiz — Testez vos connaissances
(15 XP)1. Quel est l'avantage du `if` avec initialiseur C++17 : `if (auto x = f(); x > 0)` ?
2. Que fait `for (const auto& [key, val] : my_map)` ?
3. Qu'advient-il de la branche éliminée d'une instruction `if constexpr` ?