Module 00 : Fondamentaux du C++
Concepts clés :
- Namespaces
- Classes et Objets
- Fonctions membres (méthodes)
- Spécificateurs d’accès (public/private)
- Constructeurs et Destructeurs
- iostream (cout, cin, cerr)
- std::string
- Listes d’initialisation
- Mots-clés static et const
Pourquoi c’est important
Section intitulée « Pourquoi c’est important »Avant de plonger dans la syntaxe, comprenez POURQUOI ces concepts existent :
Pourquoi les Namespaces ?
Section intitulée « Pourquoi les Namespaces ? »Imaginez deux bibliothèques qui définissent toutes deux une fonction appelée print(). En C, vous auriez un conflit de noms. Les namespaces résolvent ce problème en donnant à chaque bibliothèque sa propre “pièce” pour les noms.
Pourquoi les Classes au lieu des Structs ?
Section intitulée « Pourquoi les Classes au lieu des Structs ? »En C, vous avez les structs (données) et les fonctions (comportement) séparément. Si vous voulez vous assurer qu’une struct est toujours dans un état valide, vous devez faire confiance à chaque programmeur pour l’utiliser correctement.
Les classes regroupent les données ET le comportement ensemble, et peuvent en plus cacher les détails internes. C’est ce qu’on appelle l’encapsulation.
Pourquoi Private et Public ?
Section intitulée « Pourquoi Private et Public ? »Pensez à votre compte bancaire. Vous voulez que les gens connaissent votre solde (interface publique : consulter le solde), mais vous ne voulez pas qu’ils modifient directement le registre interne (données privées).
Ces trois concepts—namespaces, encapsulation et contrôle d’accès—forment la base d’un code C++ organisé et maintenable.
Comprendre les opérateurs C++ (Pour les débutants complets)
Section intitulée « Comprendre les opérateurs C++ (Pour les débutants complets) »Avant de plonger dans le code C++, comprenons les opérateurs que vous verrez tout au long de ce module :
L’opérateur << (Insertion de flux)
Section intitulée « L’opérateur << (Insertion de flux) »std::cout << "Hello" << 42 << std::endl;<<s’appelle “opérateur d’insertion de flux” ou “opérateur de sortie”- Il insère des données dans un flux de sortie (comme
std::cout, qui signifie “character output”) - Pensez-y comme une flèche pointant dans la direction du flux de données : les données vont DES variables VERS la sortie
- Vous pouvez chaîner plusieurs opérateurs
<<ensemble pour afficher plusieurs valeurs std::endlinsère un retour à la ligne et vide le tampon
L’opérateur >> (Extraction de flux)
Section intitulée « L’opérateur >> (Extraction de flux) »std::cin >> number;>>s’appelle “opérateur d’extraction de flux” ou “opérateur d’entrée”- Il extrait des données d’un flux d’entrée (comme
std::cin, qui signifie “character input”) - Pensez-y comme une flèche pointant dans la direction du flux de données : les données vont DE l’entrée VERS la variable
L’opérateur :: (Résolution de portée)
Section intitulée « L’opérateur :: (Résolution de portée) »std::cout // Accès à cout depuis le namespace stdClassName::method // Accès à method depuis ClassName::est l‘“opérateur de résolution de portée”- Il indique au compilateur OÙ chercher un nom (variable, fonction, classe)
std::coutsignifie “utiliser lecoutqui appartient au namespacestd”- Cela évite les conflits de noms quand différentes bibliothèques utilisent les mêmes noms
L’opérateur . (Accès aux membres)
Section intitulée « L’opérateur . (Accès aux membres) »object.method() // Accès à la méthode d'un objetobject.attribute // Accès à l'attribut d'un objet.est l‘“opérateur d’accès aux membres” ou “opérateur point”- Il accède aux membres (méthodes ou attributs) d’un objet
- Utilisez-le quand vous avez un objet réel (pas un pointeur)
L’opérateur -> (Accès aux membres via pointeur)
Section intitulée « L’opérateur -> (Accès aux membres via pointeur) »pointer->method() // Équivalent à (*pointer).method()pointer->attribute // Équivalent à (*pointer).attribute->est l‘“opérateur flèche” ou “opérateur d’accès aux membres via pointeur”- C’est un raccourci pour déréférencer un pointeur ET accéder à un membre
pointer->method()est exactement la même chose que(*pointer).method()- Utilisez-le quand vous avez un pointeur vers un objet
L’opérateur & (Adresse de)
Section intitulée « L’opérateur & (Adresse de) »int* ptr = &x; // ptr stocke l'adresse mémoire de x&devant une variable donne son adresse mémoire- C’est ainsi que vous obtenez un pointeur vers une variable
- Ne confondez pas avec
&dans les déclarations (qui crée une référence)
L’opérateur * (Déréférencement)
Section intitulée « L’opérateur * (Déréférencement) »*ptr = 42; // Stocke 42 à l'adresse pointée par ptrint value = *ptr; // Récupère la valeur stockée à ptr*devant un pointeur le “déréférence” (accède à la valeur à cette adresse)- Il suit le pointeur jusqu’aux données réelles stockées en mémoire
Du C au C++ : Le changement de mentalité
Section intitulée « Du C au C++ : Le changement de mentalité »Ce qui change
Section intitulée « Ce qui change »| C | C++ |
|---|---|
printf() | std::cout << |
scanf() | std::cin >> |
malloc()/free() | new/delete |
struct (données seules) | class (données + comportement) |
| Les fonctions opèrent sur les données | Les objets ont des méthodes |
La liste des interdits (Règles 42)
Section intitulée « La liste des interdits (Règles 42) »// INTERDIT - vous donnera 0printf("Hello"); // Interdit : *printf(), utilisez std::cout a la placemalloc(sizeof(int)); // Interdit : *alloc() (malloc/calloc/realloc), utilisez new a la placefree(ptr); // Interdit, utilisez delete a la place
// INTERDIT - vous donnera -42using namespace std; // Doit préfixer avec std::friend class Other; // Sauf si explicitement autorisé par le sujet
// INTERDIT dans les Modules 00-07 - vous donnera -42#include <vector> // Pas de conteneurs STL avant le Module 08#include <list> // Pas de conteneurs STL avant le Module 08#include <map> // Pas de conteneurs STL avant le Module 08#include <algorithm> // Pas d'algorithmes STL avant le Module 08Namespaces
Section intitulée « Namespaces »Pourquoi les Namespaces existent
Section intitulée « Pourquoi les Namespaces existent »Les namespaces résolvent deux problèmes :
1. Collisions de noms : Dans les grands projets, deux bibliothèques peuvent définir une fonction avec le même nom.
// Sans namespaces - collision !void print(); // Bibliothèque Avoid print(); // Bibliothèque B - ERREUR !
// Avec namespaces - pas de collisionnamespace LibraryA { void print();}namespace LibraryB { void print();}
// UtilisationLibraryA::print();LibraryB::print();2. Organisation du code : Les namespaces vous permettent de regrouper des symboles liés sémantiquement à travers plusieurs fichiers. Contrairement au C où l’organisation est basée sur les fichiers, les namespaces vous permettent d’organiser par signification. Un namespace peut s’étendre sur plusieurs fichiers, et les fonctions liées restent ensemble logiquement même si elles sont physiquement séparées.
Le namespace std
Section intitulée « Le namespace std »Tout ce qui vient de la bibliothèque standard C++ vit dans std :
std::cout // flux de sortiestd::cin // flux d'entréestd::cerr // flux d'erreurstd::string // classe stringstd::endl // retour à la ligne + vidage du tamponL’opérateur :: (Résolution de portée)
Section intitulée « L’opérateur :: (Résolution de portée) »std::cout // cout du namespace std::globalFunction() // fonction du namespace global (pas de namespace)ClassName::method // méthode de ClassNameiostream : Flux d’entrée/sortie
Section intitulée « iostream : Flux d’entrée/sortie »Sortie basique avec cout
Section intitulée « Sortie basique avec cout »#include <iostream>
int main() { std::cout << "Hello, World!" << std::endl;
int x = 42; std::cout << "La réponse est " << x << std::endl;
// Chaînage de plusieurs valeurs std::cout << "a=" << 1 << ", b=" << 2 << std::endl;
return 0;}Entrée basique avec cin
Section intitulée « Entrée basique avec cin »#include <iostream>#include <string>
int main() { int number; std::cout << "Entrez un nombre : "; std::cin >> number;
std::string name; std::cout << "Entrez votre nom : "; std::cin >> name; // Ne lit que jusqu'à l'espace !
// Pour une ligne complète : std::getline(std::cin, name);
return 0;}Important : particularités de cin
Section intitulée « Important : particularités de cin »// PROBLÈME : mélanger cin >> et getlineint age;std::string name;
std::cin >> age; // Laisse '\n' dans le tamponstd::getline(std::cin, name); // Lit une ligne vide !
// SOLUTION : vider le tamponstd::cin >> age;std::cin.ignore(); // Ignore le '\n' restantstd::getline(std::cin, name);Formatage de sortie avec iomanip
Section intitulée « Formatage de sortie avec iomanip »#include <iostream>#include <iomanip>
int main() { // Définir la largeur du champ std::cout << std::setw(10) << "Hello" << std::endl; // " Hello"
// Alignement droite/gauche std::cout << std::right << std::setw(10) << "Hi" << std::endl; // " Hi" std::cout << std::left << std::setw(10) << "Hi" << std::endl; // "Hi "
// Caractère de remplissage std::cout << std::setfill('.') << std::setw(10) << "Hi" << std::endl; // "........Hi"
return 0;}std::string
Section intitulée « std::string »Pourquoi std::string plutôt que char* ?
Section intitulée « Pourquoi std::string plutôt que char* ? »// Style C (dangereux, mémoire manuelle)char* str = (char*)malloc(100);strcpy(str, "Hello");// Doit se rappeler de libérer !
// Style C++ (sûr, automatique)std::string str = "Hello";// Mémoire gérée automatiquementOpérations de base
Section intitulée « Opérations de base »#include <string>
std::string s = "Hello";
// Longueurs.length(); // 5s.size(); // 5 (même chose)
// Accès aux caractèress[0]; // 'H's.at(0); // 'H' (avec vérification des limites)
// Concaténations + " World"; // "Hello World"s.append(" World"); // Modifie s
// Comparaisons == "Hello"; // trues < "World"; // true (lexicographique)
// Sous-chaîness.substr(0, 3); // "Hel"s.substr(2); // "llo"
// Recherches.find("ll"); // 2 (index)s.find("xyz"); // std::string::npos (non trouvé)
// Remplacement (mais rappel : interdit dans ex04 !)s.replace(0, 2, "YY"); // "YYllo"
// Effacements.empty(); // falses.clear(); // s est maintenant ""Itération
Section intitulée « Itération »std::string s = "Hello";
// Basée sur l'indexfor (size_t i = 0; i < s.length(); i++) { std::cout << s[i];}
// C++98 n'a pas de boucles for basées sur les plages !// Ceci est du C++11 : for (char c : s) { } // INTERDITClasses et Objets
Section intitulée « Classes et Objets »Class vs Struct : Quelle différence ?
Section intitulée « Class vs Struct : Quelle différence ? »En C++, class et struct peuvent tous deux avoir des variables et fonctions membres. La seule différence est le spécificateur d’accès par défaut :
| Caractéristique | class | struct |
|---|---|---|
| Accès par défaut | private | public |
| Héritage par défaut | private | public |
| Membres accessibles ? | Seulement dans la classe | De partout |
Exemple pratique :
class MyClass { int x; // PRIVATE par défaut - accessible seulement dans la classe void foo(); // PRIVATE par défaut};
struct MyStruct { int x; // PUBLIC par défaut - accessible de partout void foo(); // PUBLIC par défaut};
MyClass c;c.x = 5; // ERREUR : x est private !
MyStruct s;s.x = 5; // OK : x est publicQuand utiliser chacun :
- Utilisez
classquand vous avez besoin d’encapsulation (cacher l’implémentation) - Utilisez
structpour les structures de données simples (comme les structs C) - En pratique, les deux fonctionnent pareil - c’est une question de convention
Disposition mémoire visuelle :
Objet MyClass : Objet MyStruct :+-------------+ +-------------+| _firstName | | _firstName | <-- Tous les membres| _lastName | | _lastName | sont accessibles| _phone | | _phone |+-------------+ +-------------+ (private) (public)Structure de base d’une classe
Section intitulée « Structure de base d’une classe »#ifndef CONTACT_HPP#define CONTACT_HPP
#include <string>
class Contact {private: // Attributs (membres de données) std::string _firstName; std::string _lastName; std::string _phoneNumber;
public: // Constructeur Contact();
// Destructeur ~Contact();
// Fonctions membres (méthodes) void setFirstName(std::string name); std::string getFirstName() const; void display() const;};
#endif#include "Contact.hpp"#include <iostream>
// Implémentation du constructeurContact::Contact() { std::cout << "Contact créé" << std::endl;}
// Implémentation du destructeurContact::~Contact() { std::cout << "Contact détruit" << std::endl;}
// Settervoid Contact::setFirstName(std::string name) { this->_firstName = name;}
// Getter (const - ne modifie pas l'objet)std::string Contact::getFirstName() const { return this->_firstName;}
// Méthode d'affichagevoid Contact::display() const { std::cout << "Nom : " << _firstName << " " << _lastName << std::endl;}Spécificateurs d’accès
Section intitulée « Spécificateurs d’accès »| Spécificateur | Accès |
|---|---|
private | Accessible seulement dans la classe |
public | Accessible de partout |
protected | Accessible dans la classe et les classes dérivées |
Règle générale : Rendez les attributs private, fournissez des getters/setters publics.
Le pointeur this
Section intitulée « Le pointeur this »Chaque fonction membre non-static a accès à un pointeur spécial appelé this. Il pointe vers l’instance courante - l’objet sur lequel la méthode a été appelée. C’est ainsi que les fonctions membres savent à quelles données d’objet accéder.
class Example {private: int value;
public: void setValue(int value) { // 'value' fait référence au paramètre // 'this->value' fait référence au membre this->value = value; }
// this est implicite - ces deux sont équivalents : int getValue() { return value; } int getValue() { return this->value; }};Pourquoi this est important : Quand vous appelez obj.setValue(42), le compilateur passe &obj comme premier argument caché. Dans la fonction, this == &obj.
Fonctions membres const
Section intitulée « Fonctions membres const »class Example {private: int _value;
public: // Peut modifier l'objet void setValue(int v) { _value = v; }
// Ne peut pas modifier l'objet (const à la fin) int getValue() const { return _value; }};Constructeurs et Destructeurs
Section intitulée « Constructeurs et Destructeurs »Constructeur par défaut
Section intitulée « Constructeur par défaut »class MyClass {public: MyClass() { std::cout << "Constructeur par défaut appelé" << std::endl; }};
// UtilisationMyClass obj; // Appelle le constructeur par défautConstructeur paramétré
Section intitulée « Constructeur paramétré »class Contact {private: std::string _name; int _age;
public: Contact(std::string name, int age) { _name = name; _age = age; }};
// UtilisationContact c("John", 25);Listes d’initialisation (IMPORTANT !)
Section intitulée « Listes d’initialisation (IMPORTANT !) »class Contact {private: std::string _name; int _age;
public: // SANS liste d'initialisation (assignation dans le corps) Contact(std::string name, int age) { _name = name; // D'abord construit par défaut, puis assigné _age = age; }
// AVEC liste d'initialisation (initialisation directe) Contact(std::string name, int age) : _name(name), _age(age) { // Les membres sont initialisés avant l'exécution du corps }};Pourquoi utiliser les listes d’initialisation ?
-
Plus efficace : Sans liste d’init, les membres sont construits par défaut PUIS assignés (2 opérations). Avec liste d’init, les membres sont directement initialisés (1 opération).
// SANS liste d'init - DEUX opérations par membre :Contact(std::string name) {// 1. _name est construit par défaut (chaîne vide)// 2. _name reçoit la valeur de 'name'_name = name;}// AVEC liste d'init - UNE opération par membre :Contact(std::string name) : _name(name) {// _name est directement construit avec 'name'} -
Requis pour les membres
const(on ne peut pas assigner à const après construction) -
Requis pour les membres référence (doivent être liés à l’initialisation)
-
Requis pour les membres sans constructeur par défaut
class Example {private: const int _id; // DOIT utiliser liste d'init std::string& _ref; // DOIT utiliser liste d'init
public: // C'est la SEULE façon : Example(int id, std::string& ref) : _id(id), _ref(ref) {}};Destructeur
Section intitulée « Destructeur »class FileHandler {private: int* _data;
public: FileHandler() { _data = new int[100]; // Allocation }
~FileHandler() { delete[] _data; // Nettoyage std::cout << "FileHandler détruit, mémoire libérée" << std::endl; }};Membres statiques
Section intitulée « Membres statiques »Attributs statiques
Section intitulée « Attributs statiques »Partagés entre TOUTES les instances d’une classe.
// Headerclass Counter {private: static int _count; // Déclaration
public: Counter() { _count++; } ~Counter() { _count--; } static int getCount() { return _count; }};
// Source (DOIT être défini hors de la classe)int Counter::_count = 0; // Définition + initialisation
// UtilisationCounter a;Counter b;Counter c;std::cout << Counter::getCount(); // 3Fonctions membres statiques
Section intitulée « Fonctions membres statiques »Peuvent être appelées sans objet. Ne peuvent pas accéder aux membres non-statiques.
class Math {public: static int add(int a, int b) { return a + b; }};
// Utilisation - pas d'objet nécessaireint result = Math::add(5, 3);Le mot-clé const
Section intitulée « Le mot-clé const »Philosophie de conception : const est plus qu’une fonctionnalité du langage - c’est une discipline pour écrire du code correct. En marquant tout ce qui ne devrait pas changer comme const, vous :
- Détectez les bugs à la compilation (modifications accidentelles)
- Documentez l’intention (les lecteurs savent ce qui ne changera pas)
- Permettez les optimisations du compilateur
- Rendez le code plus facile à comprendre dans les systèmes complexes
Règle générale : Utilisez const par défaut. Ne le retirez que quand vous avez besoin de modifier quelque chose.
Variables const
Section intitulée « Variables const »const int MAX = 100; // Ne peut pas être modifiéMAX = 200; // ERREUR !Paramètres const
Section intitulée « Paramètres const »void print(const std::string& s) { // s ne peut pas être modifié // Passé par référence (efficace, pas de copie) std::cout << s << std::endl;}Fonctions membres const
Section intitulée « Fonctions membres const »class Example {public: int getValue() const { // Promet de ne pas modifier l'objet return _value; }};Valeurs de retour const
Section intitulée « Valeurs de retour const »class Example {private: std::string _name;
public: // Retourne une référence const - l'appelant ne peut pas modifier const std::string& getName() const { return _name; }};Gardes d’inclusion
Section intitulée « Gardes d’inclusion »Chaque fichier header DOIT avoir des gardes d’inclusion pour éviter la double inclusion :
#ifndef CONTACT_HPP#define CONTACT_HPP
class Contact { // ...};
#endifPourquoi ?
// Sans gardes :#include "Contact.hpp"#include "Contact.hpp" // ERREUR : Contact redéfini !
// Avec gardes :#include "Contact.hpp" // Définit CONTACT_HPP, inclut la classe#include "Contact.hpp" // CONTACT_HPP déjà défini, ignoréCompilation
Section intitulée « Compilation »Bases du Makefile pour C++
Section intitulée « Bases du Makefile pour C++ »NAME = programCXX = c++CXXFLAGS = -Wall -Wextra -Werror -std=c++98
SRCS = main.cpp Contact.cpp PhoneBook.cppOBJS = $(SRCS:.cpp=.o)
all: $(NAME)
$(NAME): $(OBJS) $(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS)
%.o: %.cpp $(CXX) $(CXXFLAGS) -c $< -o $@
clean: rm -f $(OBJS)
fclean: clean rm -f $(NAME)
re: fclean all
.PHONY: all clean fclean reCette structure evite aussi le “relink” : si aucun fichier .cpp n’a change, make ne relancera pas l’etape finale de link.
Explication ligne par ligne du Makefile
Section intitulée « Explication ligne par ligne du Makefile »| Ligne | Code | Explication |
|---|---|---|
| 1 | NAME = program | Définit une variable NAME contenant le nom de l’exécutable final. Utiliser une variable permet de changer facilement le nom du programme à un seul endroit. |
| 2 | CXX = c++ | Définit le compilateur C++ à utiliser. c++ est la commande standard qui pointe généralement vers g++ ou clang++. Utiliser une variable permet de changer facilement de compilateur. |
| 3 | CXXFLAGS = -Wall -Wextra -Werror -std=c++98 | Flags du compilateur : -Wall active tous les avertissements courants, -Wextra active des avertissements supplémentaires, -Werror traite les avertissements comme des erreurs (la compilation échoue sur tout avertissement), -std=c++98 impose le standard C++98 (requis par 42). |
| 5 | SRCS = main.cpp Contact.cpp PhoneBook.cpp | Liste tous les fichiers sources à compiler. Chaque fichier .cpp sera compilé en son propre fichier objet. |
| 6 | OBJS = $(SRCS:.cpp=.o) | Référence de substitution : Prend la liste SRCS et remplace .cpp par .o. Résultat : main.o Contact.o PhoneBook.o. Cela crée la liste des fichiers objets. |
| 8 | all: $(NAME) | La cible par défaut. Quand vous exécutez make sans arguments, il construit all, qui dépend de $(NAME) (l’exécutable). |
| 10-11 | $(NAME): $(OBJS) | Règle de liaison : L’exécutable dépend de tous les fichiers objets. Si un fichier .o est plus récent que l’exécutable, cette règle s’exécute. |
| 11 | $(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS) | La commande de liaison : appelle le compilateur avec les flags, -o $(NAME) spécifie le nom du fichier de sortie, $(OBJS) liste tous les fichiers objets à lier ensemble. |
| 13-14 | %.o: %.cpp | Règle de pattern : Définit comment construire N’IMPORTE QUEL fichier .o à partir de son fichier .cpp correspondant. Le % est un joker qui correspond à n’importe quel nom de fichier. |
| 14 | $(CXX) $(CXXFLAGS) -c $< -o $@ | La commande de compilation : -c signifie compiler seulement (ne pas lier), $< est une variable automatique représentant le premier prérequis (le fichier .cpp), $@ est la cible (le fichier .o). |
| 16-17 | clean: | Une cible pour supprimer les fichiers objets. N’a pas de prérequis, donc s’exécute toujours quand appelée. |
| 17 | rm -f $(OBJS) | Supprime tous les fichiers objets. -f signifie “forcer” (ne pas errorer si les fichiers n’existent pas). |
| 19-20 | fclean: clean | ”Nettoyage complet” : exécute d’abord clean (son prérequis), puis supprime aussi l’exécutable. |
| 20 | rm -f $(NAME) | Supprime l’exécutable final. |
| 22 | re: fclean all | ”Refaire” : exécute fclean d’abord, puis all. Cela force une recompilation complète depuis zéro. |
| 24 | .PHONY: all clean fclean re | Déclare ces cibles comme “phony” (pas des fichiers réels). Sans cela, si un fichier nommé clean existait, make clean n’exécuterait pas la règle. |
Référence rapide des variables automatiques
Section intitulée « Référence rapide des variables automatiques »| Variable | Signification |
|---|---|
$@ | La cible de la règle (ce qu’on construit) |
$< | Le premier prérequis (première dépendance) |
$^ | Tous les prérequis (toutes les dépendances) |
Pourquoi cette structure ?
Section intitulée « Pourquoi cette structure ? »- Compilation incrémentale : Ne recompile que les fichiers qui ont changé
- Pas de relinking : Si aucun fichier objet n’a changé, l’exécutable n’est pas reconstruit
- Maintenance facile : Changez
NAMEouSRCSà un seul endroit - Cibles de nettoyage :
clean,fclean, etresuivent les conventions standard de 42
Gestion de l’EOF avec std::getline
Section intitulée « Gestion de l’EOF avec std::getline »Lors de la lecture d’entrée dans une boucle, vous devez gérer l’EOF (Ctrl+D sur Unix, Ctrl+Z sur Windows) :
std::string line;
// std::getline retourne le flux, qui se convertit en false sur EOFwhile (std::getline(std::cin, line)) { // Traiter la ligne std::cout << "Reçu : " << line << std::endl;}// La boucle se termine quand EOF est atteint
// Vous pouvez aussi vérifier explicitement :if (std::cin.eof()) { std::cout << "Fin de l'entrée atteinte" << std::endl;}Sortie gracieuse sur EOF
Section intitulée « Sortie gracieuse sur EOF »#include <cstdlib> // pour std::exit
std::string input;std::cout << "Entrez une valeur : ";if (!std::getline(std::cin, input)) { std::cout << std::endl; // Affiche un retour à la ligne pour une sortie propre std::exit(0); // Quitte le programme gracieusement}Pattern du tampon circulaire
Section intitulée « Pattern du tampon circulaire »Quand vous avez besoin d’une collection de taille fixe où les nouveaux éléments remplacent les plus anciens :
class PhoneBook {private: Contact _contacts[8]; // Tableau de taille fixe int _index; // Prochaine position d'écriture int _count; // Total de contacts stockés
public: PhoneBook() : _index(0), _count(0) {}
void addContact(const Contact& c) { _contacts[_index] = c;
// Bouclage avec modulo _index = (_index + 1) % 8; // 0,1,2,3,4,5,6,7,0,1,2...
// Compter jusqu'au max if (_count < 8) _count++; }};Comment fonctionne le bouclage modulo
Section intitulée « Comment fonctionne le bouclage modulo »int index = 0;int size = 8;
index = (index + 1) % size; // 0 -> 1index = (index + 1) % size; // 1 -> 2// ... après 7 :index = (index + 1) % size; // 7 -> 0 (boucle !)Pointeurs vers membres
Section intitulée « Pointeurs vers membres »C++ étend le concept de pointeur pour permettre des pointeurs vers des membres de classe (attributs et fonctions membres). C’est différent des pointeurs normaux.
Pointeur vers attribut membre
Section intitulée « Pointeur vers attribut membre »class Sample {public: int value;};
// Pointeur vers attribut membreint Sample::*ptr = &Sample::value;
// Utilisation - besoin d'une instance pour déréférencerSample s;s.value = 42;std::cout << s.*ptr << std::endl; // Affiche 42
Sample* sp = &s;std::cout << sp->*ptr << std::endl; // Affiche aussi 42Pointeur vers fonction membre
Section intitulée « Pointeur vers fonction membre »class Sample {public: void display() { std::cout << "Hello" << std::endl; } void greet(std::string name) { std::cout << "Salut " << name << std::endl; }};
// Pointeur vers fonction membre (sans paramètres)void (Sample::*funcPtr)() = &Sample::display;
// UtilisationSample s;(s.*funcPtr)(); // Appelle s.display()
Sample* sp = &s;(sp->*funcPtr)(); // Appelle aussi display()
// Pointeur vers fonction membre avec paramètresvoid (Sample::*greetPtr)(std::string) = &Sample::greet;(s.*greetPtr)("World"); // Appelle s.greet("World")Pourquoi les pointeurs vers membres ?
Section intitulée « Pourquoi les pointeurs vers membres ? »- Callbacks : Sélectionner quelle fonction membre appeler à l’exécution
- Conception pilotée par les données : Accéder à différents attributs selon la configuration
- Algorithmes génériques : Écrire des fonctions qui fonctionnent sur n’importe quel membre
Erreurs courantes à éviter
Section intitulée « Erreurs courantes à éviter »- Utiliser
using namespace std;- Interdit à 42 (note -42) - Utiliser le mot-clé
friend- Interdit sauf si explicitement autorisé (note -42) - Utiliser les conteneurs/algorithmes STL avant le Module 08 - Interdit (note -42)
- Oublier les gardes d’inclusion - Cause des erreurs de compilation (note 0)
- Mettre l’implémentation dans les headers - Note 0 (sauf templates)
- Ne pas terminer la sortie par un retour à la ligne - Requis par le sujet
- Utiliser printf/scanf - Interdit à 42 (note 0)
- Oublier
constsur les getters - Mauvaise pratique - Ne pas initialiser les membres - Comportement indéfini
- Fuites mémoire - Toujours apparier
newavecdelete
Exercices
Section intitulée « Exercices »Appliquons maintenant ces concepts aux exercices du module.
Exercice 00 : Megaphone
Section intitulée « Exercice 00 : Megaphone »Analyse du sujet
Section intitulée « Analyse du sujet »L’exercice Megaphone vous demande de créer un programme qui :
- Prend des arguments en ligne de commande et les convertit tous en majuscules
- Concatène tous les arguments sans ajouter d’espaces entre eux
- Affiche un message par défaut
* LOUD AND UNBEARABLE FEEDBACK NOISE *quand aucun argument n’est donné
C’est votre premier programme C++. L’objectif est d’apprendre les E/S de base avec std::cout et de comprendre comment fonctionnent les arguments en ligne de commande en C++.
Stratégie d’approche
Section intitulée « Stratégie d’approche »Avant d’écrire du code, réfléchissez au problème :
- Comprendre argc/argv :
argcest le nombre d’arguments,argvest un tableau de chaînes C.argv[0]est toujours le nom du programme. - Gérer le cas limite en premier : Vérifier si aucun argument n’a été donné (argc == 1).
- Traiter chaque argument : Boucler à partir de l’index 1 pour sauter le nom du programme.
- Convertir caractère par caractère : Pour chaque argument, parcourir chaque caractère et convertir en majuscule.
- Afficher à la fin : Imprimer un seul retour à la ligne après tous les caractères.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Squelette de base :
#include <iostream>
int main(int argc, char **argv){ // TODO: implémenter return 0;}Étape 2 - Gérer le cas sans arguments :
#include <iostream>
int main(int argc, char **argv){ if (argc == 1) { std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl; return 0; } // TODO: gérer les arguments return 0;}Étape 3 - Implémentation complète :
#include <iostream>#include <cctype>
int main(int argc, char **argv){ if (argc == 1) { std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl; return 0; }
for (int i = 1; i < argc; i++) { for (int j = 0; argv[i][j]; j++) { std::cout << (char)toupper(argv[i][j]); } } std::cout << std::endl;
return 0;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
| 1 | #include <iostream> | Bibliothèque E/S C++. Utilisez ceci au lieu de stdio.h. Nous donne std::cout. |
| 2 | #include <cctype> | Wrapper C++ pour les fonctions de caractères. Fournit toupper(). |
| 4 | int main(int argc, char **argv) | Signature standard pour les programmes recevant des arguments en ligne de commande. |
| 6 | if (argc == 1) | Pourquoi 1, pas 0 ? Parce que argv[0] est toujours le nom du programme, donc 1 = “pas d’arguments réels”. |
| 8 | std::cout << ... << std::endl; | Envoie le message vers stdout. std::endl vide le tampon et ajoute un retour à la ligne. |
| 9 | return 0; | Sortie anticipée après avoir affiché le message par défaut. |
| 12 | for (int i = 1; i < argc; i++) | Commence à 1 pour sauter argv[0] (le nom du programme). |
| 14 | for (int j = 0; argv[i][j]; j++) | Boucle jusqu’au terminateur nul. argv[i][j] est le j-ième caractère du i-ième argument. |
| 16 | (char)toupper(argv[i][j]) | toupper() retourne un int, donc on caste en char pour un affichage correct. |
| 19 | std::cout << std::endl; | Affiche le retour à la ligne final seulement après tous les caractères. |
Pièges courants
Section intitulée « Pièges courants »-
Utiliser printf au lieu de std::cout
- Habitude du C ! Dans les modules C++, vous devez utiliser
std::coutpour la sortie. - Faux :
printf("hello"); - Juste :
std::cout << "hello";
- Habitude du C ! Dans les modules C++, vous devez utiliser
-
Oublier que toupper() retourne int
- Sans le cast, vous pourriez afficher des codes ASCII au lieu de caractères.
- Faux :
std::cout << toupper(c); - Juste :
std::cout << (char)toupper(c);
-
Oublier le cas sans argument
- Le sujet exige explicitement d’afficher
* LOUD AND UNBEARABLE FEEDBACK NOISE *quand il n’y a pas d’arguments. - Toujours tester avec
./megaphone(sans args).
- Le sujet exige explicitement d’afficher
-
Ajouter des espaces entre les arguments
- Le sujet veut que les arguments soient concaténés directement.
./megaphone "Hello" "World"devrait afficherHELLOWORLD, pasHELLO WORLD.
Conseils de test
Section intitulée « Conseils de test »Testez votre programme avec les exemples exacts du sujet :
./megaphone "shhhhh... I think the students are asleep..."# Attendu : SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
./megaphone Damnit " ! " "Sorry students, I thought this thing was off."# Attendu : DAMNIT ! SORRY STUDENTS, I THOUGHT THIS THING WAS OFF.
./megaphone# Attendu : * LOUD AND UNBEARABLE FEEDBACK NOISE *Cas limites supplémentaires à vérifier :
./megaphone ""# Attendu : (ligne vide - juste un retour à la ligne)
./megaphone "123" "!@#"# Attendu : 123!@# (les non-lettres passent inchangées)Code final
Section intitulée « Code final »#include <iostream>#include <cctype>
int main(int argc, char **argv){ if (argc == 1) { std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl; return 0; }
for (int i = 1; i < argc; i++) { for (int j = 0; argv[i][j]; j++) { std::cout << (char)toupper(argv[i][j]); } } std::cout << std::endl;
return 0;}Exercice 01 : PhoneBook
Section intitulée « Exercice 01 : PhoneBook »Votre premier vrai programme C++ avec des classes. Cet exercice vous enseigne les fondamentaux orientés objet : encapsulation, conception de classes, et comment les objets interagissent.
Analyse du sujet
Section intitulée « Analyse du sujet »L’exercice PhoneBook vous demande de construire un système de gestion de contacts avec ces contraintes :
Deux classes nécessaires :
- Contact : Stocke les données d’une personne (prénom, nom, surnom, numéro de téléphone, secret le plus sombre)
- PhoneBook : Gère une collection de jusqu’à 8 contacts
Exigences principales :
- Tableau fixe de 8 contacts - PAS de vectors, PAS d’allocation dynamique (
new/delete) - Trois commandes :
ADD,SEARCH,EXIT - Tampon circulaire : Quand plein, le contact le plus ancien est remplacé
- Pas de champs vides : Chaque champ doit avoir du contenu
- Affichage SEARCH : Colonnes de 10 caractères, alignées à droite, tronquées avec ”.” si trop long
- Recommande : gerer EOF (Ctrl+D) proprement (le traiter comme
EXIT)
La commande SEARCH a deux phases :
- Afficher un tableau formaté montrant tous les contacts
- Demander un index, puis afficher les détails complets de ce contact
Stratégie d’approche
Section intitulée « Stratégie d’approche »Réfléchissez à la conception avant de coder :
Étape 1 - Concevoir la classe Contact d’abord
Posez-vous : Quelles données un contact contient-il ? De quelles méthodes a-t-il besoin ?
- 5 attributs string privés (les champs)
- Un flag pour savoir si l’emplacement est utilisé (
_isEmpty) - Des setters pour stocker les données, des getters pour les récupérer
- Tout privé sauf les méthodes d’interface
Étape 2 - Concevoir la classe PhoneBook
Posez-vous : Comment gérer 8 contacts ? Comment savoir où ajouter le suivant ?
- Tableau fixe :
Contact _contacts[8] - Suivre où ajouter :
_currentIndex(cycle 0→7→0) - Suivre combien existent :
_totalContacts(plafond à 8)
Étape 3 - Planifier la boucle principale
while (true) { lire commande si ADD → appeler phonebook.addContact() si SEARCH → appeler phonebook.searchContacts() si EXIT → break}Étape 4 - Gérer les cas limites
- Rejet des entrées vides (boucler jusqu’à non-vide)
- Gestion EOF (
std::getlineretourne false) - Index invalide (vérifier plage et format)
Insight clé - Le tampon circulaire :
Vous avez besoin de deux compteurs :
_currentIndex: Où ajouter le PROCHAIN contact (boucle avec modulo 8)_totalContacts: Combien de contacts existent (maximum 8)
Quand on ajoute le contact #9, _currentIndex sera 0, remplaçant le contact le plus ancien.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Classe Contact (le conteneur de données) :
#ifndef CONTACT_HPP#define CONTACT_HPP
#include <string>
class Contact {private: std::string _firstName; std::string _lastName; std::string _nickname; std::string _phoneNumber; std::string _darkestSecret; bool _isEmpty;
public: Contact(); ~Contact();
// Setters - prennent const reference pour l'efficacité void setFirstName(const std::string& firstName); void setLastName(const std::string& lastName); void setNickname(const std::string& nickname); void setPhoneNumber(const std::string& phoneNumber); void setDarkestSecret(const std::string& secret); void setIsEmpty(bool isEmpty);
// Getters - marqués const (promesse de ne pas modifier l'objet) std::string getFirstName() const; std::string getLastName() const; std::string getNickname() const; std::string getPhoneNumber() const; std::string getDarkestSecret() const; bool isEmpty() const;};
#endifPourquoi ces choix de conception ?
| Décision | Raison |
|---|---|
| Attributs privés | Encapsulation - le code externe ne peut pas corrompre les données |
const std::string& params | Évite de copier la chaîne, plus efficace |
const sur les getters | Promet que ces méthodes ne modifieront pas l’objet |
Flag _isEmpty | Permet de vérifier si un emplacement est utilisé |
| Préfixe underscore | Convention courante pour identifier les membres privés |
Étape 2 - Classe PhoneBook (le gestionnaire) :
#ifndef PHONEBOOK_HPP#define PHONEBOOK_HPP
#include "Contact.hpp"#include <string>
class PhoneBook {private: Contact _contacts[8]; // Tableau fixe - PAS de vectors ! int _currentIndex; // Où ajouter le suivant (0-7, boucle) int _totalContacts; // Combien sont stockés (max 8)
// Méthodes helper privées - détails d'implémentation internes std::string _truncate(const std::string& str) const; void _displayContactRow(int index) const; std::string _getInput(const std::string& prompt) const;
public: PhoneBook(); ~PhoneBook();
void addContact(); void searchContacts() const;};
#endifPourquoi Contact _contacts[8] et pas un vector ?
Le sujet interdit explicitement les conteneurs de la STL. Cela vous force à comprendre comment fonctionnent les tableaux de taille fixe en C++. Le tableau est alloué sur la pile comme partie de l’objet PhoneBook lui-même.
Étape 3 - La logique du tampon circulaire :
void PhoneBook::addContact() { Contact newContact;
// Collecter tous les champs (le helper rejette les entrées vides) newContact.setFirstName(_getInput("Entrez le prénom : ")); newContact.setLastName(_getInput("Entrez le nom : ")); newContact.setNickname(_getInput("Entrez le surnom : ")); newContact.setPhoneNumber(_getInput("Entrez le numéro de téléphone : ")); newContact.setDarkestSecret(_getInput("Entrez le secret le plus sombre : ")); newContact.setIsEmpty(false);
// Stocker à l'index courant (écrase le plus ancien si plein) _contacts[_currentIndex] = newContact;
// Tampon circulaire : bouclage avec modulo _currentIndex = (_currentIndex + 1) % 8;
// Compter (plafond à 8) if (_totalContacts < 8) _totalContacts++;
std::cout << "Contact ajouté avec succès !" << std::endl;}L’astuce du modulo expliquée :
(_currentIndex + 1) % 8 fait cycler l’index : 0→1→2→3→4→5→6→7→0→1→2…
_currentIndex | + 1 | % 8 | Résultat |
|---|---|---|---|
| 0 | 1 | 1 % 8 | 1 |
| 6 | 7 | 7 % 8 | 7 |
| 7 | 8 | 8 % 8 | 0 ← boucle ! |
Étape 4 - Formatage de l’affichage SEARCH :
std::string PhoneBook::_truncate(const std::string& str) const { if (str.length() > 10) return str.substr(0, 9) + "."; // 9 chars + point = 10 return str;}
void PhoneBook::_displayContactRow(int index) const { std::cout << std::setw(10) << std::right << index << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getFirstName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getLastName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getNickname()) << std::endl;}Comprendre std::setw et std::right :
std::setw(10)- Définit la largeur à 10 caractères pour la prochaine sortiestd::right- Aligne le contenu à droite dans cette largeur
Exemple : "John" devient " John" (6 espaces + 4 chars = 10)
Explication ligne par ligne
Section intitulée « Explication ligne par ligne »Validation d’entrée avec gestion EOF :
std::string PhoneBook::_getInput(const std::string& prompt) const { std::string input;
while (true) { std::cout << prompt; if (!std::getline(std::cin, input)) { // Retourne false sur EOF std::cout << std::endl; std::exit(0); // Sortie propre sur Ctrl+D } if (!input.empty()) break; std::cout << "Le champ ne peut pas être vide. Veuillez réessayer." << std::endl; } return input;}| Ligne | Pourquoi |
|---|---|
std::getline(std::cin, input) | Lit toute la ligne incluant les espaces (contrairement à std::cin >>) |
if (!std::getline(...)) | getline retourne false quand EOF (Ctrl+D) est rencontré |
std::exit(0) | Termine le programme proprement - requis pour la gestion EOF |
if (!input.empty()) | Rejette les chaînes vides, continue à demander |
Validation d’index pour SEARCH :
// Obtenir et valider l'indexstd::string indexStr;std::cout << "Entrez l'index pour voir les détails : ";if (!std::getline(std::cin, indexStr)) { std::cout << std::endl; return;}
// Validation stricte : doit être un chiffre unique 0-7if (indexStr.length() != 1 || indexStr[0] < '0' || indexStr[0] > '7') { std::cout << "Index invalide." << std::endl; return;}
int index = indexStr[0] - '0'; // Convertit char digit en intif (index >= _totalContacts) { std::cout << "Index invalide." << std::endl; return;}| Ligne | Pourquoi |
|---|---|
indexStr.length() != 1 | N’accepte que les entrées d’un seul caractère |
indexStr[0] < '0' || indexStr[0] > '7' | Doit être un chiffre 0-7 |
indexStr[0] - '0' | Conversion caractère en int : '5' - '0' = 5 |
index >= _totalContacts | Ne peut pas accéder à un contact qui n’existe pas |
Pièges courants
Section intitulée « Pièges courants »1. Utiliser vectors ou allocation dynamique
Le sujet l’INTERDIT EXPLICITEMENT. Vous échouerez l’évaluation.
// FAUX - interditstd::vector<Contact> _contacts;Contact* _contacts = new Contact[8];
// JUSTE - tableau fixeContact _contacts[8];2. Mauvais formatage d’affichage
// FAUX - pas de formatagestd::cout << firstName << "|" << lastName << std::endl;
// JUSTE - 10 chars, aligné à droite, tronquéstd::cout << std::setw(10) << std::right;if (firstName.length() > 10) std::cout << firstName.substr(0, 9) + ".";else std::cout << firstName;3. Ne pas gérer correctement le tampon circulaire
// FAUX - incrémente juste_currentIndex++;
// JUSTE - boucle_currentIndex = (_currentIndex + 1) % 8;4. Accepter les champs vides
// FAUX - accepte toutstd::getline(std::cin, input);contact.setFirstName(input);
// JUSTE - boucle jusqu'à non-videwhile (input.empty()) { std::cout << "Le champ ne peut pas être vide : "; std::getline(std::cin, input);}5. Ne pas gérer EOF (Ctrl+D)
// FAUX - ignore EOF, le programme bloquestd::getline(std::cin, input);
// JUSTE - vérifie la valeur de retour et sortif (!std::getline(std::cin, input)) { std::cout << std::endl; std::exit(0);}6. Rendre les attributs publics
// FAUX - casse l'encapsulationclass Contact {public: std::string firstName; // N'importe qui peut modifier directement !};
// JUSTE - privé avec accesseursclass Contact {private: std::string _firstName;public: void setFirstName(const std::string& name); std::string getFirstName() const;};Conseils de test
Section intitulée « Conseils de test »# Tester la fonctionnalité ADD> ADDEntrez le prénom : JohnEntrez le nom : DoeEntrez le surnom : JDEntrez le numéro de téléphone : 555-1234Entrez le secret le plus sombre : Aime la pizza à l'ananasContact ajouté avec succès !
# Tester le rejet des champs vides> ADDEntrez le prénom :Le champ ne peut pas être vide. Veuillez réessayer.Entrez le prénom : Jane
# Tester SEARCH sans contacts> SEARCHPhoneBook est vide.
# Tester le formatage d'affichage SEARCH> SEARCH Index|First Name| Last Name| Nickname-------------------------------------------- 0| John| Doe| JDEntrez l'index pour voir les détails : 0Prénom : JohnNom : DoeSurnom : JDNuméro de téléphone : 555-1234Secret le plus sombre : Aime la pizza à l'ananas
# Tester la troncation (nom > 10 chars)# Ajouter un contact avec prénom "Christopher"> SEARCH# Devrait afficher "Christoph." (9 chars + point)
# Tester le tampon circulaire (ajouter 9 contacts)# Le contact #9 devrait remplacer le contact #0
# Tester index invalide> SEARCHEntrez l'index pour voir les détails : 9Index invalide.
> SEARCHEntrez l'index pour voir les détails : abcIndex invalide.
# Tester la gestion EOF> (Appuyez sur Ctrl+D)# Le programme devrait quitter proprement avec un retour à la ligneCode final
Section intitulée « Code final »Contact.hpp
#ifndef CONTACT_HPP#define CONTACT_HPP
#include <string>
class Contact {private: std::string _firstName; std::string _lastName; std::string _nickname; std::string _phoneNumber; std::string _darkestSecret; bool _isEmpty;
public: Contact(); ~Contact();
void setFirstName(const std::string& firstName); void setLastName(const std::string& lastName); void setNickname(const std::string& nickname); void setPhoneNumber(const std::string& phoneNumber); void setDarkestSecret(const std::string& secret); void setIsEmpty(bool isEmpty);
std::string getFirstName() const; std::string getLastName() const; std::string getNickname() const; std::string getPhoneNumber() const; std::string getDarkestSecret() const; bool isEmpty() const;};
#endifContact.cpp
#include "Contact.hpp"
Contact::Contact() : _isEmpty(true) {}
Contact::~Contact() {}
void Contact::setFirstName(const std::string& firstName) { _firstName = firstName; }void Contact::setLastName(const std::string& lastName) { _lastName = lastName; }void Contact::setNickname(const std::string& nickname) { _nickname = nickname; }void Contact::setPhoneNumber(const std::string& phoneNumber) { _phoneNumber = phoneNumber; }void Contact::setDarkestSecret(const std::string& secret) { _darkestSecret = secret; }void Contact::setIsEmpty(bool isEmpty) { _isEmpty = isEmpty; }
std::string Contact::getFirstName() const { return _firstName; }std::string Contact::getLastName() const { return _lastName; }std::string Contact::getNickname() const { return _nickname; }std::string Contact::getPhoneNumber() const { return _phoneNumber; }std::string Contact::getDarkestSecret() const { return _darkestSecret; }bool Contact::isEmpty() const { return _isEmpty; }PhoneBook.hpp
#ifndef PHONEBOOK_HPP#define PHONEBOOK_HPP
#include "Contact.hpp"#include <string>
class PhoneBook {private: Contact _contacts[8]; int _currentIndex; int _totalContacts;
std::string _truncate(const std::string& str) const; void _displayContactRow(int index) const; std::string _getInput(const std::string& prompt) const;
public: PhoneBook(); ~PhoneBook();
void addContact(); void searchContacts() const;};
#endifPhoneBook.cpp
#include "PhoneBook.hpp"#include <iostream>#include <iomanip>#include <cstdlib>
PhoneBook::PhoneBook() : _currentIndex(0), _totalContacts(0) {}
PhoneBook::~PhoneBook() {}
std::string PhoneBook::_truncate(const std::string& str) const { if (str.length() > 10) return str.substr(0, 9) + "."; return str;}
void PhoneBook::_displayContactRow(int index) const { std::cout << std::setw(10) << std::right << index << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getFirstName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getLastName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getNickname()) << std::endl;}
std::string PhoneBook::_getInput(const std::string& prompt) const { std::string input;
while (true) { std::cout << prompt; if (!std::getline(std::cin, input)) { std::cout << std::endl; std::exit(0); } if (!input.empty()) break; std::cout << "Le champ ne peut pas être vide. Veuillez réessayer." << std::endl; } return input;}
void PhoneBook::addContact() { Contact newContact;
newContact.setFirstName(_getInput("Entrez le prénom : ")); newContact.setLastName(_getInput("Entrez le nom : ")); newContact.setNickname(_getInput("Entrez le surnom : ")); newContact.setPhoneNumber(_getInput("Entrez le numéro de téléphone : ")); newContact.setDarkestSecret(_getInput("Entrez le secret le plus sombre : ")); newContact.setIsEmpty(false);
_contacts[_currentIndex] = newContact; _currentIndex = (_currentIndex + 1) % 8; if (_totalContacts < 8) _totalContacts++;
std::cout << "Contact ajouté avec succès !" << std::endl;}
void PhoneBook::searchContacts() const { if (_totalContacts == 0) { std::cout << "PhoneBook est vide." << std::endl; return; }
// Afficher l'en-tête std::cout << std::setw(10) << "Index" << "|"; std::cout << std::setw(10) << "First Name" << "|"; std::cout << std::setw(10) << "Last Name" << "|"; std::cout << std::setw(10) << "Nickname" << std::endl; std::cout << std::string(44, '-') << std::endl;
// Afficher tous les contacts for (int i = 0; i < _totalContacts; i++) _displayContactRow(i);
// Obtenir l'index de l'utilisateur std::cout << "Entrez l'index pour voir les détails : "; std::string indexStr; if (!std::getline(std::cin, indexStr)) { std::cout << std::endl; return; }
// Valider l'index if (indexStr.length() != 1 || indexStr[0] < '0' || indexStr[0] > '7') { std::cout << "Index invalide." << std::endl; return; }
int index = indexStr[0] - '0'; if (index >= _totalContacts) { std::cout << "Index invalide." << std::endl; return; }
// Afficher les détails complets du contact std::cout << "Prénom : " << _contacts[index].getFirstName() << std::endl; std::cout << "Nom : " << _contacts[index].getLastName() << std::endl; std::cout << "Surnom : " << _contacts[index].getNickname() << std::endl; std::cout << "Numéro de téléphone : " << _contacts[index].getPhoneNumber() << std::endl; std::cout << "Secret le plus sombre : " << _contacts[index].getDarkestSecret() << std::endl;}main.cpp
#include "PhoneBook.hpp"#include <iostream>#include <cstdlib>
int main() { PhoneBook phonebook; std::string command;
while (true) { std::cout << "Entrez une commande (ADD, SEARCH, EXIT) : "; if (!std::getline(std::cin, command)) { std::cout << std::endl; break; }
if (command == "ADD") phonebook.addContact(); else if (command == "SEARCH") phonebook.searchContacts(); else if (command == "EXIT") break; }
return 0;}Exercice 02 : The Job Of Your Dreams (Bonus)
Section intitulée « Exercice 02 : The Job Of Your Dreams (Bonus) »Note : C’est un exercice BONUS. Complétez-le après ex00 et ex01 si vous voulez de la pratique supplémentaire avec les membres statiques et la rétro-ingénierie.
Cet exercice est différent. Au lieu de construire à partir de zéro, on vous donne des morceaux et vous devez déduire les parties manquantes. Vous recevez :
Account.hpp- La déclaration complète de la classetests.cpp- Le code de test qui utilise la classe- Un fichier log - La sortie attendue montrant ce qui devrait se passer
Votre travail : Écrire Account.cpp pour que les tests produisent une sortie correspondant au log (sauf les timestamps).
Analyse du sujet
Section intitulée « Analyse du sujet »Ce qui vous est donné :
Le fichier header Account.hpp déclare :
- Des membres statiques suivant l’état global (
_nbAccounts,_totalAmount, etc.) - Des membres d’instance pour chaque compte (
_accountIndex,_amount, etc.) - Une méthode privée
_displayTimestamp() - Constructeur, destructeur, et diverses méthodes
Ce que vous devez déterminer :
- Comment initialiser les membres statiques (ils DOIVENT être définis quelque part)
- Comment formater le timestamp du log :
[YYYYMMDD_HHMMSS] - Le format exact de sortie (paires
clé:valeurséparées par des points-virgules) - Ce que chaque méthode devrait afficher et quand
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Analyser le fichier header
Lisez chaque ligne de Account.hpp. Listez ce que vous devez implémenter :
- 4 membres statiques nécessitent une initialisation
- 4 méthodes getter statiques
- 1 méthode d’affichage statique (
displayAccountsInfos) - Constructeur et destructeur
makeDeposit,makeWithdrawal,checkAmount,displayStatus_displayTimestampprivée
Étape 2 - Étudier le fichier log
Le log montre EXACTEMENT quelle sortie produire. Observations clés :
- Chaque ligne commence par un timestamp :
[19920104_091532] - Le format est séparé par des points-virgules :
index:0;amount:42;created - Les appels au destructeur se font dans l’ordre INVERSE de la construction
Étape 3 - Mapper les lignes du log aux méthodes
| Pattern du log | Méthode |
|---|---|
accounts:N;total:N;deposits:N;withdrawals:N | displayAccountsInfos() |
index:N;amount:N;created | Constructeur |
index:N;amount:N;closed | Destructeur |
index:N;p_amount:N;deposit:N;amount:N;nb_deposits:N | makeDeposit() |
index:N;p_amount:N;withdrawal:refused | makeWithdrawal() (échec) |
index:N;p_amount:N;withdrawal:N;amount:N;nb_withdrawals:N | makeWithdrawal() (succès) |
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Initialisation des membres statiques :
#include "Account.hpp"
// CRITIQUE : Les membres statiques DOIVENT être initialisés hors de la classe// Cela alloue le stockage réel pour euxint Account::_nbAccounts = 0;int Account::_totalAmount = 0;int Account::_totalNbDeposits = 0;int Account::_totalNbWithdrawals = 0;Pourquoi hors de la classe ? Les membres statiques sont partagés par TOUTES les instances. Le header ne fait que les déclarer ; vous devez les définir (allouer le stockage pour eux) exactement une fois dans un fichier .cpp.
Étape 2 - Formatage du timestamp :
#include <ctime>#include <iomanip>
void Account::_displayTimestamp() { std::time_t now = std::time(NULL); std::tm* local = std::localtime(&now);
std::cout << "[" << (local->tm_year + 1900) // Année depuis 1900 << std::setfill('0') << std::setw(2) << (local->tm_mon + 1) // Mois 0-11 << std::setfill('0') << std::setw(2) << local->tm_mday << "_" << std::setfill('0') << std::setw(2) << local->tm_hour << std::setfill('0') << std::setw(2) << local->tm_min << std::setfill('0') << std::setw(2) << local->tm_sec << "] ";}Comprendre <ctime> :
| Fonction | But |
|---|---|
std::time(NULL) | Obtient l’heure courante en secondes depuis epoch |
std::localtime(&now) | Convertit en struct heure locale (tm) |
tm->tm_year | Années depuis 1900 (ajouter 1900 pour l’année réelle) |
tm->tm_mon | Mois 0-11 (ajouter 1 pour format humain) |
Étape 3 - Constructeur et destructeur :
Account::Account(int initial_deposit) : _amount(initial_deposit), _nbDeposits(0), _nbWithdrawals(0) { _accountIndex = _nbAccounts++; _totalAmount += initial_deposit;
_displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";created" << std::endl;}
Account::~Account() { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";closed" << std::endl;}Étape 4 - Méthodes de dépôt et retrait :
void Account::makeDeposit(int deposit) { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";p_amount:" << _amount << ";deposit:" << deposit;
_amount += deposit; _nbDeposits++; _totalAmount += deposit; _totalNbDeposits++;
std::cout << ";amount:" << _amount << ";nb_deposits:" << _nbDeposits << std::endl;}
bool Account::makeWithdrawal(int withdrawal) { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";p_amount:" << _amount << ";withdrawal:";
if (withdrawal > _amount) { std::cout << "refused" << std::endl; return false; }
_amount -= withdrawal; _nbWithdrawals++; _totalAmount -= withdrawal; _totalNbWithdrawals++;
std::cout << withdrawal << ";amount:" << _amount << ";nb_withdrawals:" << _nbWithdrawals << std::endl; return true;}Pièges courants
Section intitulée « Pièges courants »-
Oublier d’initialiser les membres statiques
- Sans initialisation, vous aurez des erreurs de liaison
- Doit être fait dans le fichier .cpp, pas le header
-
Mauvais format de timestamp
- Le mois est 0-11, pas 1-12
- L’année est depuis 1900, pas l’année réelle
-
Oublier de mettre à jour les totaux globaux
- Chaque dépôt/retrait doit mettre à jour les compteurs statiques
-
Ordre des destructeurs
- Les objets sont détruits dans l’ordre inverse de leur construction
- Vérifiez que votre log correspond à cet ordre
Conseils de test
Section intitulée « Conseils de test »Comparez votre sortie avec le fichier log fourni. Les timestamps seront différents, mais tout le reste doit correspondre exactement :
# Compilez et exécutezc++ -Wall -Wextra -Werror -std=c++98 *.cpp -o account./account > my_output.log
# Comparez (ignorez les timestamps)diff <(sed 's/\[[0-9_]*\]/[TIMESTAMP]/g' 19920104_091532.log) \ <(sed 's/\[[0-9_]*\]/[TIMESTAMP]/g' my_output.log)Si les fichiers correspondent (sauf timestamps), votre implémentation est correcte !