# Cours 5 - Tableaux (Arrays), Vecteurs (Vectors) et Chaînes de caractères (Strings)

Où est Charlie le Bug ? Sujets couverts : Variables, boucles, fonctions, passage de paramètres

void doubleValue(int x) {
    x = x * 2;
}
 
int add(int a, int b);
int add(int a, int b) {
    int sum = a + b;
}
 
int calculate(int x = 5, int y) { }
 
void process(bool condition) {
    int result;
    if (condition) {
        result = 10;
    }
    cout << result;
}

Solution

void doubleValue(int x) {  // Bug 1: should be reference (int& x) to modify original
    x = x * 2;
}
 
int add(int a, int b);     // Declaration
int add(int a, int b) {    // Bug 2: missing return statement!
    int sum = a + b;
}
 
int calculate(int x = 5, int y) { }  // Bug 3: default params must go right-to-left
 
void process(bool condition) {
    int result;
    if (condition) {
        result = 10;
    }
    cout << result;        // Bug 4: result may be uninitialized if condition is false
}

Tableaux fixes : std::array

Tableaux de style C (les connaître, les éviter)

int scores[5] {90, 85, 78, 92, 88};
// Problems:
// - Don't know their own size
// - Decay to pointers when passed to functions
// - No bounds checking
// - Can't be returned from functions

std::array<T, N> : Le tableau moderne de taille fixe

#include <array>
std::array<int, 5> scores {90, 85, 78, 92, 88};
scores.size();     // Returns 5
scores[0];         // Access first element (no bounds check)
scores.at(0);      // Access with bounds check (throws if out of range)

Tutoriel : Boucle for avec un tableau

// Using a for loop with an array
#include <iostream>
#include <format>
#include <array>
 
int main()
{
  std::array<unsigned, 6> height {26, 37, 47, 55, 62, 75}; // An array of heights
 
  unsigned total {};
  for (std::size_t i {} ;i < height.size(); ++i)
  {
    total += height[i];
  }
 
  const auto average{ static_cast<double>(total) / height.size() };
  std::cout << std::format("The average height is {:.1f}.", average) << std::endl;
 
  unsigned count {};
  for (std::size_t i {}; i < height.size(); ++i)
  {
    if (height[i] < average) ++count;
  }
  std::cout << std::format("{} people are below average height.", count) << std::endl;
}
 

Concepts clés std::array connaît sa propre taille — plus besoin d'une variable size séparée

  • static_cast<double>(total) empêche la division entière
  • {:.1f} formate un nombre à virgule flottante avec 1 décimale
  • mais justement, sa taille est fixe!

Obtenir la taille d'un tableau

// Obtaining the number of array elements
#include <iostream>
#include <format>
#include <array>
int main()
{
  std::array values {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};  // CTAD: std::array<int, 10>
 
  std::cout << std::format("There are {} elements in the array.", values.size()) << std::endl;
 
  int sum {};
  for (std::size_t i {}; i < values.size(); ++i)
  {
    sum += values[i];
  }
  std::cout << std::format("The sum of the array elements is {}.", sum) << std::endl;
}

Boucle for basée sur les intervalles (aperçu)

std::array values {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
for (int val : values)        // Range-based for - iterates every element
{
    std::cout << std::format("{} ", val);
}
 

Tableaux dynamiques : std::vector & std::string

std::vector : Tableaux dynamiques

#include <iostream>
#include <format>
#include <vector>
 
int main()
{
    std::vector<int> scores;          // Empty vector
    scores.push_back(90);             // Add elements
    scores.push_back(85);
    scores.push_back(78);
 
    std::cout << std::format("Size: {}", scores.size()) << std::endl;       // 3
    std::cout << std::format("First: {}", scores[0]) << std::endl;          // 90
    std::cout << std::format("Last: {}", scores.back()) << std::endl;       // 78
 
    scores.pop_back();                              // Remove last element
    std::cout << std::format("After pop, size: {}", scores.size()) << std::endl; // 2
 
    // Iterate
    for (int s : scores)
        std::cout << std::format("Score: {}", s) << std::endl;
}

**Opérations clés de ****std::vector**

MéthodeDescription
push_back(x)Ajouter un élément à la fin
pop_back()Supprimer le dernier élément
size()Nombre d’éléments
empty()Vrai si aucun élément
clear()Supprimer tous les éléments
[i]Accéder à un élément (sans vérification)
at(i)Accéder à un élément (avec vérification des bornes)
front()Premier élément
back()Dernier élément
insert()Insérer à l’index spécifié

std::string : Pas seulement des tableaux de char

// Concatenating strings
#include <iostream>
#include <format>
#include <string>
int main()
{
  std::string first;                             // Stores the first name
  std::string second;                            // Stores the second name
 
  std::cout << std::format("Enter your first name: ");
  std::cin >> first;                             // Read first name
 
  std::cout << std::format("Enter your last name: ");
  std::cin >> second;                            // Read second name
 
  std::string sentence {"Your full name is "};   // Create basic sentence
  sentence += first + " " + second + ".";        // Augment with names
 
  std::cout << std::format("{}", sentence) << std::endl;
  std::cout << std::format("The string contains {} characters.", sentence.length()) << std::endl;
}

Tutoriel : Compter les voyelles et les consonnes

// Accessing characters in a string
#include <iostream>
#include <format>
#include <string>
#include <cctype>
int main()
{
  std::string text;
  std::cout << std::format("Enter a line of text:") << std::endl;
  std::getline(std::cin, text);              // Read a line including spaces
 
  unsigned vowels {};
  unsigned consonants {};
  for (std::size_t i {}; i < text.length(); ++i)
  {
    if (std::isalpha(text[i]))               // Check for a letter
    {
      switch (std::tolower(text[i]))         // Convert to lowercase
      {
        case 'a': case 'e': case 'i': case 'o': case 'u': case 'y':                                                    
          ++vowels;
          break;
        default:
          ++consonants;
          break;
      }
    }
  }
 
  std::cout << std::format("Your input contained {} vowels and {} consonants.", vowels, consonants) << std::endl;
}

Opérations clés sur les chaînes

OpérationDescription
s.length() / s.size()Nombre de caractères
s[i]Accéder au caractère à la position i
s += "text"Ajouter à la fin
s.substr(pos, len)Extraire une sous-chaîne
s.find("text")Trouver la position d’une sous-chaîne
s.empty()Vrai si vide
std::getline(cin, s)Lire une ligne entière (espaces inclus)

std::cin >> textversus std::getline(std::cin, text)

std::cin >> texte L'opérateur d'extraction >> lit les caractères depuis l'entrée standard jusqu'au premier espace blanc (espace, tabulation ou retour à la ligne). Les espaces blancs en début de saisie sont ignorés.

std::string texte;
std::cin >> texte;
// Saisie : "Bonjour le monde"
// Résultat : texte == "Bonjour"  ← le reste est perdu

std::getline(std::cin, texte) std::getline() lit toute la ligne jusqu'au caractère de retour à la ligne '\n'. Les espaces sont conservés. Le '\n' est consommé mais n'est pas stocké dans la chaîne.

std::string texte;
std::getline(std::cin, texte);
// Saisie : "Bonjour le monde"
// Résultat : texte == "Bonjour le monde"  ✓

Tableau comparatif

Comportementstd::cin >> textestd::getline()
S’arrête à l’espace
Conserve les espaces
Ignore les espaces en début
Consomme le '\n' final

Le piège classique : mélanger les deux Lorsqu'on utilise std::cin >> avant std::getline(), un '\n' résiduel reste dans le buffer d'entrée (input buffer). std::getline() le lit immédiatement et retourne une chaîne vide.

int age;
std::string nom;
 
std::cout << "Entrez votre âge : ";
std::cin >> age;
// L'utilisateur tape "25" puis appuie sur Entrée.
// Le '25' est lu, mais le '\n' reste dans le buffer.
 
std::cout << "Entrez votre nom complet : ";
std::getline(std::cin, nom);
// std::getline() lit immédiatement le '\n' résiduel.
// Résultat : nom == ""  ← chaîne vide, la saisie est ignorée !

Pourquoi ça arrive ?

Buffer après std::cin >> age :
[ '\n' ]
    ^
    std::getline() s'arrête ici immédiatement

La solution : vider le buffer Après un std::cin >>, il faut consommer le '\n' résiduel avant d'appeler std::getline().

Option 1 — std::cin.ignore() (simple)

std::cin >> age;
std::cin.ignore(); // consomme un seul caractère (le '\n')
std::getline(std::cin, nom);

Option 2 — std::cin.ignore() robuste (recommandée)

#include <limits>
 
std::cin >> age;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// Consomme tout ce qui reste dans le buffer jusqu'au '\n' inclus
std::getline(std::cin, nom);

Cette forme est préférable si l’utilisateur a pu saisir des caractères supplémentaires après la valeur (ex. : "25 abc\n").

Exemple complet corrigé

#include <iostream>
#include <string>
#include <limits>
 
int main() {
    int age;
    std::string nom;
 
    std::cout << "Entrez votre âge : ";
    std::cin >> age;
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
 
    std::cout << "Entrez votre nom complet : ";
    std::getline(std::cin, nom);
 
    std::cout << "Âge : " << age << std::endl;
    std::cout << "Nom : " << nom << std::endl;
 
    return 0;
}

À retenir

Dès que std::cin >> et std::getline() coexistent dans un programme, il faut toujours vider le buffer entre les deux. C’est l’une des erreurs les plus fréquentes chez les débutants en C++.

☝🏼

Conseil: toujours utiliser std::getline(std::cin, my_string) plutôt que std::cin >> my_string lorsqu’il s’agit de lire une ligne de la console

ColorLab v2 - Palettes, Couleurs Web & Opérations sur les Chaînes

Prérequis : ColorLab v1 (Semaine 4 - struct Color, fonctions, fichiers multiples, pass by value/ref)

Rappel : où en sommes-nous ? Dans le ColorLab v1 (semaine 4), on a introduit struct Color pour regrouper le nom et les composantes RGB, et on a vu comment les fonctions manipulent ces structs avec le passage par référence :

Color soleil {"Rouge Soleil", 220, 60, 10};
Color ocean  {"Bleu Océan",   10, 80, 210};
Color fusion = mixColors(soleil, ocean);   // 2 paramètres au lieu de 9 !
displayColor(fusion);                      // 1 paramètre au lieu de 4 !

C’est bien plus propre qu’avec des int séparés. Mais on travaille toujours avec des couleurs individuelles. Et si on voulait gérer une palette de 12 couleurs web ? Les chercher par nom ? Les trier ? Il nous faut des collections.

L'évolution : des couleurs individuelles aux collections On a déjà notre struct Color de la semaine 4. Ce qui change cette semaine, c'est qu'on peut maintenant les organiser en collections et exploiter les opérations sur les chaînes :

  • **std::vector<Color>** — une palette dynamique qui peut grandir à volonté
  • **std::string** — on exploite find(), npos et le formatage hex pour chercher et convertir les noms de couleurs
// Semaine 4 : couleurs individuelles
Color soleil {"Rouge Soleil", 220, 60, 10};
Color ocean  {"Bleu Océan",   10, 80, 210};
 
// Semaine 5 : une palette entière !
std::vector<Color> palette = getWebColors();   // 12 couleurs d'un coup
int idx = findByName(palette, "Coral");         // recherche par nom
std::string hex = colorToHex(palette[idx]);     // "#FF7F50"

1 - Le Header File (colors.h) - v2

// colors.h - v2
// ==============
// Déclarations pour le ColorLab v2 : palettes, couleurs web, opérations sur les chaînes.
 
#ifndef COLORS_H
#define COLORS_H
 
#include <string>
#include <vector>
 
// --- struct Color (défini en semaine 4 — rappel) ---
struct Color {
    std::string name;   // Nom de la couleur
    int r {};           // Rouge  (0-255)
    int g {};           // Vert   (0-255)
    int b {};           // Bleu   (0-255)
};
 
// Garde une valeur dans la plage [0, 255].
// Paramètre BY REFERENCE - modifie l'original.
void clamp(int& value);
 
// Crée une couleur avec validation et clamping automatique.
// Retourne un Color par valeur.
Color createColor(const std::string& name, int r, int g, int b);
 
// Affiche une couleur avec un bloc ANSI coloré dans le terminal.
// Paramètre BY CONST REFERENCE - lecture seule, efficace.
void displayColor(const Color& color);
 
// Calcule la luminosité moyenne pondérée d'une couleur (0–255).
// Paramètre BY CONST REFERENCE.
int brightness(const Color& color);
 
// Mélange deux couleurs et retourne le résultat.
// Les deux couleurs d'entrée sont BY CONST REFERENCE - on ne les modifie pas.
Color mixColors(const Color& a, const Color& b, const std::string& mixName);
 
// --- NOUVEAU v2 : fonctions qui utilisent std::vector et std::string ---
 
// Affiche toutes les couleurs d'une palette.
// Le vector est passé BY CONST REFERENCE - on ne modifie pas la palette.
void displayPalette(const std::vector<Color>& palette);
 
// Cherche une couleur par nom exact dans la palette.
// Retourne l'index si trouvée, -1 sinon.
int findByName(const std::vector<Color>& palette, const std::string& name);
 
// Cherche toutes les couleurs dont le nom contient une sous-chaîne.
// Utilise std::string::find() — retourne un vector d'indices.
std::vector<int> findByPartialName(const std::vector<Color>& palette,
                                   const std::string& partial);
 
// Convertit une couleur en chaîne hexadécimale CSS : "#RRGGBB".
// Utilise std::format pour le formatage hexadécimal.
std::string colorToHex(const Color& color);
 
// Retourne une palette de couleurs web standard prédéfinies.
std::vector<Color> getWebColors();
 
#endif // COLORS_H

Ce qui a changé par rapport à v1

Aspectv1 (semaine 4)v2 (semaine 5)
struct ColorIntroduit (name, r, g, b)Réutilisé tel quel
CollectionsCouleurs individuellesstd::vector<Color> — palettes dynamiques
Couleurs prédéfiniesAucune12 couleurs web (getWebColors())
RechercheNon disponiblefindByName(), findByPartialName()
Opérations sur chaînesstd::string pour les nomsstring::find(), formatage hex
Format de sortiestd::cout << chaînéstd::format partout
ItérationPas de collection à itérerRange-based for sur la palette

2 - Le Source File (colors.cpp) - v2

// colors.cpp - v2
// ================
// Définitions pour le ColorLab v2 : palettes, recherche, hex.
 
#include "colors.h"
#include <iostream>
#include <format>
 
// ------------------------------------------------------------
// clamp - inchangé depuis v1
// ------------------------------------------------------------
void clamp(int& value) {
    if (value < 0)   value = 0;
    if (value > 255) value = 255;
}
 
// ------------------------------------------------------------
// createColor - factory function avec clamping automatique
// ------------------------------------------------------------
Color createColor(const std::string& name, int r, int g, int b) {
    clamp(r);
    clamp(g);
    clamp(b);
    return Color{name, r, g, b};
}
 
// ------------------------------------------------------------
// displayColor - affiche un bloc coloré ANSI
// BY CONST REFERENCE - on ne modifie pas la couleur
// ------------------------------------------------------------
void displayColor(const Color& color) {
    std::cout << std::format("  [{:<20s}] ", color.name);
    std::cout << "\033[38;2;" << color.r << ";" << color.g << ";" << color.b << "m";
    std::cout << "████████";
    std::cout << "\033[0m";
    std::cout << std::format("   R:{:<3d}  G:{:<3d}  B:{:<3d}", color.r, color.g, color.b);
    std::cout << std::endl;
}
 
// ------------------------------------------------------------
// brightness - luminosité moyenne pondérée 
// The standard formula is a **weighted average**, most commonly the ITU-R BT.709 luma coefficient used in modern displays:
//
// Y = 0.2126·R + 0.7152·G + 0.0722·B
// The weights reflect the fact that the human eye is **far more sensitive to green** than to red, and barely sensitive to blue. This is due to the distribution of cone cells in the retina — the M (medium) cones responsible for green perception are the most numerous.
// BY CONST REFERENCE - lecture seule
// ------------------------------------------------------------
int brightness(const Color& color) {
    return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
}
 
// ------------------------------------------------------------
// mixColors - prend 2 Color, retourne un Color
// BY CONST REFERENCE pour les entrées
// ------------------------------------------------------------
Color mixColors(const Color& a, const Color& b, const std::string& mixName) {
    return Color{mixName, (a.r + b.r) / 2, (a.g + b.g) / 2, (a.b + b.b) / 2};
}
 
// ============================================================
// NOUVEAU v2 : fonctions qui exploitent std::vector et std::string
// ============================================================
 
// ------------------------------------------------------------
// displayPalette - affiche toutes les couleurs d'un vector
// Le vector est passé BY CONST REFERENCE - efficace et sûr
// On utilise un range-based for pour itérer
// ------------------------------------------------------------
void displayPalette(const std::vector<Color>& palette) {
    std::cout << std::format("=== Palette ({} couleurs) ===", palette.size()) << std::endl;
    for (const Color& c : palette) {
        displayColor(c);
    }
}
 
// ------------------------------------------------------------
// findByName - recherche linéaire par nom exact
// Retourne l'index ou -1 si non trouvée
// ------------------------------------------------------------
int findByName(const std::vector<Color>& palette, const std::string& name) {
    for (std::size_t i {0}; i < palette.size(); ++i) {
        if (palette[i].name == name) {
            return static_cast<int>(i);
        }
    }
    return -1;
}
 
// ------------------------------------------------------------
// findByPartialName - recherche par sous-chaîne
// Utilise string::find() pour chercher un fragment dans chaque nom.
// Si find() retourne std::string::npos, le fragment n'a pas été trouvé.
// Retourne un vector d'indices — peut être vide si rien ne correspond.
// ------------------------------------------------------------
std::vector<int> findByPartialName(const std::vector<Color>& palette,
                                   const std::string& partial) {
    std::vector<int> results;
    for (std::size_t i {0}; i < palette.size(); ++i) {
        if (palette[i].name.find(partial) != std::string::npos) {
            results.push_back(static_cast<int>(i));
        }
    }
    return results;
}
 
// ------------------------------------------------------------
// colorToHex - convertit une couleur en string hexadécimale CSS
// Utilise std::format avec {:02X} pour formater chaque composante
// en 2 chiffres hexadécimaux majuscules (00-FF).
// Retourne une std::string par valeur.
// ------------------------------------------------------------
std::string colorToHex(const Color& color) {
    return std::format("#{:02X}{:02X}{:02X}", color.r, color.g, color.b);
}
 
// ------------------------------------------------------------
// getWebColors - retourne une palette de couleurs web connues
// Les noms viennent du standard CSS/SVG - vos étudiants les
// connaissent peut-être déjà du web !
// ------------------------------------------------------------
std::vector<Color> getWebColors() {
    return {
        {"Teal",               0, 128, 128},
        {"Salmon",           250, 128, 114},
        {"Coral",            255, 127,  80},
        {"Goldenrod",        218, 165,  32},
        {"Orchid",           218, 112, 214},
        {"SteelBlue",         70, 130, 180},
        {"Tomato",           255,  99,  71},
        {"MediumAquamarine", 102, 205, 170},
        {"SlateGray",        112, 128, 144},
        {"Crimson",          220,  20,  60},
        {"LimeGreen",         50, 205,  50},
        {"DarkOrange",       255, 140,   0}
    };
}

Points importants à noter

  • **std::vector<Color>** peut contenir autant de couleurs qu’on veut, et on peut en ajouter dynamiquement avec push_back().
  • **Range-based ****for** : for (const Color& c : palette) est bien plus lisible que for (int i = 0; i < palette.size(); ++i).
  • **string::find()** retourne la position de la sous-chaîne trouvée, ou std::string::npos si elle n’existe pas. C’est le patron classique pour chercher dans une chaîne.
  • **std::format("{:02X}", value)** formate un entier en hexadécimal (X) sur au moins 2 caractères (02), avec des zéros à gauche. Parfait pour les codes couleur CSS.
  • **findByPartialName()** retourne un std::vector<int> — un vector qu’on construit dans la fonction avec push_back() et qu’on retourne par valeur. C’est un bon exemple d’utilisation dynamique d’un vector.

3 - main.cpp - v2

// main.cpp - ColorLab v2
// =======================
// Palettes, couleurs web, recherche par nom et codes hex.
 
#include "colors.h"
#include <iostream>
#include <format>
 
#ifdef _WIN32
#include <windows.h>
#endif
 
#ifdef _WIN32
void 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);
}
#endif
 
 
int main() {
 
    #ifdef _WIN32
    enableAnsiColors();
    #endif
    
    std::cout << "=== ColorLab v2 - Palettes & Couleurs Web ===" << "\n\n";
 
    // --- Créer des couleurs avec notre struct (rappel semaine 4) ---
    Color soleil = createColor("Rouge Soleil", 220, 60, 10);
    Color ocean  = createColor("Bleu Océan",   10, 80, 210);
 
    std::cout << "--- Nos couleurs personnalisées ---" << std::endl;
    displayColor(soleil);
    displayColor(ocean);
    std::cout << std::endl;
 
    // --- Mélanger deux couleurs ---
    Color fusion = mixColors(soleil, ocean, "Fusion");
    std::cout << "--- Mélange ---" << std::endl;
    displayColor(fusion);
    std::cout << std::format("  Luminosité de {} : {}/255",
                 fusion.name, brightness(fusion)) << "\n\n";
 
    // --- Charger la palette de couleurs web ---
    std::vector<Color> palette = getWebColors();
    displayPalette(palette);
    std::cout << std::endl;
 
    // --- Conversion en hexadécimal (std::string + std::format) ---
    std::cout << "--- Codes hexadécimaux ---" << std::endl;
    for (const Color& c : palette) {
        std::cout << std::format("  {:<20s} → {}", c.name, colorToHex(c)) << "\n";
    }
    std::cout << std::endl;
 
    // --- Rechercher une couleur par nom exact ---
    std::string recherche {"Coral"};
    int index = findByName(palette, recherche);
    if (index >= 0) {
        std::cout << std::format("'{}' trouvé à l'index {} :",
                     recherche, index) << "\n";
        displayColor(palette[index]);
        std::cout << std::format("  Code hex : {}", colorToHex(palette[index])) << "\n";
    } else {
        std::cout << std::format("'{}' non trouvé dans la palette.",
                     recherche) << "\n";
    }
    std::cout << std::endl;
 
    // --- Recherche partielle (string::find + vector retourné) ---
    std::cout << "--- Recherche partielle : couleurs contenant 'Dark' ou 'Green' ---"
              << std::endl;
    std::vector<int> darkColors = findByPartialName(palette, "Dark");
    std::vector<int> greenColors = findByPartialName(palette, "Green");
 
    std::cout << std::format("  '{}' : {} résultat(s)", "Dark", darkColors.size()) << "\n";
    for (int idx : darkColors) {
        displayColor(palette[idx]);
    }
 
    std::cout << std::format("  '{}' : {} résultat(s)", "Green", greenColors.size()) << "\n";
    for (int idx : greenColors) {
        displayColor(palette[idx]);
    }
    std::cout << std::endl;
 
    // --- Ajouter nos couleurs personnalisées à la palette ---
    palette.push_back(soleil);
    palette.push_back(ocean);
    palette.push_back(fusion);
    std::cout << std::format("Palette mise à jour : {} couleurs au total.",
                 palette.size()) << "\n\n";
 
    // --- Afficher les luminosités de toute la palette ---
    std::cout << "=== Luminosités ===" << std::endl;
    for (const Color& c : palette) {
        std::cout << std::format("  {:<20s} : {}/255",
                     c.name, brightness(c)) << "\n";
    }
 
    std::cout << "\n=== Séance de Labo Terminée ===" << std::endl;
    return 0;
}

Compiler et exécuter Compilez dans CLion (Build), l’icône Marteau

Exécutez dans un cmd.exe

5 - 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).

☝🏼

Remarque sur Windows, avez-vous remarqué qu’il y a un espace de trop dans l’espace réservé au nom “Rouge Soleil”?

Le piège UTF-8 et std::format en C++

Le problème

Quand on utilise std::format avec un padding fixe sur des chaînes contenant des caractères accentués (é, è, ê, etc.), l’alignement se décale sur Windows.

std::cout << std::format("  [{:<20s}] ", color.name);

{:<20s} remplit jusqu’à 20 octets, pas 20 caractères visuels. En UTF-8, un caractère accentué comme é occupe 2 octets au lieu de 1. Résultat : std::format croit que la chaîne est plus longue qu’elle ne l’est visuellement, et ajoute moins d’espaces.

Exemple concret

NomCaractères visuelsOctets UTF-8Décalage
Rouge Soleil1212aucun
Bleu Océan1011 (é = 2 octets)1 espace en moins

Sur Mac, le terminal compense souvent ce décalage. Sur Windows, non.

La solution

On compte les octets « en trop » dus aux caractères multi-octets, puis on augmente la largeur de padding en conséquence.

void displayColor(const Color& color) {
    // Compter la longueur visuelle (en caractères, pas en octets)
    int visualLen = 0;
    for (std::size_t i = 0; i < color.name.size(); ) {
        unsigned char ch = color.name[i];
        if      (ch < 0x80) { i += 1; }   // ASCII : 1 octet
        else if (ch < 0xE0) { i += 2; }   // 2 octets (accents latins)
        else if (ch < 0xF0) { i += 3; }   // 3 octets (CJK, symboles)
        else                { i += 4; }    // 4 octets (emojis)
        visualLen++;
    }
 
    // Ajuster le padding : on ajoute la différence octets - caractères
    int padding = 20 + (static_cast<int>(color.name.size()) - visualLen);
 
    std::cout << std::format("  [{:<{}s}] ", color.name, padding);
    // ... reste de l'affichage
}

Pourquoi ça marche

Pour "Bleu Océan" :

  • color.name.size()11 (octets)
  • visualLen10 (caractères)
  • padding20 + (11 - 10) = 21

std::format remplit jusqu’à 21 octets, ce qui donne visuellement 20 colonnes — alignement parfait.

Rappel : encodage UTF-8

PlageOctetsExemples
U+0000 – U+007F1A, z, 0, !
U+0080 – U+07FF2é, è, ñ, ü
U+0800 – U+FFFF3中, █, →
U+10000 – U+10FFFF4😀, 🎨

En résumé:

std::format avec {:<20s} remplit jusqu’à ce que la chaîne fasse 20 octets. Comme "Bleu Océan" fait déjà 11 octets (à cause du é), std::format n’ajoute que 9 espaces au lieu de 10. Visuellement, la chaîne ne fait que 10 caractères + 9 espaces = 19 colonnes au lieu de 20.

Donc "Bleu Océan" prend plus de place en mémoire (11 octets), mais moins de place à l’écran (19 colonnes) — c’est justement le piège. Le terminal affiche par colonnes visuelles, std::format compte en octets. Le é « mange » un espace de padding.

Récapitulatif : qu'avons-nous appris ?

ConceptOù on l’a vu
**std::vector<T>**std::vector<Color> palette — palette dynamique
**push_back()**Ajouter des couleurs à la palette
**.size()**Nombre de couleurs dans la palette
**Range-based ****for**for (const Color& c : palette)
**Retour d’un ****vector**findByPartialName() construit et retourne un std::vector<int>
**string::find()**Chercher une sous-chaîne dans le nom d’une couleur
**std::string::npos**Valeur retournée par find() quand la sous-chaîne n’existe pas
**std::format("{:02X}")**Formatage hexadécimal pour colorToHex()
Recherche linéairefindByName() — parcourir un vector pour trouver un élément
Recherche par sous-chaînefindByPartialName() — combiner string::find() et vector

Erreurs courantes à surveiller 1. Accéder à un index invalide dans un vector

std::vector<Color> palette = getWebColors();
displayColor(palette[99]);  // DANGER - dépasse la taille ! Comportement indéfini.
// Utilisez .at(99) pour obtenir une exception au lieu d'un crash silencieux

2. Oublier **&** dans le range-based for

for (Color c : palette) {          // COPIE chaque couleur à chaque itération !
for (const Color& c : palette) {   // Référence - pas de copie (préféré)

3. Comparaison de strings sensible à la casse

if (palette[i].name == "Coral")       // OK - comparaison exacte
if (palette[i].name == "coral")       // FAUX - C++ est sensible à la casse !
// Même chose pour find() : "dark" ne trouvera pas "DarkOrange"

**4. Oublier de vérifier **npos** après ****find()**

// FAUX - find() retourne std::string::npos (pas -1 !) si non trouvé
if (palette[i].name.find("Dark") >= 0) { ... }
 
// CORRECT - comparer avec npos
if (palette[i].name.find("Dark") != std::string::npos) { ... }

5. Utiliser un vector retourné sans vérifier s’il est vide

std::vector<int> results = findByPartialName(palette, "Purple");
displayColor(palette[results[0]]);  // DANGER si results est vide !
 
// CORRECT - vérifier d'abord
if (!results.empty()) {
    displayColor(palette[results[0]]);
}

Travail personnel

Lecture

  • Lire Beginning C++23 Chapitres 6-7

Défi 1 - Tri par luminosité

Ajoutez une fonction sortByBrightness qui trie une palette du plus sombre au plus lumineux. Vous pouvez utiliser un algorithme de tri simple (selection sort) - on verra std::sort dans les semaines suivantes.

// Déclaration dans colors.h :
void sortByBrightness(std::vector<Color>& palette);

Affichez la palette avant et après le tri pour vérifier le résultat.

Défi 2 - Couleur la plus proche

Écrivez une fonction qui, étant donné des valeurs R, G, B quelconques, trouve la couleur la plus proche dans la palette (par distance de Manhattan) :

// Déclaration dans colors.h :
int findClosest(const std::vector<Color>& palette, int r, int g, int b);

La « distance » entre deux couleurs se calcule comme :
distance = |r1-r2| + |g1-g2| + |b1-b2|

Défi 3 - Gradient entre deux couleurs de la palette (avancé)

Ajoutez une fonction displayGradient qui affiche une transition progressive entre deux couleurs dans le terminal. Combinez-la avec findByName() pour chercher les couleurs dans la palette :

// Déclaration dans colors.h :
void displayGradient(const Color& from, const Color& to, int steps);

Testez en affichant un gradient entre “Crimson” et “LimeGreen” de votre palette web. Utilisez std::lerp de <cmath> (C++20) pour interpoler chaque composante.

Solutions des défis

⚠️ Essayez d’abord par vous-même avant de regarder !

Solution - Défi 1 Déclaration dans **colors.h** :

void sortByBrightness(std::vector<Color>& palette);

Définition dans **colors.cpp** :

// ------------------------------------------------------------
// sortByBrightness - selection sort par luminosité
// La palette est passée BY REFERENCE - on la modifie directement
// ------------------------------------------------------------
void sortByBrightness(std::vector<Color>& palette) {
    for (std::size_t i {0}; i < palette.size(); ++i) {
        std::size_t minIdx {i};
        for (std::size_t j {i + 1}; j < palette.size(); ++j) {
            if (brightness(palette[j]) < brightness(palette[minIdx])) {
                minIdx = j;
            }
        }
        if (minIdx != i) {
            Color temp = palette[i];
            palette[i] = palette[minIdx];
            palette[minIdx] = temp;
        }
    }
}

Utilisation dans **main.cpp** :

std::cout << "=== Avant le tri ===" << std::endl;
displayPalette(palette);
 
sortByBrightness(palette);
 
std::cout << "\\n=== Après tri par luminosité ===" << std::endl;
displayPalette(palette);

Solution - Défi 2 Déclaration dans **colors.h** :

int findClosest(const std::vector<Color>& palette, int r, int g, int b);

Définition dans **colors.cpp** :

#include <cstdlib>   // pour std::abs
 
// ------------------------------------------------------------
// findClosest - trouve la couleur la plus proche par distance de Manhattan
// La palette est BY CONST REFERENCE - on ne la modifie pas
// ------------------------------------------------------------
int findClosest(const std::vector<Color>& palette, int r, int g, int b) {
    int bestIndex {-1};
    int bestDistance {999999};
 
    for (std::size_t i {0}; i < palette.size(); ++i) {
        int distance = std::abs(palette[i].r - r)
                     + std::abs(palette[i].g - g)
                     + std::abs(palette[i].b - b);
        if (distance < bestDistance) {
            bestDistance = distance;
            bestIndex = static_cast<int>(i);
        }
    }
    return bestIndex;
}

Utilisation dans **main.cpp** :

std::cout << "--- Couleur la plus proche de (200, 50, 50) ---" << std::endl;
int closest = findClosest(palette, 200, 50, 50);
if (closest >= 0) {
    std::cout << std::format("  C'est '{}' !", palette[closest].name) << std::endl;
    displayColor(palette[closest]);
}

Solution - Défi 3 Déclaration dans **colors.h** :

void displayGradient(const Color& from, const Color& to, int steps);

Définition dans **colors.cpp** :

#include <cmath>   // pour std::lerp (C++20)
 
// ------------------------------------------------------------
// displayGradient - affiche une ligne de blocs colorés
// interpolés entre deux couleurs
// ------------------------------------------------------------
void displayGradient(const Color& from, const Color& to, int steps) {
    std::cout << std::format("  [{}] → [{}]", from.name, to.name) << std::endl;
    std::cout << "  ";
 
    for (int i {0}; i < steps; ++i) {
        float t = static_cast<float>(i) / (steps - 1);
 
        int r = static_cast<int>(std::lerp(static_cast<float>(from.r),
                                           static_cast<float>(to.r), t));
        int g = static_cast<int>(std::lerp(static_cast<float>(from.g),
                                           static_cast<float>(to.g), t));
        int b = static_cast<int>(std::lerp(static_cast<float>(from.b),
                                           static_cast<float>(to.b), t));
 
        std::cout << "\\033[38;2;" << r << ";" << g << ";" << b << "m" << "██";
    }
 
    std::cout << "\\033[0m" << std::endl;
}

Utilisation dans **main.cpp** :

int crimsonIdx = findByName(palette, "Crimson");
int limeIdx    = findByName(palette, "LimeGreen");
 
if (crimsonIdx >= 0 && limeIdx >= 0) {
    std::cout << "--- Gradient ---" << std::endl;
    displayGradient(palette[crimsonIdx], palette[limeIdx], 20);
}

Point clé : Remarquez comment findByName() et displayGradient() travaillent ensemble : on cherche par nom dans la palette, puis on passe les couleurs trouvées au gradient. C’est la puissance des collections + fonctions combinées.