int i = 0;while (i < 10);{ cout << i << endl; i++;}if (x = 5) { }for (int j = 0; j < 10; j++) cout << j; cout << " done";switch (choice) { case 1: doSomething(); case 2: doOther();}
Solution :
Supprimer le ; après while (i < 10)( Sinon ça provoque une boucle infinie )
Remplacer = par == : if (x == 5)( = is assignment, == is comparison )
Ajouter des accolades : for (...) { cout << j; cout << " done"; }( only first line is in the loop (misleading indent )
Ajouter break; après doSomething(); ( missing break — falls through to case 2 )
Il manque un peu partout les std:: devant cout et endl
Fondamentaux des fonctions
Déclaration vs Définition de fonction
// Declaration (prototype) — tells the compiler "this function exists"double power(double x, int n);// Definition — provides the actual implementation. Note : a definition is also a declaration !double power(double x, int n){ double result {1.0}; if (n >= 0) { for (int i {1}; i <= n; ++i) result *= x; } else { for (int i {1}; i <= -n; ++i) result /= x; } return result;}
// This works — definition comes before the callint add(int a, int b) { return a + b;}int main() { add(2, 3); // compiler already knows about add()}
// This fails — compiler hasn't seen add() yetint main() { add(2, 3); // error: 'add' was not declared}int add(int a, int b) { return a + b;}
int add(int a, int b); // declaration only — no bodyint main() { add(2, 3); // now the compiler knows the signature}int add(int a, int b) { // definition comes later return a + b;}
Tutoriel : Calcul de puissances
// Calculating powers#include <iostream>#include <format>double power(double x, int n){ double result{ 1.0 }; if (n >= 0) { for (int i{ 1 }; i <= n; ++i) result *= x; } else // n < 0 { for (int i{ 1 }; i <= -n; ++i) result /= x; } return result;}int main(){ // Calculate powers of 8 from -3 to +3 for (int i{ -3 }; i <= 3; ++i) std::cout << std::format("{:10g}", power(8.0, i)); std::cout << std::endl;}
Sortie :0.00195312 0.015625 0.125 1 8 64 512
Pourquoi les prototypes de fonctions sont importants
// This WON'T compile — power() is used before it's defined!#include <iostream>#include <format>int main(){ for (int i {-3}; i <= 3; ++i) std::cout << std::format("{:10g}", power(8.0, i)); // ERROR: 'power' not declared std::cout << std::endl;}double power(double x, int n){ // ... definition comes too late}
Règle : Soit vous définissez les fonctions avant main(), soit vous déclarez un prototype avant main() et définissez la fonction après. Règle plus réglée : utiliser des header files (.h ou .hpp ou .hxx) et les inclure, mais on n’a pas encore vu ça :)
Overload de fonctions
Plusieurs fonctions peuvent partager le même nom si elles ont des listes de paramètres différentes :
int maximum(int a, int b) { return a > b ? a : b; }double maximum(double a, double b) { return a > b ? a : b; }// The compiler picks the right one based on argument types
Ceci dit, si la seule différence est le type de retour de la fonction, ça ne compilera pas. C’est le type ou le nombre de paramètres à la fonction overloadée qui compte.
int compute(double x); // OKdouble compute(double x); // error: cannot overload on return type alone
Règle : Les paramètres par défaut doivent aller de droite à gauche :
void func(int a, int b = 5, int c = 10); // OK// void func(int a = 5, int b, int c); // ERROR!func(3) // valide, même chose que func(3,5,10) // même chose
Ma qué, pourquoi de droite à gauche me direz vous ?
Parce que les appels de fonction en C++ associent les arguments aux paramètres de gauche à droite, par position. Le compilateur a donc besoin de savoir sans ambiguïté à quel paramètre correspond chaque argument.
Si on pouvait sauter un paramètre au milieu, le compilateur ne saurait pas ce qu’on veut dire :
// hypothétiquement, si le C++ permettait ceci (ce n'est pas le cas)void f(int a = 1, int b, int c = 3);f(10, 20); // est-ce f(10, 20, 3) ou f(1, 10, 20) ? C'est ambigu
En imposant les valeurs par défaut de droite à gauche, chaque appel est sans ambiguïté — on remplit à partir de la gauche et les valeurs par défaut complètent à partir de la droite :
void f(int a, int b = 2, int c = 3);f(10); // f(10, 2, 3)f(10, 20); // f(10, 20, 3)f(10, 20, 30); // f(10, 20, 30)
Il n’y a jamais de trou. Le C++ n’a pas d’arguments nommés (contrairement à Python avec f(c=30, a=10)), donc la position est la seule information dont le compilateur dispose.
Overload, valeurs par défaut et name mangling :
Le compilateur traite les fonctions overloadées comme des fonctions complètement distinctes. En coulisses, le C++ utilise le "name mangling", i.e. il encode les types des paramètres dans le nom du symbole. Ainsi, compute(int) pourrait devenir quelque chose comme _Z7computei et compute(double) devient _Z7computed dans le code binaire compilé. On peut d'ailleurs observer cela avec nm ou c++filt sur un fichier objet compilé.
Évidemment, c’est du C++, donc c’est bourré d’exceptions et de subtilités pas évidents, comme :
const et les références comptent — f(int&) et f(const int&) sont des overloads distincts.
Cela dit, un const de premier niveau sur un paramètre passé par valeur ne compte pas : f(int) et f(const int) ont la même signature, puisque l’appelant ne peut pas faire la différence.
Les arguments par défaut peuvent créer des ambiguïtés. Si on a f(int x, int y = 0) et f(int x), un appel à f(5) est ambigu et ne compilera pas — même si techniquement ce sont deux fonctions distinctes.
Passage de paramètres
Passage par valeur
// Failing to modify the original value of a function argument#include <iostream>double changeIt(double it); // Function prototypeint main(){ double it {5.0}; double result {changeIt(it)}; std::cout << std::format("After function execution, it = {}", it) << std::endl; // Still 5.0! std::cout << std::format("Result returned is {}", result) << std::endl; // 15.0}// Function that attempts to modify an argument and return itdouble changeIt(double it){ it += 10.0; // This modifies the COPY std::cout << std::format("Within function, it = {}", it) << std::endl; // 15.0 return it;}
Sortie :
Within function, it = 15After function execution, it = 5 ← Original unchanged!Result returned is 15
Passage par référence (&)
void doubleValue(int& x) // Note the & — this is a reference{ x = x * 2; // This modifies the ORIGINAL}int main(){ int num {5}; doubleValue(num); std::cout << std::format("{}", num) << std::endl; // 10 — the original was modified!}
Passage par référence constante (const &)
void printName(const std::string& name) // Can read, but cannot modify{ std::cout << std::format("Name: {}", name) << std::endl; // name = "Changed"; // ERROR: name is const!}
En résumé
Quand utiliser quoi :
Méthode
À utiliser quand…
Exemple
Passage par valeur
Types simples (int, double, bool) que vous n’avez pas besoin de modifier
void print(int x)
Passage par référence &
Vous devez modifier l’original
void swap(int& a, int& b)
Passage par const ref const&
Types volumineux que vous avez seulement besoin de lire
void print(const std::string& s)
Questions :
Est-ce que x, dans les cas 2 ou 3, est de type int ?
Quel est le “scope” de la variable x passée en paramètre à la fonction f?
Lab 1 : Fonction swap
#include <iostream>#include <format>void swap(int& a, int& b){ int temp {a}; a = b; b = temp;}int main(){ int x {10}, y {20}; std::cout << std::format("Before: x={}, y={}", x, y) << std::endl; swap(x, y); std::cout << std::format("After: x={}, y={}", x, y) << std::endl;}
Lab 2 : ColorLab
Fichiers multiples, Headers, struct et Passage de Paramètres
Un Laboratoire de Couleurs
On va construire un petit système de mélange de couleurs RGB. Il nous permettra de créer des couleurs, ajuster leurs composantes rouge, vert et bleu, mélanger deux couleurs ensemble, et afficher les résultats directement dans le terminal avec de la couleur. En chemin, on verra le pass-by-value, le pass-by-reference, et les const references en action.
Les couleurs RGB sont parfaites pour cet exercice : chaque composante (R, G, B) est un entier entre 0 et 255, ce qui nous oblige aussi à gérer le clamping (s’assurer qu’on ne dépasse jamais ces bornes).
Jusqu’ici, vous avez tout écrit dans un seul fichier main.cpp. Ça fonctionne bien pour les petits programmes, mais imaginez que vous avez 50 fonctions — faire défiler des milliers de lignes devient un cauchemar.
En développement C++ professionnel, on organise le code comme ceci :
Fichier
Rôle
main.cpp
Contient main() et la logique principale
colors.h
Header file — contient les déclarations (prototypes) des fonctions
colors.cpp
Source file — contient les définitions (implémentations) des fonctions
Pensez-y comme un atelier de peinture :
Le header file (.h) est le catalogue de l’atelier — il vous dit ce qui est disponible (noms des fonctions, paramètres, types de retour).
Le source file (.cpp) est l’arrière-boutique — c’est comment les choses sont réellement préparées (le corps des fonctions).
main.cpp est l’artiste — il consulte le catalogue et passe ses commandes.
Deux autres nouveaux concepts qu’on n’a pas encore vu
1 - Les struct en C++
Le problème
Sans struct, si on veut représenter une couleur avec seulement le types de base, on a, par exemple, besoin de plusieurs variables indépendantes :
Une struct regroupe des variables liées dans un type personnalisé
On accède aux membres avec .
Ça rend le code plus lisible et les fonctions plus simples
2 - Affichage de couleurs dans le terminal
On utilisera les ANSI escape codes — une fonctionnalité intégrée dans les terminaux Mac et Linux, sans aucune librairie externe. Ça nous permet d’afficher du texte coloré directement.
// \033 is interpreted as the octal escape for the ESC character (ASCII 27), which is what triggers ANSI color codes in the terminal// Exemple d'ANSI escape code pour afficher du texte en couleur RGB :int main(){ // --- Créer deux couleurs (pass by value) --- int r1 = 220, g1 = 60, b1 = 10; // Rouge Soleil std::cout << "\033[38;2;" << r1 << ";" << g1 << ";" << b1 << "m"; std::cout << "████████"; // Bloc de couleur std::cout << "\033[0m"; // Réinitialiser la couleur}
Ce que vous verrez dans le terminal cmd.exe :
☝🏼
CLion sur Windows malheureusement a un terminal qui ne supporte pas bien les couleurs. Pour bien voir le résultat, exécutez colorlab.exe, qui se trouve dans votre dosser cmake-build.debug, dans un command line (cmd.exe).
1 - Créer le projet dans CLion
Créez un nouveau dossier appelé ColorLab.
Ouvrez VSCode et “Open folder” de ColorLab
À l’intérieur de ce dossier, créez ces** fichiers** :
main.cpp
colors.h
colors.cpp
CMakeLists.txt
☝🏼
Dans CLion, ajoutez le toolchain MinGW (Default) dans Settings → Build, Execution, Deployments → Toolchains
Il se peut que vous ayez à faire un “Load CMake Project” si vous voyez le warning suivant “Project not configured” au-dessus de votre fichier CMakeLists.txt
à ce moment-là, cliquez sur Fix… et choisissez “Load CMake Project”
3 - Le Header File (colors.h)
Le header file ne contient que les déclarations (prototypes). Il dit au compilateur : « ces fonctions existent, et voici à quoi elles ressemblent. »
// colors.h// ========// Déclarations pour les fonctions du Laboratoire de Couleurs.#ifndef COLORS_H#define COLORS_H#include <string>// Une couleur RGB nommée.struct Color { std::string name; int r, g, b;};// Vérifie si les composantes sont dans la plage [0-255].// BY CONST REFERENCE — lecture seule, pas de copie de la struct.bool isValid(const Color& c);// Garde une valeur dans la plage [0, 255].// BY REFERENCE — on modifie la variable originale.void clamp(int& value);// ⚠️ PIÈGE — version cassée de clamp !// BY VALUE — la modification reste locale, l'original ne change PAS.void clampBroken(int value);// Augmente les composantes RGB d'une couleur.// 'c' est BY REFERENCE — on modifie la couleur originale.// Les amounts sont BY VALUE — un int est petit, pas besoin de référence.void add(Color& c, int redAmount, int greenAmount, int blueAmount);// Diminue les composantes RGB d'une couleur.// Même logique : BY REFERENCE pour 'c', BY VALUE pour les amounts.void remove(Color& c, int redAmount, int greenAmount, int blueAmount);// Mélange deux couleurs (version ints) — 9 paramètres !// Les 6 premiers sont BY VALUE — on ne veut pas altérer les originaux.// Les 3 derniers sont BY REFERENCE — c'est là qu'on écrit le résultat.void mixColors(int r1, int g1, int b1, int r2, int g2, int b2, int& outR, int& outG, int& outB);// Mélange deux couleurs (version struct) — OVERLOAD de la fonction ci-dessus.// Même nom, signature différente → le compilateur choisit la bonne version.// Entrées BY CONST REFERENCE — on ne modifie pas les originaux.// Retour PAR VALEUR — efficace grâce au RVO (Return Value Optimization).Color mixColors(const Color& a, const Color& b);// Affiche une couleur dans le terminal avec un bloc coloré ANSI.// BY CONST REFERENCE — efficace (évite la copie du string + ints),// et on promet de ne pas modifier la couleur.void displayColor(const Color& c);#endif // COLORS_H
C’est quoi ce #ifndef / #define / #endif ?
On appelle ça des include guards. Ils empêchent le header d’être traité plus d’une fois s’il est accidentellement inclus à plusieurs endroits. C’est une pratique standard — faites-le toujours dans vos header files.
Comment ça fonctionne :
La première fois que le compiler voit #include "colors.h", COLORS_H n’est pas encore défini.
Donc il le définit (#define COLORS_H) et traite tout ce qui se trouve à l’intérieur.
S’il rencontre #include "colors.h" une deuxième fois, COLORS_H est déjà défini, donc il saute tout. Pas de déclarations en double.
4 - Le Source File (colors.cpp)
Le source file contient les définitions (mises en œuvre). C’est ici que la vraie logique se trouve.
// colors.cpp// ==========// Définitions pour les fonctions du Laboratoire de Couleurs.#include "colors.h"#include <iostream>// ------------------------------------------------------------// isValid — vérifie que les composantes sont dans [0, 255]// 'c' est passé BY CONST REFERENCE — on lit sans copier// ------------------------------------------------------------bool isValid(const Color& c) { if (c.r < 0 || c.r > 255 || c.g < 0 || c.g > 255 || c.b < 0 || c.b > 255) { std::cout << " ATTENTION : " << c.name << " a des valeurs hors plage !" << std::endl; return false; } return true;}// ------------------------------------------------------------// clamp — garde une valeur entre 0 et 255// BY REFERENCE — modifie l'original// ------------------------------------------------------------void clamp(int& value) { if (value < 0) value = 0; if (value > 255) value = 255;}// ------------------------------------------------------------// clampBroken — ⚠️ PIÈGE : version cassée de clamp// BY VALUE — c'est une COPIE locale.// La modification ne sort jamais de cette fonction !// ------------------------------------------------------------void clampBroken(int value) { if (value < 0) value = 0; if (value > 255) value = 255; // 'value' est une copie → elle meurt ici, l'original est INCHANGÉ}// ------------------------------------------------------------// add — augmente les 3 composantes et applique le clamp// 'c' est BY REFERENCE — modifie la couleur originale// Les amounts sont BY VALUE — petit type, copie pas chère// ------------------------------------------------------------void add(Color& c, int redAmount, int greenAmount, int blueAmount) { c.r += redAmount; c.g += greenAmount; c.b += blueAmount; clamp(c.r); clamp(c.g); clamp(c.b); std::cout << " +RGB (" << redAmount << ", " << greenAmount << ", " << blueAmount << ") → R:" << c.r << " G:" << c.g << " B:" << c.b << std::endl;}// ------------------------------------------------------------// remove — diminue les 3 composantes et applique le clamp// Même logique que la fonction add// ------------------------------------------------------------void remove(Color& c, int redAmount, int greenAmount, int blueAmount) { c.r -= redAmount; c.g -= greenAmount; c.b -= blueAmount; clamp(c.r); clamp(c.g); clamp(c.b); std::cout << " -RGB (" << redAmount << ", " << greenAmount << ", " << blueAmount << ") → R:" << c.r << " G:" << c.g << " B:" << c.b << std::endl;}// ------------------------------------------------------------// mixColors (version ints) — 9 paramètres, pas très lisible...// Les 6 premiers BY VALUE — les originaux ne changent pas// Les 3 derniers BY REFERENCE — c'est là qu'on écrit le résultat// ------------------------------------------------------------void mixColors(int r1, int g1, int b1, int r2, int g2, int b2, int& outR, int& outG, int& outB) { std::cout << " Mélange (version ints)..." << std::endl; outR = (r1 + r2) / 2; outG = (g1 + g2) / 2; outB = (b1 + b2) / 2;}// ------------------------------------------------------------// mixColors (version struct) — OVERLOAD : même nom, signature différente// BY CONST REFERENCE → 2 paramètres au lieu de 9, bien plus clair// Retour PAR VALEUR — le compilateur optimise via RVO// ------------------------------------------------------------Color mixColors(const Color& a, const Color& b) { std::cout << " Mélange de " << a.name << " + " << b.name << "..." << std::endl; return Color{ a.name + " + " + b.name, (a.r + b.r) / 2, (a.g + b.g) / 2, (a.b + b.b) / 2 };}// ------------------------------------------------------------// displayColor — affiche un bloc coloré ANSI dans le terminal// BY CONST REFERENCE — évite la copie, garantit la lecture seule// ------------------------------------------------------------void displayColor(const Color& c) { std::cout << " [" << c.name << "] "; std::cout << "\033[38;2;" << c.r << ";" << c.g << ";" << c.b << "m"; std::cout << "████████"; std::cout << "\033[0m"; std::cout << " R:" << c.r << " G:" << c.g << " B:" << c.b << std::endl;}
Points importants à noter
On écrit #include "colors.h" (avec des guillemets, pas des chevrons). Les guillemets disent au compiler de chercher d’abord dans le dossier du projet. Les chevrons (< >) sont pour les headers système/standard library.
Chaque signature de fonction ici doit correspondre exactement à la déclaration dans le header.
La fonction clamp() est appelée à l’intérieur des autres fonctions — c’est un bon exemple de réutilisation de code.
5 - Le fichier principal contenant la fonction point d’entrée main() main.cpp
// main.cpp// ========// Le Laboratoire de Couleurs — là où la magie opère.#include "colors.h"#include <iostream>#ifdef _WIN32#include <windows.h>#endif#ifdef _WIN32void enableAnsiColors() { // Set console to UTF-8 SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); // Enable ANSI escape sequences HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwMode = 0; GetConsoleMode(hOut, &dwMode); SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);}#endifint main() { #ifdef _WIN32 enableAnsiColors(); #endif // --- Créer deux couleurs --- Color soleil{"Rouge Soleil", 220, 60, 10}; Color ocean{"Bleu Océan", 10, 80, 210}; displayColor(soleil); // const Color& → lecture seule, pas de copie displayColor(ocean); std::cout << std::endl; // --- Validation (const Color&) --- std::cout << "--- Test de validation ---" << std::endl; Color invalide{"Invalide", 300, -10, 50}; if (!isValid(invalide)) { std::cout << " Couleur rejetée !" << std::endl; } std::cout << std::endl; // --- PIÈGE : clampBroken vs clamp (by value vs by reference) --- std::cout << "--- clampBroken vs clamp ---" << std::endl; int testValue = 999; std::cout << " Avant : testValue = " << testValue << std::endl; clampBroken(testValue); // BY VALUE → ne modifie PAS testValue std::cout << " clampBroken : testValue = " << testValue << " (inchangé ! la copie est morte)" << std::endl; clamp(testValue); // BY REFERENCE → modifie testValue std::cout << " clamp : testValue = " << testValue << " (modifié via référence)" << std::endl; std::cout << std::endl; // --- Modifier une couleur (Color&) --- std::cout << "--- Modification de Rouge Soleil ---" << std::endl; add(soleil, 0, 80, 0); // Color& → modifie la struct originale remove(soleil, 0, 0, 5); displayColor(soleil); std::cout << std::endl; // --- Réinitialiser Rouge Soleil avant le mélange --- // Le mélange sera vraiment Rouge Soleil + Bleu Océan, pas la version modifiée soleil = Color{"Rouge Soleil", 220, 60, 10}; std::cout << "--- Rouge Soleil réinitialisé pour le mélange ---" << std::endl; displayColor(soleil); std::cout << std::endl; // --- Mélange avec ints : 9 paramètres, 3 out-references --- std::cout << "--- Mélange (version ints — 9 paramètres) ---" << std::endl; int outR, outG, outB; mixColors(soleil.r, soleil.g, soleil.b, ocean.r, ocean.g, ocean.b, outR, outG, outB); std::cout << " Résultat : R:" << outR << " G:" << outG << " B:" << outB << std::endl; std::cout << std::endl; // --- Mélange avec struct : même nom, 2 paramètres, bien plus clair --- std::cout << "--- Mélange (version struct — overload) ---" << std::endl; Color fusion = mixColors(soleil, ocean); // le compilateur choisit l'overload displayColor(fusion); std::cout << std::endl; // --- Vérifier que les originaux n'ont pas changé après les mélanges --- std::cout << "--- Originaux intacts (const& les protège) ---" << std::endl; displayColor(soleil); displayColor(ocean); std::cout << std::endl; return 0;}
Remarquez que main.cpp n’a besoin que de #include "colors.h" — il n’a pas besoin de savoir comment les fonctions fonctionnent, seulement leurs déclarations respectives.
6 - Compiler et exécuter
C’est le marteau dans CLion pour compiler, le “Play” vert pour exécuter
6.2 - Sortie attendue
☝🏼
CLion sur Windows malheureusement a un terminal qui ne supporte pas bien les couleurs. Pour bien voir le résultat, exécutez colorlab.exe, qui se trouve dans votre dosser cmake-build-debug, dans un command line (cmd.exe).
Pour ce faire, vous pouvez naviguer dans Windows Explorer vers le dossier cmake-build-debug, right-click et choisir “Open Terminal”.
Ensuite, il suffit d’exécuter colorlab.exe.
Si PowerShell se plain, exécutez en précédant colorlab.exe d’un “.\”, donc:
.\colorlab.exe puis enter