C++ : classes Introduction aux Langages Orientés Objets Qu’est-ce qu’un objet ? Propriétés d'un objet Notion d’héritage et de polymorphisme Les classes en C++ Interface de classe Implantation Constructeur / destructeur Héritage / polymorphisme
Langage Orienté Objet : qu’est-ce qu’un objet ? Un objet est une entité informatique comprenant : des données membres (ou champs ou attributs ou variables d’instances) des fonctions membres (ou méthodes ou routines) On appelle encapsulation le regroupement des variables et des fonctions au sein d'une même entité. Ex : un objet vecteur est composé de trois réels et d’opérations telles que la translation, la norme, le produit scalaire … L'accès aux données et méthodes peut être réglementé : . Fonctions membres Données membres Partie publique Vision interne Vision externe Partie privée
Objet : exemples (notation UML) DeuxRoues m_tailleRoues m_nbVitesses m_couleur m_poids Accélérer() Freiner() ChangerVitesse() GetCouleur() GetPoids() Nom de la classe CompteBancaire m_numéro m_solde m_propriétaire Créditer() Débiter() Fermer() Numéro() Solde() Propriétaire() Vision interne Données membres ou attributs Vision interne Fonctions membres ou méthodes Vision externe Vision externe
Propriétés d’un objet Un objet possède un état : L’état correspond à la valeur de ses attributs à un instant donné. Il peut varier au cours du temps. Un objet est décrit par une classe : Une classe est un prototype qui définit des attributs et des méthodes communes à tous les objets d'une certaine nature. C’est donc un modèle utilisé pour créer plusieurs objets présentant des caractéristiques communes. Un objet possède une identité : Les objets peuvent être distingués grâce à leurs existences inhérentes et non grâce à la description des propriétés qu'ils peuvent avoir. Deux objets peuvent être distincts même si tous leurs attributs ont des valeurs identiques. Ne pas confondre instance d'objet et classe d'objets Une instance de classe fait référence à une chose précise Une classe désigne un groupe de choses similaires Ex : Le vélo de mon voisin et le mien sont deux instances de la classe vélo, même s’ils sont strictement identiques
Notion d’héritage L'héritage est un principe propre à la POO qui permet de créer une nouvelle classe à partir d'une classe existante. La classe nouvellement créée, dite classe dérivée, contient les attributs et les méthodes de la classe dont elle dérive, auxquelles s’ajoutent de nouveaux attributs et de nouvelles méthodes propres à la classe dérivée. L’héritage permet donc de définir une hiérarchie de classes : La classe de base est une classe générique. Les classes dérivées sont de plus en plus spécialisées classe de base ou classe mère ou classe parente Deux roues classes dérivées ou classes filles Sans moteur A moteur Vélo Patinette Moto Scooter Mobylette
Notion de polymorphisme Une classe dérivée peut fournir une nouvelle définition d’une méthode d'une classe parent car elle peut avoir besoin de réagir différemment à l'appel de cette méthode. Cette redéfinition substituera une méthode à une autre : c’est la spécialisation. La notion de polymorphisme signifie que, la même opération pouvant se comporter différemment sur différentes classes de la hiérarchie, il est possible d'appeler la méthode d'un objet sans se soucier de son type intrinsèque. Ceci permet de faire abstraction des détails des classes spécialisées d'une famille d'objet, en les masquant par une interface commune (qui est la classe de base). Velo m_typePedale GetTypePedale() virtual Accélérer() virtual Freiner() virtual ChangerVitesse() DeuxRoues m_tailleRoues m_couleur m_poids m_nbVitesses virtual Accélérer() virtual Freiner() virtual ChangerVitesse() GetCouleur() GetPoids() La fonction Accélérer() n’est pas la même pour un Velo et une Moto. La redéfinition de cette fonction dans chacune des sous-classes entraînera un comportement différent suivant que le DeuxRoues est un Velo ou une Moto. Moto m_moteur m_boiteVitesse FairePlein() AllumerClignotant() virtual Accélérer() virtual Freiner() virtual ChangerVitesse()
Programmation orientée objet vs programmation séquentielle Avantages : Programmes plus faciles à maintenir Si on décide de modifier la structure des données dans un programme séquentiel, presque tout le code est à réécrire Programmation plus claire : les fonctions sont rattachées à un type de données Modularité accrue -> possibilité de ré-utiliser le code Inconvénients : Le programme résultant peut être moins efficace (en termes de taille mémoire et de rapidité)
Classe : interface C’est la description de la structure interne de la classe class Ellipse { Visibilité des membres : public : membres accessibles à tous private : membres accessibles à partir de la classe ; accès impossible par l’extérieur protected : membres accessibles à partir de la classe et des classes dérivées ; accès impossible par l’extérieur protected : float m_cX, m_cY; float m_a, m_b; public : void deplace(float dx, float dy); void zoom(float z); float surface(); Vision interne Vision externe }; NB : dans cet exemple, on a choisi de représenter l'ellipse en interne à la classe par les coordonnées de son centre (cX, cY), par son grand axe a et par son petit axe b.
Classe : implantation C’est la définition des fonctions associées Interface de la classe Ellipse class Ellipse { protected : float m_cX, m_cY; float m_a, m_b; public : void deplace(float dx, float dy); void zoom(float z); float surface(); }; void Ellipse::deplace(float dx, float dy) { m_cX += dx; m_cY += dy; } void Ellipse ::zoom(float z) m_a *= z; m_b *= z; float Ellipse ::surface() return 3.14 * m_a * m_b / 4.; Opérateur de portée
Classe : instanciation Interface de la classe Ellipse class Ellipse { protected : float m_cX, m_cY; float m_r; public : void deplace(float dx, float dy); void zoom(float z); float surface(); }; Instancier une classe permet de créer un objet (analogue à une déclaration de variable) Instanciation statique de l’objet e, e étant une variable de type Ellipse int main() { Ellipse e; e.deplace(50, 0); float s = e.surface(); e.zoom(1.5); e.m_cX = 30; // Impossible e. deplace(20); // Impossible } Implantation de la classe Ellipse void Ellipse::deplace(float dx, float dy) { m_cX += dx; m_cY += dy; } void Ellipse::zoom(float z) m_a *= z; m_b *= z; float Ellipse::surface() return 3.14 * m_a * m_b / 4.; Accès aux membres par le "."
Organisation (usuelle) des fichiers Par convention, l'extension d'un fichier contenant du code C est .cpp, .c++ , .cc , .cxx ou .C L'extension d'un fichier de déclarations, appelé header, est .h Par convention, on crée un fichier .cpp et un .h par classe, chaque fichier ayant le nom de la classe en minuscule. ellipse.h ellipse.cpp main.cpp class Ellipse { protected : float m_cX, m_cY; float m_a, m_b; public : void deplace(float dx, float dy); void zoom(float z); float surface(); }; #include “ellipse.h” void Ellipse::deplace(float dx, float dy) { m_cX += dx; m_cY += dy; } void Ellipse ::zoom(float z) m_a *= z; m_b *= z; float Ellipse :: surface() return 3.14 * m_a * m_b / 4.; #include "ellipse.h" int main() { Ellipse e; e.deplace(50, 0); float s = e.surface(); e.zoom(1.5); return 0; } Par convention, les noms de classe commencent par une majuscule, les données membres par _ ou m_, les fonctions membres par une minuscule.
Constructeurs de classe Le constructeur est une fonction membre qui sert à initialiser les données membres de l’objet Systématiquement appelé quand un objet est instancié. N’a pas de type de retour Porte le nom de l’objet Une même classe peut avoir plusieurs constructeurs Constructeurs particuliers : constructeur par défaut Ne contient aucun argument Automatiquement généré si aucun constructeur n’est défini A définir explicitement dans le cas contraire constructeur de copie Contient comme argument un objet du même type Sert à créer des clones d’objets Automatiquement généré par le compilateur (copie membre à membre) mais pas toujours satisfaisant
Constructeurs de classe : exemple ellipse.h ellipse.cpp main.cpp class Ellipse { public : Ellipse(); // Constructeur par défaut Ellipse(float x, float y, float a, float b); Ellipse(const Ellipse & e); protected : float m_cX, m_cY; float m_a, m_b; void deplace(float dx, float dy); void zoom(float z) ; float surface() ; }; #include “ellipse.h” Ellipse::Ellipse() { m_cX = m_ cY = 0; m_a = m_b = 1; } Ellipse::Ellipse(float x, float y, float a, float b) : m_ cX(x), m_ cY(y), m_a(a), m_b(b) Ellipse::Ellipse(const Ellipse & e) m_ cX = e. m_ cX; m_ cY = e. m_ cY; m_a = e.m_a ; m_b = e.m_b; void Ellipse::deplace(float dx, float dy) m_ cX += dx; m_cY += dy; etc … #include "ellipse.h" int main() { Ellipse e1; Ellipse e2(2.5, 6.5, 12, 15); Ellipse e3(e1); // e3 est un clone de e1 Ellipse e4 = e1; // e4 est un clone de e1 return 0; }
Destructeur de classe Fonction membre systématiquement appelée juste avant la destruction d’un objet Porte le nom de la classe et est précédé de ~ Pas de type de retour Pas d’arguments Un seul par classe Permet de libérer les ressources ellipse.h ellipse.cpp prog.cpp class Ellipse { public : Ellipse(); // Constructeur par défaut Ellipse (float x, float y, float a, float b); Ellipse(const Ellipse & e); ~Ellipse(); // Destructeur protected : float m_cX, m_cY; float m_a, m_b; void deplace(float dx, float dy); void zoom(float z); float surface(); }; #include “ellipse.h” Ellipse::Ellipse() { m_cX = m_ cY = 0; m_a = m_b = 1; } etc … Ellipse::~ Ellipse() // Libération des ressources void Ellipse::deplace(float dx, float dy) m_cX += dx; m_cY += dy; #include "ellipse.h" int main() { Ellipse e; Ellipse* pE = new Ellipse(2.5, 6.5, 12, 15); delete pE; // appelle le destructeur pour pE return 0; // Le destructeur est implicitement appelé pour e. }
Héritage L’héritage permet de spécialiser une classe en définissant une relation de type « est une sorte de ». Un cercle est un spécialisation d'une ellipse, il en possède les propriétés plus d'autres qui lui sont spécifiques. On dérive donc la classe Cercle de la classe Ellipse. Forme Ellipse Rectangle Cercle Carre
Héritage L’héritage permet de spécialiser une classe en définissant une relation de type « est une sorte de ». Un cercle est un spécialisation d'une ellipse, il en possède les propriétés plus d'autres qui lui sont spécifiques. On dérive donc la classe Cercle de la classe Ellipse. ellipse.h cercle.h prog.cpp class Ellipse { public : Ellipse(); Ellipse (float x, float y, float a, float b); Ellipse(const Ellipse & e); ~Ellipse(); protected : float m_cX, m_cY; float m_a, m_b; void deplace(float dx, float dy); void zoom(float z); float surface(); virtual void affiche(); }; #include “ellipse.h” class Cercle : public Ellipse { public : Cercle(); Cercle (float x, float y, float diametre); ~ Cercle(); virtual void affiche(); }; #include "cercle.h" int main() { Cercle c(5, 5, 15); c. affiche(); return 0; } cercle.cpp Autorise la redéfinition de la fonction dans les classes dérivées #include <iostream.h> #include “cercle.h” Cercle::Cercle() : public Ellipse() { } Cercle::Cercle(float x, float y, float diametre) : public Ellipse( x, y, diametre, diametre) void Cercle::affiche() std::cout << "Cercle de rayon " << m_a / 2 << "\n"; Le constructeur de la classe dérivée appelle généralement un des constructeurs de la classe de base. ellipse.cpp #include <iostream.h> void Ellipse::affiche() { std::cout << "Ellipse de grand axe " << m_a; std ::cout << " et de petitt axe " << m_b << "\n"; }
Polymorphisme Un objet héritant une méthode d'une classe parente peut réagir de façon différente à l'appel de cette méthode. ellipse.h cercle.h main.cpp class Ellipse { public : Ellipse(); Ellipse (float x, float y, float a, float b); Ellipse(const Ellipse & e); ~Ellipse(); protected : float m_cX, m_cY; float m_a, m_b; void deplace(float dx, float dy); void zoom(float z); float surface(); virtual void affiche(); }; #include “ellipse.h” class Cercle : public Ellipse { public : Cercle(); Cercle (float x, float y, float d); ~ Cercle(); virtual void affiche(); }; #include " cercle.h" int main() { Ellipse e(0, 0, 8.5, 10.2); e.deplace(-1, 1); e.affiche(); Cercle c(-2.5, 2.5, 7,4); c.deplace(0.5, 1.5); c. affiche(); Ellipse *p1; p1 = new Ellipse; p1 ->affiche(); Ellipse *p2; p2 = new Cercle; p2->affiche(); return 0; } La fonction deplace() n’est pas redéfinie dans la classe Cercle, appelle celle de Ellipse. La fonction affiche() est redéfinie dans la classe Cercle, appelle celle de Cercle. cercle.cpp Appelle la fonction affiche() de la classe Ellipse. #include “cercle.h” Cercle::Cercle() : public Ellipse() { } Cercle::Cercle(float x, float y, float d) : public Ellipse( x, y, d, d) void Cercle::affiche() std::cout << "Cercle de rayon "; std::cout << m_a / 2 << "\n"; Si la fonction affiche() est virtuelle et est redéfinie dans la classe Cercle, appelle celle de Cercle bien que le pointeur soit de type Ellipse. C'est le mécanisme de polymorphisme d'héritage. ellipse.cpp #include <iostream.h> void Ellipse::affiche() { std::cout << "Ellipse de grand axe " << m_a; std ::cout << " et de petit axe " << m_b << "\n"; }
Vocabulaire Variable : associe un nom (un symbole) à une valeur qui peut éventuellement varier au cours du temps ; une variable est d’un type donné, défini une fois pour toute (type prédéfini dans le langage ou créé par le développeur). Encapsulation : regroupement des variables et des fonctions au sein d'une même entité appelée « classe ». Classe : prototype qui définit des attributs et des méthodes communes à tous les objets d'une certaine nature. Interface de classe : description de la structure interne de la classe incluant une liste des données membres et le prototype des fonctions membres ; dans un « .h ». Implantation de classe : définition (code) des fonctions déclarées dans l’interface de classe ; dans un « .cpp ». Instanciation d’un objet : permet de créer un objet d’un type donné ; analogue à une déclaration de variable. Héritage : permet de définir une hiérarchie de classe, chaque classe fille héritant des méthodes et des données de son/ses antécédent(s). Polymorphisme : deux objets héritant une même méthode d'une classe parente, peuvent réagir de façon différente à l'appel de cette méthode et, à cette fin, redéfinir la méthode. Il est ensuite possible d'appeler la méthode d'un objet sans se soucier de son type intrinsèque.