Module 01 : Allocation mémoire, Références et Pointeurs
Concepts clés :
- Allocation Stack vs Heap
- Opérateurs
newetdelete - Références vs Pointeurs
- Pointeurs vers membres
- Instructions
switch
Pourquoi c’est important
Section intitulée « Pourquoi c’est important »Comprendre la mémoire est crucial pour écrire du code C++ correct et efficace. Voici pourquoi ces concepts sont importants :
Pourquoi Stack vs Heap ?
Section intitulée « Pourquoi Stack vs Heap ? »La stack est rapide mais limitée—comme un petit bureau où vous travaillez sur la tâche en cours. Le heap est plus grand mais nécessite une gestion manuelle—comme un entrepôt où vous stockez des objets à long terme.
Pourquoi les Références plutôt que les Pointeurs ?
Section intitulée « Pourquoi les Références plutôt que les Pointeurs ? »Les références sont des alias plus sûrs pour des variables existantes. Elles ne peuvent pas être null et ne peuvent pas être réassignées. Utilisez-les quand vous devez passer de grands objets efficacement sans copie.
Pourquoi new/delete plutôt que malloc/free ?
Section intitulée « Pourquoi new/delete plutôt que malloc/free ? »En C++, new et delete font plus qu’allouer de la mémoire—ils appellent les constructeurs et destructeurs. Cela garantit que les objets sont correctement initialisés et nettoyés.
Comprendre les nouveaux opérateurs de ce module
Section intitulée « Comprendre les nouveaux opérateurs de ce module »Ce module introduit les opérateurs de gestion mémoire. Voici ce qu’ils signifient :
L’opérateur new
Section intitulée « L’opérateur new »int* ptr = new int; // Alloue la mémoire pour un intint* arr = new int[10]; // Alloue la mémoire pour 10 intsnewalloue de la mémoire sur le heap (aussi appelé “free store”)- Il retourne un pointeur vers la mémoire allouée
- La mémoire allouée avec
newpersiste jusqu’à ce que vous ladeleteexplicitement - Contrairement à
malloc(),newcalcule automatiquement la taille et initialise correctement les objets
L’opérateur delete
Section intitulée « L’opérateur delete »delete ptr; // Libère la mémoire pour un objetdelete[] arr; // Libère la mémoire pour un tableau (NOTEZ les [] !)deletelibère la mémoire qui a été allouée avecnewdelete[](avec crochets) libère les tableaux alloués avecnew[]- CRITIQUE : Utiliser
deletesur un tableau est un comportement indéfini ! - CRITIQUE : Utiliser
delete[]sur un objet unique est un comportement indéfini !
Références avec &
Section intitulée « Références avec & »int& ref = x; // ref est un alias (un autre nom) pour x&après un type crée une référence (pas un pointeur)- Une référence est juste un autre nom pour une variable existante
- Quand vous changez
ref, vous changezx- c’est la même variable ! - Les références doivent être initialisées à la déclaration
- Les références ne peuvent pas être réassignées pour référencer une variable différente
Rappel de la syntaxe des pointeurs
Section intitulée « Rappel de la syntaxe des pointeurs »int* ptr = &x; // ptr stocke l'adresse de x (ptr "pointe vers" x)*ptr = 42; // Déréférence ptr pour accéder/modifier xptr->method(); // Si ptr pointe vers un objet, appelle sa méthode- Rappel :
*avant un pointeur le déréférence (accède à la valeur) &avant une variable obtient son adresse->est un raccourci pour déréférencement + accès membre
L’opérateur ->* (Pointeur vers membre)
Section intitulée « L’opérateur ->* (Pointeur vers membre) »void (Class::*ptr)() = &Class::method; // ptr pointe vers une fonction membre(object.*ptr)(); // Appel via objet(objectPtr->*ptr)(); // Appel via pointeur vers objet->*combine l’opérateur flèche avec le pointeur-vers-membre- Il est utilisé quand vous avez un pointeur vers un objet ET un pointeur vers l’un de ses membres
- C’est une syntaxe avancée utilisée pour les pointeurs de fonction vers des fonctions membres
Comprendre la disposition mémoire (Guide visuel)
Section intitulée « Comprendre la disposition mémoire (Guide visuel) »Comprendre comment la mémoire est organisée vous aide à écrire un meilleur code C++ :
Vue d’ensemble de la disposition mémoire
Section intitulée « Vue d’ensemble de la disposition mémoire »┌─────────────────────────────────────┐│ MÉMOIRE HAUTE ││ ││ ┌──────────────────────────────┐ ││ │ HEAP │ │ ← Allocation dynamique (new/delete)│ │ (croît vers le bas) │ │ Grand, plus lent, nettoyage manuel│ │ [Zombie* z] │ ││ │ [Brain* b] ← new Brain() │ ││ └──────────────────────────────┘ ││ ││ ↓ écart ↓ ││ ││ ┌──────────────────────────────┐ ││ │ STACK │ │ ← Allocation automatique│ │ (croît vers le haut) │ │ Rapide, petit, automatique│ │ │ ││ │ ┌────────────────────┐ │ ││ │ │ main() │ │ ││ │ │ int x = 42; │ │ ││ │ │ Zombie z; │ ← constructeur appelé│ │ │ │ │ ││ │ └────────────────────┘ │ ││ └──────────────────────────────┘ ││ ││ ┌──────────────────────────────┐ ││ │ SEGMENT DATA │ │ ← Variables globales/statiques│ └──────────────────────────────┘ ││ ││ ┌──────────────────────────────┐ ││ │ SEGMENT CODE │ │ ← Instructions du programme│ └──────────────────────────────┘ ││ ││ MÉMOIRE BASSE │└─────────────────────────────────────┘Disposition du cadre de pile
Section intitulée « Disposition du cadre de pile »┌─────────────────────────────────────┐│ Cadre de pile pour function() │├─────────────────────────────────────┤│ Adresse de retour │ ← Où revenir├─────────────────────────────────────┤│ Registres sauvegardés │ ← État précédent├─────────────────────────────────────┤│ Variables locales : ││ int x = 42; → [ 42 ] ││ Zombie z; → [ objet ] │├─────────────────────────────────────┤│ Paramètres de fonction │└─────────────────────────────────────┘ ↑ La pile croît VERS LE HAUTObjet en mémoire
Section intitulée « Objet en mémoire »┌─────────────────────────────────────┐│ Objet Contact sur la STACK │├─────────────────────────────────────┤│ vptr (caché) → vtable │ (seulement si la classe a des fonctions virtual)├─────────────────────────────────────┤│ std::string _firstName ││ ├─ pointeur vers mémoire heap ││ └─ info taille/capacité │├─────────────────────────────────────┤│ std::string _lastName │├─────────────────────────────────────┤│ int _age = 25; → [ 25 ] │└─────────────────────────────────────┘1. Mémoire Stack vs Heap
Section intitulée « 1. Mémoire Stack vs Heap »Allocation Stack (Automatique)
Section intitulée « Allocation Stack (Automatique) »void function() { int x = 42; // Sur la stack Zombie zombie; // Sur la stack} // Automatiquement détruit iciCaractéristiques :
- Allocation/désallocation rapide
- Taille limitée (~1-8 Mo typiquement)
- Nettoyage automatique quand la portée se termine
- Ne peut pas survivre à la fonction
Allocation Heap (Dynamique)
Section intitulée « Allocation Heap (Dynamique) »void function() { int* x = new int(42); // Sur le heap Zombie* zombie = new Zombie(); // Sur le heap
// ... les utiliser ...
delete x; // Doit supprimer manuellement delete zombie; // Doit supprimer manuellement}Caractéristiques :
- Allocation plus lente
- Grande capacité (limitée par la RAM)
- Doit être libérée manuellement
- Peut survivre à la fonction (retourner le pointeur)
2. Opérateurs new et delete
Section intitulée « 2. Opérateurs new et delete »Objet unique
Section intitulée « Objet unique »// Allocationint* ptr = new int; // Non initialiséint* ptr = new int(); // Initialisé à zéroint* ptr = new int(42); // Initialisé à 42
// Désallocationdelete ptr;ptr = NULL; // Bonne pratique (C++98)Tableaux
Section intitulée « Tableaux »// Allocationint* arr = new int[10]; // Tableau de 10 intsZombie* horde = new Zombie[5]; // Tableau de 5 Zombies
// Désallocation - NOTEZ LES []delete[] arr; // DOIT utiliser delete[] pour les tableauxdelete[] horde;Erreurs courantes
Section intitulée « Erreurs courantes »// FAUX : Utiliser delete sur un tableauint* arr = new int[10];delete arr; // COMPORTEMENT INDÉFINI !
// FAUX : Utiliser delete[] sur un objet uniqueint* ptr = new int(42);delete[] ptr; // COMPORTEMENT INDÉFINI !
// FAUX : Double deleteint* ptr = new int(42);delete ptr;delete ptr; // CRASH ou corruption !Quand utiliser Stack vs Heap
Section intitulée « Quand utiliser Stack vs Heap »| Utiliser Stack quand… | Utiliser Heap quand… |
|---|---|
| Durée de vie de l’objet = portée fonction | L’objet doit survivre à la fonction |
| Taille connue à la compilation | Taille déterminée à l’exécution |
| Petits objets | Grands objets |
| Performance critique | Retourner de nouveaux objets |
3. Références
Section intitulée « 3. Références »Qu’est-ce qu’une référence ?
Section intitulée « Qu’est-ce qu’une référence ? »Une référence est un alias - un autre nom pour une variable existante.
int x = 42;int& ref = x; // ref EST x (pas une copie, pas un pointeur)
ref = 100; // Change x à 100std::cout << x; // Affiche 100Références vs Pointeurs
Section intitulée « Références vs Pointeurs »| Caractéristique | Référence | Pointeur |
|---|---|---|
| Syntaxe | int& ref = x; | int* ptr = &x; |
| Peut être null | Non | Oui |
| Peut être réassigné | Non | Oui |
| Doit être initialisé | Oui | Non |
| Accéder à la valeur | ref | *ptr |
| Accéder à l’adresse | &ref | ptr |
Règles clés pour les références
Section intitulée « Règles clés pour les références »// DOIT être initialiséeint& ref; // ERREUR : les références doivent être initialiséesint& ref = x; // OK
// NE PEUT PAS être nullint& ref = NULL; // ERREUR : ne peut pas lier à null
// NE PEUT PAS être réassignéeint& ref = x;ref = y; // Ceci ne réassigne pas ref, ça copie y dans x !Pourquoi les références existent
Section intitulée « Pourquoi les références existent »// MAUVAIS : Passage par valeur (copie tout l'objet)void printZombie(Zombie z) { // z est une COPIE - coûteux pour les grands objets}
// MIEUX : Passage par pointeurvoid printZombie(Zombie* z) { if (z != NULL) // Doit vérifier null std::cout << z->getName();}
// MEILLEUR : Passage par référencevoid printZombie(Zombie& z) { // z est l'objet original - pas de copie // Ne peut pas être null - pas de vérification nécessaire std::cout << z.getName();}
// Référence CONST - accès en lecture seulevoid printZombie(const Zombie& z) { std::cout << z.getName(); // Peut lire // z.setName("X"); // ERREUR : z est const}Quand utiliser quoi
Section intitulée « Quand utiliser quoi »| Situation | Utiliser |
|---|---|
| Jamais null, ne changera pas ce qu’il référence | Référence |
| Pourrait être null | Pointeur |
| Doit être réassigné | Pointeur |
| Retourner un nouvel objet depuis une fonction | Pointeur (ou smart pointer en C++ moderne) |
| Paramètre optionnel | Pointeur (peut passer NULL) |
4. Démonstration d’adresse mémoire (ex02)
Section intitulée « 4. Démonstration d’adresse mémoire (ex02) »std::string str = "HI THIS IS BRAIN";std::string* stringPTR = &str; // Pointeur VERS strstd::string& stringREF = str; // Référence (alias) DE str
// Adresses - toutes devraient être identiques !std::cout << &str << std::endl; // Adresse de strstd::cout << stringPTR << std::endl; // Le pointeur contient la même adressestd::cout << &stringREF << std::endl; // Adresse de ref = adresse de str
// Valeurs - toutes devraient être identiques !std::cout << str << std::endl; // Accès directstd::cout << *stringPTR << std::endl; // Déréférencer le pointeurstd::cout << stringREF << std::endl; // La référence est identique à l'original5. Référence vs Pointeur dans les classes (ex03)
Section intitulée « 5. Référence vs Pointeur dans les classes (ex03) »Utiliser une référence (HumanA)
Section intitulée « Utiliser une référence (HumanA) »class HumanA {private: std::string _name; Weapon& _weapon; // Référence - DOIT toujours avoir une arme
public: // DOIT initialiser la référence dans le constructeur HumanA(std::string name, Weapon& weapon) : _name(name), _weapon(weapon) {}
void attack() { std::cout << _name << " attaque avec " << _weapon.getType(); }};
// UtilisationWeapon club("club");HumanA bob("Bob", club); // DOIT fournir une arme à la constructionUtiliser un pointeur (HumanB)
Section intitulée « Utiliser un pointeur (HumanB) »class HumanB {private: std::string _name; Weapon* _weapon; // Pointeur - pourrait ne pas avoir d'arme
public: HumanB(std::string name) : _name(name), _weapon(NULL) {}
void setWeapon(Weapon& weapon) { _weapon = &weapon; }
void attack() { if (_weapon) std::cout << _name << " attaque avec " << _weapon->getType(); else std::cout << _name << " n'a pas d'arme"; }};
// UtilisationHumanB jim("Jim"); // Pas d'arme initialementjim.setWeapon(club); // Arme ajoutée plus tardGuide de décision
Section intitulée « Guide de décision »- Référence : Quand l’objet DOIT exister à la construction et pendant toute la durée de vie
- Pointeur : Quand l’objet pourrait ne pas exister, ou pourrait changer
6. Pointeurs vers membres (ex05)
Section intitulée « 6. Pointeurs vers membres (ex05) »Pointeurs de fonction (style C)
Section intitulée « Pointeurs de fonction (style C) »void sayHello() { std::cout << "Hello"; }void sayBye() { std::cout << "Bye"; }
// Pointeur de fonctionvoid (*funcPtr)() = &sayHello;funcPtr(); // Appelle sayHello()
funcPtr = &sayBye;funcPtr(); // Appelle sayBye()Pointeurs vers fonctions membres
Section intitulée « Pointeurs vers fonctions membres »class Harl {public: void debug() { std::cout << "Debug"; } void info() { std::cout << "Info"; } void warning() { std::cout << "Warning"; } void error() { std::cout << "Error"; }
void complain(std::string level) { // Tableau de pointeurs de fonctions membres void (Harl::*funcs[4])() = { &Harl::debug, &Harl::info, &Harl::warning, &Harl::error };
std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++) { if (level == levels[i]) { (this->*funcs[i])(); // Appelle la fonction membre return; } } }};Décomposition de la syntaxe
Section intitulée « Décomposition de la syntaxe »// Déclarationvoid (ClassName::*pointerName)(parameters);
// AssignationpointerName = &ClassName::methodName;
// Appel via objet(object.*pointerName)(args);
// Appel via pointeur vers objet(objectPtr->*pointerName)(args);7. Instruction switch (ex06)
Section intitulée « 7. Instruction switch (ex06) »Syntaxe de base
Section intitulée « Syntaxe de base »switch (expression) { case value1: // code break; case value2: // code break; default: // code si pas de correspondance}Comportement de fall-through
Section intitulée « Comportement de fall-through »// SANS break - tombe dans le case suivantswitch (level) { case 3: // WARNING std::cout << "Message warning\n"; // Pas de break - tombe ! case 2: // INFO std::cout << "Message info\n"; // Pas de break - tombe ! case 1: // DEBUG std::cout << "Message debug\n"; break;}
// Si level = 3 : affiche Warning, Info, Debug// Si level = 2 : affiche Info, Debug// Si level = 1 : affiche Debugswitch vs if-else
Section intitulée « switch vs if-else »// Ne peut faire un switch que sur des types intégraux en C++98switch (number) { ... } // OKswitch (character) { ... } // OKswitch (string) { ... } // ERREUR en C++98 !
// Pour les chaînes, doit utiliser if-elseif (str == "DEBUG") { ... }else if (str == "INFO") { ... }Conversion string-vers-entier pour switch
Section intitulée « Conversion string-vers-entier pour switch »Puisque C++98 ne peut pas faire de switch sur les chaînes, convertissez d’abord les chaînes en entiers :
// Convertir chaîne en index pour switchint getLevel(const std::string& level) { std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"}; for (int i = 0; i < 4; i++) { if (level == levels[i]) return i; } return -1; // Non trouvé}
void complain(const std::string& level) { switch (getLevel(level)) { case 0: std::cout << "Message debug" << std::endl; break; case 1: std::cout << "Message info" << std::endl; break; case 2: std::cout << "Message warning" << std::endl; break; case 3: std::cout << "Message error" << std::endl; break; default: std::cout << "Niveau inconnu" << std::endl; }}C’est plus propre qu’une longue chaîne if-else et permet le comportement fall-through.
8. E/S de fichiers (ex04)
Section intitulée « 8. E/S de fichiers (ex04) »Lecture depuis un fichier
Section intitulée « Lecture depuis un fichier »#include <fstream>#include <string>
std::ifstream inFile("input.txt");
if (!inFile.is_open()) { std::cerr << "Impossible d'ouvrir le fichier" << std::endl; return 1;}
std::string line;while (std::getline(inFile, line)) { std::cout << line << std::endl;}
inFile.close();Écriture dans un fichier
Section intitulée « Écriture dans un fichier »std::ofstream outFile("output.txt");
if (!outFile.is_open()) { std::cerr << "Impossible de créer le fichier" << std::endl; return 1;}
outFile << "Hello, World!" << std::endl;outFile << "Ligne 2" << std::endl;
outFile.close();Lire un fichier entier dans une chaîne
Section intitulée « Lire un fichier entier dans une chaîne »std::ifstream inFile("input.txt");std::string content;std::string line;
while (std::getline(inFile, line)) { content += line; content += "\n";}Exercice 00 : BraiiiiiiinnnzzzZ
Section intitulée « Exercice 00 : BraiiiiiiinnnzzzZ »Analyse du sujet
Section intitulée « Analyse du sujet »Cet exercice vous demande de créer une classe Zombie avec deux fonctions :
newZombie(std::string name): Crée un zombie sur le HEAP, retourne un pointeurrandomChump(std::string name): Crée un zombie sur la STACK, s’annonce lui-même
L’insight clé : quand la mémoire doit-elle survivre au-delà d’un appel de fonction ?
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Comprendre l’exigence de durée de vie
Posez-vous : “L’appelant a-t-il besoin du zombie après le retour de la fonction ?”
newZombie: OUI → Allocation heap (survit au retour de fonction)randomChump: NON → Allocation stack (nettoyage auto)
Étape 2 - Concevoir la classe Zombie
- Attribut privé
_name - Constructeur qui définit le nom
- Destructeur qui affiche “[nom] is dead”
- Méthode
announce()qui affiche “[nom]: BraiiiiiiinnnzzzZ…”
Étape 3 - Implémenter les deux stratégies d’allocation
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Classe Zombie :
#ifndef ZOMBIE_HPP#define ZOMBIE_HPP
#include <string>
class Zombie {private: std::string _name;public: Zombie(std::string name); ~Zombie(); void announce() const;};
#endifÉtape 2 - Implémentation :
#include "Zombie.hpp"#include <iostream>
Zombie::Zombie(std::string name) : _name(name) {}
Zombie::~Zombie() { std::cout << _name << " is dead." << std::endl;}
void Zombie::announce() const { std::cout << _name << ": BraiiiiiiinnnzzzZ..." << std::endl;}Étape 3 - Les deux fonctions d’allocation :
#include "Zombie.hpp"
Zombie* newZombie(std::string name) { return new Zombie(name); // Heap - l'appelant doit delete !}#include "Zombie.hpp"
void randomChump(std::string name) { Zombie zombie(name); // Stack - détruit auto à la fin de la fonction zombie.announce();}// Destructeur appelé ICI automatiquementExplication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
new Zombie(name) | Alloue sur le heap | La mémoire survit au retour de fonction |
return new Zombie(...) | Retourne le pointeur | L’appelant obtient la propriété |
Zombie zombie(name) | Allocation stack | Rapide, nettoyage automatique |
Fin de randomChump | Destructeur s’exécute | Variables stack nettoyées |
Pièges courants
Section intitulée « Pièges courants »1. Fuites mémoire avec newZombie
// FAUX - fuite mémoire !newZombie("Bob"); // Créé mais jamais supprimé
// JUSTEZombie* z = newZombie("Bob");z->announce();delete z; // Nettoyage !2. Retourner un pointeur stack (comportement indéfini)
// FAUX - retourne l'adresse d'un objet mort !Zombie* badZombie(std::string name) { Zombie z(name); return &z; // z meurt ici, le pointeur est dangling !}3. Oublier le message du destructeur
Le sujet exige que le destructeur affiche un message. Les évaluateurs vérifieront !
Conseils de test
Section intitulée « Conseils de test »# Compilerc++ -Wall -Wextra -Werror *.cpp -o zombie
# Tester le zombie heap./zombie# Devrait voir : message announce, puis plus tard "X is dead" quand supprimé
# Surveiller les fuites mémoire avec valgrindvalgrind --leak-check=full ./zombieCode final
Section intitulée « Code final »#ifndef ZOMBIE_HPP#define ZOMBIE_HPP
#include <string>
class Zombie {private: std::string _name;public: Zombie(std::string name); ~Zombie(); void announce() const;};
Zombie* newZombie(std::string name);void randomChump(std::string name);
#endifExercice 01 : Moar brainz!
Section intitulée « Exercice 01 : Moar brainz! »Analyse du sujet
Section intitulée « Analyse du sujet »Créez une fonction zombieHorde(int N, std::string name) qui :
- Alloue N zombies en une seule allocation
- Initialise tous les zombies avec le même nom
- Retourne un pointeur vers le premier zombie
Contrainte clé : UNE allocation, pas N appels séparés à new.
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Utiliser la syntaxe d’allocation de tableau
new Zombie[N] alloue N zombies de manière contiguë.
Étape 2 - Gérer l’exigence du constructeur par défaut
L’allocation de tableau appelle le constructeur par défaut. Vous devez en ajouter un !
Étape 3 - Initialiser les noms après l’allocation
Puisque le constructeur par défaut ne peut pas prendre de paramètres, ajoutez une méthode setName().
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Ajouter constructeur par défaut et setter :
class Zombie {private: std::string _name;public: Zombie(); // Constructeur par défaut pour allocation tableau Zombie(std::string name); ~Zombie(); void setName(std::string name); // Définir le nom après construction void announce() const;};Étape 2 - Implémenter zombieHorde :
#include "Zombie.hpp"
Zombie* zombieHorde(int N, std::string name) { if (N <= 0) return NULL;
// Allocation unique pour tous les N zombies Zombie* horde = new Zombie[N];
// Initialiser le nom de chaque zombie for (int i = 0; i < N; i++) horde[i].setName(name);
return horde;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
new Zombie[N] | Allocation tableau | Bloc contigu unique |
horde[i].setName(name) | Initialise chacun | Constructeur par défaut a laissé nom vide |
return horde | Retourne premier élément | Le tableau se dégrade en pointeur |
Pièges courants
Section intitulée « Pièges courants »1. Utiliser delete au lieu de delete[]
Zombie* horde = new Zombie[N];
// FAUX - comportement indéfini !delete horde;
// JUSTE - doit utiliser delete[] pour les tableauxdelete[] horde;2. Faire N allocations séparées
// FAUX - N allocations, pas une !for (int i = 0; i < N; i++) horde[i] = new Zombie(name);
// JUSTE - allocation uniqueZombie* horde = new Zombie[N];3. Retourner un tableau stack
// FAUX - le tableau stack meurt au retour !Zombie* zombieHorde(int N, std::string name) { Zombie horde[N]; // VLA sur stack (aussi non-standard !) return horde; // Pointeur dangling !}Conseils de test
Section intitulée « Conseils de test »# Tester création et nettoyage de hordeint main() { Zombie* horde = zombieHorde(5, "Walker");
for (int i = 0; i < 5; i++) horde[i].announce();
delete[] horde; // Devrait voir 5 messages "is dead" return 0;}Code final
Section intitulée « Code final »#include "Zombie.hpp"
Zombie* zombieHorde(int N, std::string name) { if (N <= 0) return NULL;
Zombie* horde = new Zombie[N];
for (int i = 0; i < N; i++) horde[i].setName(name);
return horde;}Exercice 02 : HI THIS IS BRAIN
Section intitulée « Exercice 02 : HI THIS IS BRAIN »Analyse du sujet
Section intitulée « Analyse du sujet »Démontrez que les références sont des alias en montrant :
- Une variable string
- Un pointeur vers cette string
- Une référence vers cette string
Les trois devraient afficher la même adresse et la même valeur.
Stratégie d’approche
Section intitulée « Stratégie d’approche »C’est une simple démonstration. L’insight clé : une référence EST la variable originale, juste avec un autre nom.
Construction progressive du code
Section intitulée « Construction progressive du code »#include <iostream>#include <string>
int main() { std::string str = "HI THIS IS BRAIN"; std::string* stringPTR = &str; // Pointeur stocke l'adresse std::string& stringREF = str; // Référence EST str
// Afficher les adresses - toutes devraient être identiques std::cout << "Adresse de str : " << &str << std::endl; std::cout << "Adresse dans stringPTR : " << stringPTR << std::endl; std::cout << "Adresse de stringREF : " << &stringREF << std::endl;
// Afficher les valeurs - toutes devraient être identiques std::cout << "Valeur de str : " << str << std::endl; std::cout << "Valeur via stringPTR : " << *stringPTR << std::endl; std::cout << "Valeur de stringREF : " << stringREF << std::endl;
return 0;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
std::string* stringPTR = &str | Stocker adresse | Pointeur contient adresse mémoire |
std::string& stringREF = str | Créer alias | Référence EST l’original |
&str | Obtenir adresse | Opérateur address-of |
&stringREF | Obtenir adresse | Même que &str ! |
*stringPTR | Déréférencer | Obtenir valeur à l’adresse |
stringREF | Utiliser directement | Pas de déréférencement nécessaire |
Pièges courants
Section intitulée « Pièges courants »1. Confondre les significations de &
// Dans une déclaration : & signifie "type référence"std::string& ref = str;
// Dans une expression : & signifie "adresse de"std::cout << &str;2. Penser que la référence est une copie
std::string& ref = str;ref = "CHANGED";// str est aussi "CHANGED" - c'est la même chose !Conseils de test
Section intitulée « Conseils de test »./brain# La sortie devrait montrer la même adresse 3 fois# La sortie devrait montrer la même valeur 3 foisCode final
Section intitulée « Code final »#include <iostream>#include <string>
int main() { std::string str = "HI THIS IS BRAIN"; std::string* stringPTR = &str; std::string& stringREF = str;
std::cout << &str << std::endl; std::cout << stringPTR << std::endl; std::cout << &stringREF << std::endl;
std::cout << str << std::endl; std::cout << *stringPTR << std::endl; std::cout << stringREF << std::endl;
return 0;}Exercice 03 : Unnecessary Violence
Section intitulée « Exercice 03 : Unnecessary Violence »Analyse du sujet
Section intitulée « Analyse du sujet »Créez des classes démontrant quand utiliser une référence vs un pointeur comme membre de classe :
- HumanA : A toujours une arme → utiliser une référence
- HumanB : Pourrait ne pas avoir d’arme → utiliser un pointeur
L’arme est partagée - quand elle change, les deux humains voient le changement.
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Comprendre la décision de conception
| HumanA | HumanB |
|---|---|
| Doit avoir une arme à la construction | Peut ne pas avoir d’arme |
| L’arme ne change jamais | L’arme peut être définie plus tard |
Utiliser Weapon& (référence) | Utiliser Weapon* (pointeur) |
Étape 2 - Règle d’initialisation des références
Les références DOIVENT être initialisées à la construction. Ne peuvent pas être NULL.
Étape 3 - Les pointeurs peuvent être NULL
Les pointeurs peuvent commencer à NULL et être assignés plus tard.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Classe Weapon :
#ifndef WEAPON_HPP#define WEAPON_HPP
#include <string>
class Weapon {private: std::string _type;public: Weapon(std::string type); const std::string& getType() const; void setType(std::string type);};
#endifÉtape 2 - HumanA avec référence :
class HumanA {private: std::string _name; Weapon& _weapon; // Référence - doit toujours existerpublic: HumanA(std::string name, Weapon& weapon); void attack() const;};HumanA::HumanA(std::string name, Weapon& weapon) : _name(name), _weapon(weapon) {} // DOIT utiliser liste d'initialisation !
void HumanA::attack() const { std::cout << _name << " attaque avec son " << _weapon.getType() << std::endl;}Étape 3 - HumanB avec pointeur :
class HumanB {private: std::string _name; Weapon* _weapon; // Pointeur - peut être NULLpublic: HumanB(std::string name); void setWeapon(Weapon& weapon); void attack() const;};HumanB::HumanB(std::string name) : _name(name), _weapon(NULL) {}
void HumanB::setWeapon(Weapon& weapon) { _weapon = &weapon;}
void HumanB::attack() const { if (_weapon) std::cout << _name << " attaque avec son " << _weapon->getType() << std::endl; else std::cout << _name << " n'a pas d'arme !" << std::endl;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
Weapon& _weapon | Membre référence | Doit exister, ne peut pas être NULL |
: _weapon(weapon) | Liste d’init | Les références DOIVENT être initialisées ici |
Weapon* _weapon | Membre pointeur | Peut être NULL, défini plus tard |
_weapon = &weapon | Stocker adresse | Convertir référence en pointeur |
if (_weapon) | Vérification NULL | Le pointeur pourrait ne pas être défini |
_weapon->getType() | Opérateur flèche | Déréférencement + accès membre |
Pièges courants
Section intitulée « Pièges courants »1. Ne pas utiliser la liste d’initialisation pour la référence
// FAUX - référence non initialisée !HumanA::HumanA(std::string name, Weapon& weapon) { _name = name; _weapon = weapon; // ERREUR : _weapon devait déjà exister !}
// JUSTE - utiliser la liste d'initialisationHumanA::HumanA(std::string name, Weapon& weapon) : _name(name), _weapon(weapon) {}2. Ne pas vérifier NULL pour le pointeur
// FAUX - crash si pas d'arme !void HumanB::attack() const { std::cout << _weapon->getType(); // Déréférencement NULL !}
// JUSTE - vérifier d'abordif (_weapon) std::cout << _weapon->getType();Conseils de test
Section intitulée « Conseils de test »# Tester que les changements d'arme affectent les deux humainsint main() { Weapon club("crude spiked club"); HumanA bob("Bob", club); HumanB jim("Jim");
jim.setWeapon(club); bob.attack(); jim.attack();
club.setType("some other type of club"); bob.attack(); // Devrait montrer la nouvelle arme ! jim.attack(); // Devrait montrer la nouvelle arme !}Code final
Section intitulée « Code final »#include "Weapon.hpp"
Weapon::Weapon(std::string type) : _type(type) {}
const std::string& Weapon::getType() const { return _type;}
void Weapon::setType(std::string type) { _type = type;}Exercice 04 : Sed is for losers
Section intitulée « Exercice 04 : Sed is for losers »Analyse du sujet
Section intitulée « Analyse du sujet »Créez un programme qui remplace toutes les occurrences de la chaîne s1 par s2 dans un fichier :
- Lire le contenu du fichier
- Remplacer TOUTES les occurrences (pas seulement la première)
- Écrire dans
<filename>.replace - INTERDIT : fonction
std::string::replace()
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Lire le fichier entier dans une chaîne
Utiliser std::ifstream et std::getline() pour lire ligne par ligne.
Étape 2 - Implémenter le remplacement sans replace()
Utiliser find() pour localiser les occurrences, append() ou substr() pour construire le résultat.
Étape 3 - Écrire dans le fichier de sortie
Utiliser std::ofstream pour créer <filename>.replace.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - L’algorithme de remplacement :
std::string replaceAll(const std::string& content, const std::string& s1, const std::string& s2) { if (s1.empty()) return content; // Éviter boucle infinie !
std::string result; std::size_t pos = 0; std::size_t found;
while ((found = content.find(s1, pos)) != std::string::npos) { result.append(content, pos, found - pos); // Avant la correspondance result.append(s2); // Remplacement pos = found + s1.length(); // Passer après la correspondance } result.append(content, pos, std::string::npos); // Reste
return result;}Étape 2 - E/S de fichiers :
int main(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage : " << argv[0] << " <fichier> <s1> <s2>" << std::endl; return 1; }
std::string filename = argv[1]; std::ifstream inFile(filename.c_str()); // .c_str() pour C++98 if (!inFile.is_open()) { std::cerr << "Erreur : Impossible d'ouvrir le fichier" << std::endl; return 1; }
// Lire le contenu du fichier std::string content; std::string line; bool first = true; while (std::getline(inFile, line)) { if (!first) content += '\n'; content += line; first = false; } inFile.close();
// Remplacer et écrire std::string result = replaceAll(content, argv[2], argv[3]);
std::ofstream outFile((filename + ".replace").c_str()); outFile << result; outFile.close();
return 0;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
content.find(s1, pos) | Trouver occurrence suivante | Commencer la recherche depuis pos |
std::string::npos | Valeur “non trouvé” | Retournée quand pas de correspondance |
result.append(content, pos, found - pos) | Ajouter sous-chaîne | Tout avant la correspondance |
pos = found + s1.length() | Sauter après la correspondance | Ne pas re-trouver la même occurrence |
.c_str() | String vers C-string | Les flux de fichiers C++98 en ont besoin |
Pièges courants
Section intitulée « Pièges courants »1. Utiliser std::string::replace() interdit
// FAUX - interdit !str.replace(pos, s1.length(), s2);
// JUSTE - utiliser find + appendresult.append(content, pos, found - pos);result.append(s2);2. Ne pas gérer s1 vide
// Sans cette vérification : boucle infinie !if (s1.empty()) return content;3. Ne remplacer que la première occurrence
// FAUX - ne trouve que la premièresize_t pos = content.find(s1);// ... remplace une fois
// JUSTE - boucler jusqu'à plus de correspondancewhile ((found = content.find(s1, pos)) != std::string::npos) { // ... remplacer chaque}Conseils de test
Section intitulée « Conseils de test »# Créer fichier de testecho "Hello World World" > test.txt
# Remplacer "World" par "42"./sed test.txt World 42
# Vérifier le résultatcat test.txt.replace# Devrait montrer : "Hello 42 42"Code final
Section intitulée « Code final »#include <iostream>#include <fstream>#include <string>
std::string replaceAll(const std::string& content, const std::string& s1, const std::string& s2) { if (s1.empty()) return content;
std::string result; std::size_t pos = 0; std::size_t found;
while ((found = content.find(s1, pos)) != std::string::npos) { result.append(content, pos, found - pos); result.append(s2); pos = found + s1.length(); } result.append(content, pos, std::string::npos);
return result;}
int main(int argc, char** argv) { if (argc != 4) { std::cerr << "Usage : " << argv[0] << " <fichier> <s1> <s2>" << std::endl; return 1; }
std::ifstream inFile(argv[1]); if (!inFile.is_open()) { std::cerr << "Erreur : Impossible d'ouvrir le fichier" << std::endl; return 1; }
std::string content, line; bool first = true; while (std::getline(inFile, line)) { if (!first) content += '\n'; content += line; first = false; } inFile.close();
std::string result = replaceAll(content, argv[2], argv[3]);
std::ofstream outFile((std::string(argv[1]) + ".replace").c_str()); outFile << result; outFile.close();
return 0;}Exercice 05 : Harl 2.0
Section intitulée « Exercice 05 : Harl 2.0 »Analyse du sujet
Section intitulée « Analyse du sujet »Créez une classe Harl qui se plaint à différents niveaux :
- DEBUG, INFO, WARNING, ERROR
- Exigence du sujet : eviter une “foret” de
if/else if/else(utiliser des pointeurs vers fonctions membres)
La solution : utiliser des pointeurs vers fonctions membres.
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Déclarer un tableau de pointeurs de fonctions membres
void (Harl::*funcs[4])();Ceci déclare un tableau de 4 pointeurs vers des fonctions membres de Harl.
Étape 2 - Mapper les niveaux aux fonctions
Créer des tableaux parallèles : un pour les pointeurs de fonction, un pour les chaînes de niveau.
Étape 3 - Boucler et correspondre
Trouver le niveau correspondant, appeler la fonction correspondante.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Structure de la classe Harl :
#ifndef HARL_HPP#define HARL_HPP
#include <string>
class Harl {private: void debug(); void info(); void warning(); void error();public: void complain(std::string level);};
#endifÉtape 2 - Utilisation des pointeurs de fonction :
void Harl::complain(std::string level) { // Tableau de pointeurs de fonctions membres void (Harl::*funcs[4])() = { &Harl::debug, &Harl::info, &Harl::warning, &Harl::error };
// Tableau parallèle de noms de niveaux std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
// Trouver et appeler for (int i = 0; i < 4; i++) { if (level == levels[i]) { (this->*funcs[i])(); // Appeler la fonction membre via pointeur return; } }}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
void (Harl::*funcs[4])() | Déclarer tableau | 4 pointeurs vers méthodes Harl |
&Harl::debug | Obtenir adresse fonction | Adresse de fonction membre |
(this->*funcs[i])() | Appeler via pointeur | Déréférencer + appeler |
Pièges courants
Section intitulée « Pièges courants »1. Utiliser forêt if/else
// FAUX - interdit !if (level == "DEBUG") debug();else if (level == "INFO") info();// ...
// JUSTE - utiliser pointeurs de fonction(this->*funcs[i])();2. Mauvaise syntaxe pour appel de pointeur de fonction membre
// FAUXfuncs[i](); // Manque thisthis->funcs[i](); // Mauvaise syntaxe*funcs[i](); // Mauvaise syntaxe
// JUSTE(this->*funcs[i])();Conseils de test
Section intitulée « Conseils de test »./harl DEBUG# Devrait afficher message debug
./harl ERROR# Devrait afficher message error
./harl INVALID# Ne devrait rien afficher (ou gérer gracieusement)Code final
Section intitulée « Code final »#include "Harl.hpp"#include <iostream>
void Harl::debug() { std::cout << "[ DEBUG ]" << std::endl; std::cout << "I love having extra bacon..." << std::endl;}
void Harl::info() { std::cout << "[ INFO ]" << std::endl; std::cout << "I cannot believe adding extra bacon costs more..." << std::endl;}
void Harl::warning() { std::cout << "[ WARNING ]" << std::endl; std::cout << "I think I deserve to have some extra bacon for free..." << std::endl;}
void Harl::error() { std::cout << "[ ERROR ]" << std::endl; std::cout << "This is unacceptable! I want to speak to the manager now." << std::endl;}
void Harl::complain(std::string level) { void (Harl::*funcs[4])() = { &Harl::debug, &Harl::info, &Harl::warning, &Harl::error }; std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++) { if (level == levels[i]) { (this->*funcs[i])(); return; } }}Exercice 06 : Harl Filter
Section intitulée « Exercice 06 : Harl Filter »Analyse du sujet
Section intitulée « Analyse du sujet »Créez un filtre de log utilisant switch avec fall-through :
- Étant donné un niveau minimum, afficher ce niveau ET tous les niveaux au-dessus
- DEBUG → affiche DEBUG, INFO, WARNING, ERROR
- WARNING → affiche WARNING, ERROR
- Invalide → affiche message spécial
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Convertir string en int
C++98 ne peut pas faire de switch sur les strings. Convertir le nom de niveau en index (0-3).
Étape 2 - Utiliser fall-through
Ne pas utiliser break entre les cases - laisser l’exécution “tomber” vers les cases suivants.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Convertir niveau en int :
int getLevel(const std::string& level) { std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"}; for (int i = 0; i < 4; i++) if (level == levels[i]) return i; return -1; // Niveau invalide}Étape 2 - Switch avec fall-through :
switch (getLevel(argv[1])) { case 0: harl.debug(); // PAS DE BREAK - fall through ! case 1: harl.info(); // PAS DE BREAK - fall through ! case 2: harl.warning(); // PAS DE BREAK - fall through ! case 3: harl.error(); break; // Seulement break à la fin default: std::cout << "[ Probably complaining about insignificant problems ]" << std::endl;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
case 0: | Niveau DEBUG | Point d’entrée pour DEBUG |
Pas de break | Fall-through | Continuer vers case suivant |
case 3: ... break; | Niveau ERROR | S’arrêter après ERROR |
default: | Niveau invalide | Catch-all |
Pièges courants
Section intitulée « Pièges courants »1. Ajouter break à chaque case
// FAUX - ne filtre pas correctement !case 0: harl.debug(); break; // S'arrête ici, ne montre pas INFO/WARNING/ERROR
// JUSTE - pas de break pour fall-throughcase 0: harl.debug(); // fall throughcase 1: harl.info();2. Essayer de faire un switch sur une string
// FAUX - C++98 ne supporte pas ça !switch (level) { // level est std::string case "DEBUG": // ...}
// JUSTE - convertir en int d'abordswitch (getLevel(level)) { case 0: // ...}Conseils de test
Section intitulée « Conseils de test »./harlFilter DEBUG# Devrait afficher : DEBUG, INFO, WARNING, ERROR
./harlFilter WARNING# Devrait afficher : WARNING, ERROR
./harlFilter ERROR# Devrait afficher : ERROR seulement
./harlFilter INVALID# Devrait afficher : [ Probably complaining about insignificant problems ]Code final
Section intitulée « Code final »#include "Harl.hpp"#include <iostream>
int getLevel(const std::string& level) { std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"}; for (int i = 0; i < 4; i++) if (level == levels[i]) return i; return -1;}
int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Usage : " << argv[0] << " <level>" << std::endl; return 1; }
Harl harl;
switch (getLevel(argv[1])) { case 0: harl.debug(); case 1: harl.info(); case 2: harl.warning(); case 3: harl.error(); break; default: std::cout << "[ Probably complaining about insignificant problems ]" << std::endl; }
return 0;}Référence rapide
Section intitulée « Référence rapide »// Objet uniqueType* ptr = new Type(args);delete ptr;
// TableauType* arr = new Type[size];delete[] arr;Références
Section intitulée « Références »Type& ref = original; // Créer alias// ref est maintenant indistinguable de originalPointeurs de fonctions membres
Section intitulée « Pointeurs de fonctions membres »void (Class::*ptr)(args) = &Class::method;(object.*ptr)(args);E/S de fichiers
Section intitulée « E/S de fichiers »std::ifstream in("fichier");std::ofstream out("fichier");std::getline(in, str);out << content;Concepts connexes
Section intitulée « Concepts connexes »Continuez votre parcours C++ :
- Précédent : Module 00 : Fondamentaux C++ - Revoir les classes et la syntaxe de base
- Suivant : Module 02 : OCF & Opérateurs - Apprendre la Forme Canonique Orthodoxe et pourquoi une sémantique de copie correcte est importante
Termes clés de ce module
Section intitulée « Termes clés de ce module »Visitez le Glossaire pour les définitions de :
- Stack et Heap
- Pointeur et Référence
- Fuite mémoire