Chapitre 1 – Les classes
Document réservé
Vous consultez actuellement la présentation publique de ce chapitre.
Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.
Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).
1.6 Comment une méthode sait-elle sur quel objet elle travaille ? . . . . . . . . . . . . . .
1.12 La liste d’initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2026 – C++ Partie II
AccueilOrganiser un projet C++ Jusqu’à présent, tous les programmes de cet ouvrage étaient contenus dans un seul fichier source. Cette organisation est parfaitement adaptée à de petits exemples, mais elle devient rapidement difficile à maintenir lorsque les programmes prennent de l’importance. Imaginons par exemple un projet contenant plusieurs milliers de lignes de code. Regrouper toutes les fonctions, toutes les structures et toutes les classes dans un unique fichier rendrait le programme difficile à lire, à modifier et à maintenir. Pour cette raison, un projet C++ est généralement réparti entre plusieurs fichiers, chacun ayant un Dans cette organisation, • main.cpp contient la fonction main() ainsi que le déroulement général du programme. • Date.h contient la déclaration de la classe Date. • Date.cpp contient la définition des fonctions membres de cette classe. Cette organisation n’est pas imposée par le langage C++, mais elle est devenue un standard dans la plupart des projets, car elle facilite la lecture, la maintenance et la réutilisation du Les fichiers d’en-tête Lorsqu’un projet comporte plusieurs fichiers source, ceux-ci doivent pouvoir connaître les structures, les classes et les fonctions définies dans les autres fichiers. Les fichiers d’en-tête, dont l’extension est généralement .h, ont précisément pour rôle de mettre ces informations à disposition des autres fichiers du projet. 2026 – C++ Partie II Un fichier d’en-tête contient principalement des déclarations, c’est-à-dire les informations nécessaires pour utiliser un type ou une fonction, sans en donner les détails d’implémentation. Par exemple, le fichier Date.h peut contenir la déclaration suivante : void afficher() const; À ce stade, seule la déclaration de la fonction afficher() est connue. Son code sera écrit dans un fichier source distinct nommé Date.cpp. Le fichier main.cpp connaît ainsi l’existence de la classe Date grâce au fichier Date.h. Lorsque la fonction afficher() est appelée, son code est recherché dans le fichier Date.cpp lors de l’édition des liens 1 . La réalisation d’un programme C++ se déroule en plusieurs étapes : • les fichiers source (.cpp) sont d’abord compilés individuellement ; • cette compilation produit des fichiers objets ; • ces fichiers objets sont ensuite réunis lors de l’édition des liens (linking) afin de produire le programme exécutable. Dans ce chapitre, nous utiliserons simplement ce mécanisme sans entrer dans ses détails. Nous reviendrons plus loin sur chacune de ces étapes et apprendrons notamment à automatiser la compilation à l’aide de Makefiles. 1. L’édition des liens (ou linking en anglais) est l’étape qui suit la compilation. Elle consiste à rassembler les différents fichiers objets produits par le compilateur afin de former un unique programme exécutable. Nous étudierons ce mécanisme plus en détail dans un chapitre consacré à la compilation des projets C++. La réalisation d’un programme C++ se déroule en plusieurs étapes : • les fichiers source (.cpp) sont d’abord compilés individuellement ; • cette compilation produit des fichiers objets ; • ces fichiers objets sont ensuite réunis lors de l’édition des liens (linking) afin de produire le programme Dans ce chapitre, nous utiliserons simplement ce mécanisme sans entrer dans ses détails. Nous reviendrons plus loin sur chacune de ces étapes et apprendrons notamment à automatiser la compilation à l’aide de Makefiles.
Accueil2026 – C++ Partie II • Un fichier d’en-tête contient les déclarations nécessaires pour utiliser une structure, une classe ou une fonction. • Les définitions correspondantes sont généralement placées dans un fichier source (.cpp) Les fichiers d’en-tête ne sont pas réservés aux classes. Ils peuvent également contenir des déclarations de fonctions, de structures, d’énumérations, de constantes ou d’alias de types (typedef ou using). Les fichiers source, dont l’extension est généralement .cpp, contiennent les définitions des fonctions et des méthodes déclarées dans les fichiers d’en-tête. Ils renferment donc le code réellement exécuté par le programme. Le fichier Date.cpp peut par exemple contenir la définition de la fonction afficher() déclarée dans void Date::afficher() const std::cout << « … » << std::endl; La déclaration de la fonction est placée dans Date.h, tandis que son implémentation est écrite dans Cette séparation facilite la lecture du programme et permet de répartir le travail entre plusieurs • Les fichiers d’en-tête (.h) contiennent principalement les déclarations. • Les fichiers source (.cpp) contiennent les définitions correspondantes. Notre première classe Jusqu’à présent, nous avons utilisé des structures pour regrouper des données et des fonctions indépendantes pour les manipuler. Par exemple, nous avons défini une structure Date ainsi qu’une fonction permettant de l’afficher.
Accueil2026 – C++ Partie II void afficher(const Date& d) std::cout << d.jour << « / » << d.annee << std::endl; Cette organisation fonctionne parfaitement. Cependant, une question se pose naturellement. Pourquoi la fonction afficher() est-elle écrite à l’extérieur de la structure alors qu’elle manipule exclusivement des objets de type Date ? Ne serait-il pas plus naturel de regrouper les données et les fonctions qui les manipulent au même endroit ? Les fonctions afficher(), ajouterJour() ou encore estBissextile() manipulent toutes des objets de type Date. Où serait-il le plus logique de les placer ? La réponse à cette question est l’une des idées fondamentales de la programmation orientée objet. Une classe permet de regrouper dans une même entité les données et les fonctions qui les manipulent. Une classe peut ainsi être considérée comme une évolution naturelle des structures étudiées jusqu’à void afficher() const; On remarque que la fonction afficher() fait désormais partie de la classe. Voilà les 3 fichiers du programme que l’on va étudier à présent.
Accueil2026 – C++ Partie II void afficher() const; Code 1.2 – Date.cpp void Date::afficher() const std::cout << jour << « / » << annee << std::endl; Code 1.3 – main.cpp aujourdHui.jour = 31; aujourdHui.mois = 7; aujourdHui.annee = 2026; aujourdHui.afficher(); Vous pouvez télécharger le fichiers zip contenant les codes ici : Date_01. Une fonction appartenant à une classe est appelée une méthode (ou fonction membre). La classe Date déclare une méthode chargée d’afficher son contenu, la méthode afficher().
Accueil2026 – C++ Partie II void afficher() const; La méthode afficher() est uniquement déclarée dans le fichier Date.h. Son implémentation est écrite dans le fichier Date.cpp. On remarque que la définition de la méthode est précédée de Date::. Cette écriture indique au compilateur que la méthode afficher() appartient à la classe Date. La définition de la méthode est implémentée dans le fichier Date.cpp comme on le voit ci-dessus. L’appel de la méthode s’effectue directement à partir de l’objet grâce à l’opérateur .. aujourdHui.afficher(); Cette écriture est souvent plus naturelle que l’appel d’une fonction externe, car la méthode appartient directement à l’objet qu’elle manipule. L’opérateur . était déjà utilisé pour accéder aux données membres d’une structure. Avec les classes, il permet également d’appeler les méthodes de l’objet. • Une méthode est une fonction appartenant à une classe. • Elle est appelée directement à partir d’un objet à l’aide de l’opérateur .. Sans exécuter le programme, indiquez le rôle de chacun des deux opérateurs . dans les instructions suivantes. aujourdHui.jour = 31; aujourdHui.afficher(); 1. Le premier . permet l’accès à une donnée membres, 2. et le second, appelle une méthode. Une question reste cependant en suspens : comment la méthode afficher() sait-elle sur quel objet elle travaille ? Nous répondrons à cette question dans la section suivante. Comment une méthode sait-elle sur quel objet elle travaille ? Dans le programme précédent, la méthode afficher() est appelée à partir de l’objet aujourdHui :
Accueil2026 – C++ Partie II aujourdHui.afficher(); Pourtant, dans le fichier Date.cpp, le nom aujourdHui n’apparaît nulle part. void Date::afficher() const std::cout << jour << « / » << annee << std::endl; Comment la méthode sait-elle alors quelles valeurs afficher ? Lorsqu’une méthode est appelée, le compilateur lui transmet automatiquement l’objet sur lequel elle Dans notre exemple, la méthode reçoit implicitement l’objet aujourdHui. C’est pourquoi elle peut accéder directement aux membres jour, mois et annee, sans qu’il soit nécessaire d’écrire : Cette transmission est entièrement automatique. Le programmeur n’a rien de particulier à Nous découvrirons plus loin que le langage C++ met en œuvre ce mécanisme à l’aide d’un pointeur implicite nommé this. Que se passerait-il si l’on écrivait le programme suivant ? aujourdHui.afficher(); La méthode afficher() est pourtant unique. Comment peut-elle afficher deux objets différents ? Parce que le compilateur transmet automatiquement l’objet concerné à chaque appel. La première version de notre classe Date fonctionne correctement. Ses données sont cependant accessibles librement depuis l’extérieur de la classe. Par exemple, le programme suivant est parfaitement autorisé :
Accueil2026 – C++ Partie II aujourdHui.annee = 2026; Le compilateur n’émet pourtant aucun message d’erreur. Pourtant, une date telle que le 45e jour du mois de −8 n’a aucun sens.Le problème est que n’importe quelle partie du programme peut modifier directement les données de l’objet, sans aucun contrôle. Comment empêcher une partie du programme d’écrire des valeurs incohérentes dans un objet tout en autorisant la lecture ou la modification de ces valeurs lorsque cela est justifié ? La programmation orientée objet apporte une solution simple à ce problème : rendre les données privées. Les membres placés après le mot-clé private ne sont accessibles qu’à l’intérieur de la classe. void afficher() const; aujourdHui.jour = 31; Cette fois, le compilateur refuse la compilation. Les données de l’objet ne sont plus accessibles directement depuis l’extérieur de la classe. Le mot-clé private ne protège pas les données contre le programmeur. Il protège les données contre les erreurs de programmation en imposant que toute modification passe par les méthodes de la classe. Une question apparaît immédiatement. Si les données sont privées, comment créer une date, consulter ses valeurs ou les modifier ? La réponse est simple : seules les méthodes de la classe sont autorisées à accéder directement aux données privées. Nous allons découvrir progressivement les différents mécanismes permettant de le faire, en commençant par les constructeurs. Depuis que les données de notre classe sont devenues privées, les instructions suivantes ne sont plus
Accueil2026 – C++ Partie II aujourdHui.jour = 31; aujourdHui.mois = 7; aujourdHui.annee = 2026; Le compilateur refuse désormais toute tentative de modification directe des membres de la classe. Une question se pose alors naturellement : Comment créer un objet correctement initialisé si ses données sont privées ? La réponse est apportée par les constructeurs. Un constructeur est une méthode particulière appelée automatiquement lors de la création d’un objet. Son rôle est d’initialiser les données de cet objet afin qu’il soit immédiatement utilisable. Date(int j, int m, int a); void afficher() const; Contrairement aux autres méthodes, le constructeur porte exactement le même nom que la classe. Il ne possède aucun type de retour, pas même void. Une implémentation possible est : Date::Date(int j, int m, int a) L’utilisation du constructeur est : Date aujourdHui(31, 7, 2026); aujourdHui.afficher();
Accueil2026 – C++ Partie II On remarque que le constructeur n’est jamais appelé explicitement. L’instruction Date aujourdHui(31, 7, 2026); crée l’objet aujourdHui puis appelle automatiquement le constructeur correspondant. Un constructeur est exécuté une seule fois : au moment de la création de l’objet. Par la suite, les données pourront être consultées ou modifiées à l’aide des méthodes prévues Le constructeur est une méthode particulière qui : • porte le même nom que la classe ; • ne possède aucun type de retour ; • est appelé automatiquement lors de la création de l’objet ; • initialise les données de celui-ci. Depuis que les données de notre classe sont devenues privées, les instructions suivantes ne sont plus std::cout << aujourdHui.jour << std::endl; Le compilateur refuse tout accès direct aux membres privés de la classe. Il est donc nécessaire de fournir un moyen de consulter ces informations sans pour autant rendre les données accessibles à l’extérieur de la classe. Pour cela, on utilise des méthodes particulières appelées getters. Date(int j, int m, int a); int getJour() const; int getMois() const; int getAnnee() const;
Accueil2026 – C++ Partie II dont l’implémentation Date_02.cpp est : #include « Date_02.h » Date::Date(int j, int m, int a) int Date::getJour() const int Date::getMois() const int Date::getAnnee() const #include « Date_02.h » Date aujourdHui(31, 7, 2026); std::cout << aujourdHui.getJour() << « / » << aujourdHui.getMois() << « / » << aujourdHui.getAnnee() << std::endl; Les getters permettent de consulter les données d’un objet sans donner un accès direct à ses membres privés. Ainsi, la classe conserve le contrôle de ses données tout en autorisant leur lecture. Les getters ne modifient pas l’objet. Ils sont donc généralement déclarés avec le mot-clé const.
Accueil2026 – C++ Partie II • Un getter est une méthode permettant de lire la valeur d’un membre privé. • En règle générale, un getter : • retourne une valeur ; • ne modifie jamais l’objet ; • est déclaré const. Pourquoi le programme suivant est-il refusé par le compilateur ? Date aujourdHui(31,7,2026); std::cout << aujourdHui.jour; Quelle méthode faut-il utiliser à la place ? aujourdHui.getJour() Les préfixes get et set ne sont pas des mots-clés du langage C++. Il s’agit simplement d’une convention largement utilisée. Le compilateur accepte n’importe quel nom de méthode. Toutefois, employer ces préfixes rend le code plus clair et plus facilement compréhensible par les autres programmeurs. Les getters permettent de consulter les données d’un objet. Ils ne permettent cependant pas de les modifier. Pour autoriser une modification tout en conservant les données privées, on utilise des méthodes particulières appelées setters. Un setter reçoit généralement une nouvelle valeur en paramètre puis la stocke dans le membre correspondant de la classe. Le fichier d’en-tête devient Date(int j, int m, int a); int getJour() const; int getMois() const;
Accueil2026 – C++ Partie II int getAnnee() const; void setJour(int j); void setMois(int m); void setAnnee(int a); void afficher() const; L’implémentation des setters est void Date::setJour(int j) void Date::setMois(int m) void Date::setAnnee(int a) Date aujourdHui(31, 7, 2026); aujourdHui.setJour(1); aujourdHui.setMois(8); aujourdHui.setAnnee(2026); aujourdHui.afficher(); Les setters permettent de modifier les données d’un objet sans donner un accès direct à ses membres privés. Toute modification passe donc par une méthode de la classe. Pour l’instant, nos setters se contentent de recopier la valeur reçue dans le membre correspondant. Ils semblent donc peu différents d’une affectation directe. Leur véritable intérêt apparaîtra dans la section suivante, lorsque nous ajouterons des contrôles empêchant l’introduction de valeurs incohérentes dans un objet.
Accueil2026 – C++ Partie II • Un setter est une méthode permettant de modifier un membre privé. • Toute modification des données passe par les méthodes de la classe. • Les setters permettront par la suite de contrôler la validité des valeurs reçues. Encore une fois, les préfixes get et set ne sont pas des mots-clés du langage C++. Il s’agit simplement d’une convention de nommage très largement utilisée. Le compilateur accepte tout autre nom de méthode. Il est même possible de donner aux getters et aux setters le même nom que le membre auquel ils donnent accès : Le compilateur distingue sans ambiguïté les deux méthodes grâce à leur signature a . L’une ne reçoit aucun paramètre (getter), tandis que l’autre en reçoit un (setter). Il s’agit d’un exemple de surcharge de méthodes. Dans ce cours, nous utiliserons toutefois les préfixes get et set, car ils rendent immédiatement le rôle de chaque méthode plus explicite. a. La signature d’une fonction ou d’une méthode est constituée de son nom et de la liste ordonnée de ses paramètres. Le type de retour ne fait pas partie de la signature. Les instructions suivantes seront-elles acceptées ? Non, les deux fonctions ont la même signature. Le type de retour ne fait pas partie de la
Accueil2026 – C++ Partie II Pourquoi le type de retour ne fait-il pas partie de la signature d’une fonction ? Considérons les deux fonctions suivantes : Supposons ensuite les instructions suivantes : double y = valeur(); À première vue, ces deux appels semblent parfaitement valides. Pourtant, le compilateur les refuse. En effet, une valeur de type int peut être convertie en double, et une valeur de type double peut également être convertie en int. Le type de retour ne permet donc pas de déterminer avec certitude quelle fonction doit être appelée. C’est pourquoi le type de retour ne fait pas partie de la signature d’une fonction. Un langage de programmation doit toujours permettre au compilateur de choisir une fonction de manière Notre setter accepte encore des valeurs absurdes. Peut-on empêcher qu’une date devienne le 42 janvier ou le mois 18 ? Valider les données Les setters que nous venons d’écrire permettent de modifier les données d’un objet tout en conservant les membres privés. Pour l’instant, ils se contentent toutefois d’une simple affectation : void Date::setJour(int j) Rien n’empêche donc d’écrire : Date aujourdHui(31, 7, 2026); aujourdHui.setJour(42); Le compilateur accepte parfaitement ce programme alors que le jour 42 n’existe dans aucun calendrier. Notre classe ne garantit donc pas encore la cohérence des données qu’elle contient. Pour remédier à ce problème, il suffit d’effectuer un contrôle avant de modifier le membre privé.
Accueil2026 – C++ Partie II void Date::setJour(int j) if (j >= 1 && j <= 31) Si la valeur proposée est comprise entre 1 et 31, le membre est modifié. Dans le cas contraire, la modification est simplement ignorée. Notre setter vérifie uniquement que le jour est compris entre 1 et 31. Il n’empêche pas encore des dates telles que : • 29 février 2023. La validation complète d’une date est plus délicate et nécessitera plusieurs contrôles supplémentaires. Nous comprenons maintenant l’intérêt des membres privés. Si les données étaient publiques, n’importe quelle partie du programme pourrait leur attribuer une valeur incohérente. En imposant que toute modification passe par les setters, la classe conserve le contrôle de son propre état. La liste d’initialisation Jusqu’à présent, nous avons écrit notre constructeur de la manière suivante : Date::Date(int j, int m, int a) Cette écriture est parfaitement correcte et sera acceptée par tous les compilateurs C++. Il existe toutefois une manière plus naturelle d’initialiser les membres d’un objet : la liste d’initialisation. La liste d’initialisation s’écrit entre la déclaration du constructeur et son corps : Date::Date(int j, int m, int a) Chaque membre est directement initialisé avec la valeur correspondante. Le corps du constructeur peut alors rester vide lorsque aucune autre opération n’est nécessaire.
Accueil2026 – C++ Partie II La liste d’initialisation construit directement les membres de l’objet. Cette écriture est généralement préférée en C++, car elle est plus naturelle et devient indispensable pour certains types de données que nous rencontrerons plus loin. Pour des types simples comme int, les deux écritures produisent généralement le même En revanche, certains membres ne peuvent être initialisés que par une liste d’initialisation, notamment les constantes (const) et les références. Nous reviendrons sur ces cas particuliers plus loin dans cet ouvrage. Pour des types simples comme int, les deux écritures produisent généralement le même En revanche, certains membres ne peuvent être initialisés que par une liste d’initialisation, notamment les constantes (const) et les références. Nous reviendrons sur ces cas particuliers plus loin dans cet ouvrage. Vous pouvez télécharger le fichiers zip contenant les codes vu jusqu’à présent, ici : Date_02.
AccueilP2-Chap-01- Les classes
AccueilTermes à ajouter au glossaire
Codes et programmes contenus dans le chapitre
Date_01
Classe Date avec fichiers .h, .cpp et Makefile.
Date_02
Classe Date avec constructeur, setters, getters, fichiers .h, .cpp et Makefile.
README.pdf
Explications sur l’utilisation des codes