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é.
Données membres
Fonctions membres
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é :
.
Partie privée
Vision interne
Partie publique Vision externe
Objet : exemples (notation UML)
DeuxRoues Nom de la classe
CompteBancaire
m_tailleRoues
m_numéro
Vision m_nbVitesses Données membres
interne ou attributs m_solde Vision
m_couleur interne
m_propriétaire
m_poids Créditer()
Accélérer() Débiter()
Vision Fonctions membres
Freiner() Fermer()
externe ou méthodes
ChangerVitesse() Numéro() Vision
GetCouleur() Solde() externe
GetPoids() Propriétaire()
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
Deux roues classe de base ou classe mère
ou classe parente
classes dérivées
Sans moteur A moteur ou classes filles
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
DeuxRoues GetTypePedale()
m_tailleRoues virtual Accélérer()
virtual Freiner()
m_couleur
virtual ChangerVitesse()
m_poids
La fonction Accélérer() n’est pas la
m_nbVitesses
même pour un Velo et une Moto. La
virtual Accélérer() Moto redéfinition de cette fonction dans
virtual Freiner() m_moteur chacune des sous-classes entraînera un
virtual ChangerVitesse() m_boiteVitesse comportement différent suivant que le
GetCouleur() FairePlein() DeuxRoues est un Velo ou une Moto.
GetPoids() 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 :
Vision protected : – public : membres accessibles à tous
interne float m_cX, m_cY; – private : membres accessibles à
float m_a, m_b; partir de la classe ; accès impossible
par l’extérieur
public : – protected : membres accessibles à
Vision void deplace(float dx, float dy); partir de la classe et des classes
externe void zoom(float z);
dérivées ; accès impossible par
l’extérieur
float surface();
};
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
void Ellipse::deplace(float dx, float dy)
class Ellipse {
{ m_cX += dx;
protected : Opérateur m_cY += dy;
float m_cX, m_cY; de portée }
float m_a, m_b;
public : void Ellipse ::zoom(float z)
void deplace(float dx, float dy); {
void zoom(float z); m_a *= z;
float surface(); m_b *= z;
}; }
float Ellipse ::surface()
{
return 3.14 * m_a * m_b / 4.;
}
Classe : instanciation
Interface de la classe Ellipse
class Ellipse
{
protected : Instancier une classe permet de créer un objet
float m_cX, m_cY; (analogue à une déclaration de variable)
float m_r;
public :
void deplace(float dx, float dy); Instanciation statique de
void zoom(float z); l’objet e, e étant une
float surface(); int main() variable de type Ellipse
}; {
Ellipse e;
Implantation de la classe Ellipse
void Ellipse::deplace(float dx, float dy) Accès aux
{ membres [Link](50, 0);
m_cX += dx; par le "." float s = [Link]();
m_cY += dy; [Link](1.5);
}
void Ellipse::zoom(float z)
e.m_cX = 30; // Impossible
{
m_a *= z; e. deplace(20); // Impossible
m_b *= z; }
}
float Ellipse::surface()
{
return 3.14 * m_a * m_b / 4.;
}
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 [Link] [Link]
#include “ellipse.h” #include "ellipse.h"
class Ellipse
void Ellipse::deplace(float dx, float dy) int main()
{
{ {
protected : m_cX += dx; Ellipse e;
float m_cX, m_cY; m_cY += dy;
float m_a, m_b; } [Link](50, 0);
public : void Ellipse ::zoom(float z) float s = [Link]();
{ [Link](1.5);
void deplace(float dx, float dy);
m_a *= z;
void zoom(float z); m_b *= z; return 0;
float surface(); } }
}; float Ellipse :: surface()
{
return 3.14 * m_a * m_b / 4.;
}
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 [Link] [Link]
class Ellipse #include “ellipse.h” #include "ellipse.h"
{
public : Ellipse::Ellipse() int main()
Ellipse(); // Constructeur par défaut { {
Ellipse(float x, float y, float a, float b); m_cX = m_ cY = 0; Ellipse e1;
Ellipse(const Ellipse & e); m_a = m_b = 1; Ellipse e2(2.5, 6.5, 12, 15);
} Ellipse e3(e1); // e3 est un clone de
protected : e1
float m_cX, m_cY; Ellipse::Ellipse(float x, float y, float a, float b) : Ellipse e4 = e1; // e4 est un clone de
float m_a, m_b; m_ cX(x), m_ cY(y), m_a(a), m_b(b) e1
{ return 0;
public : } }
void deplace(float dx, float dy);
void zoom(float z) ; Ellipse::Ellipse(const Ellipse & e)
float surface() ; {
}; 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 …
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 [Link] [Link]
#include “ellipse.h” #include "ellipse.h"
class Ellipse
{ Ellipse::Ellipse() int main()
public : { {
Ellipse(); // Constructeur par défaut m_cX = m_ cY = 0; Ellipse e;
Ellipse (float x, float y, float a, float b); m_a = m_b = 1; Ellipse* pE = new Ellipse(2.5, 6.5, 12, 15);
Ellipse(const Ellipse & e); }
~Ellipse(); // Destructeur
etc … delete pE; // appelle le destructeur pour pE
protected :
float m_cX, m_cY; Ellipse::~ Ellipse() return 0;
float m_a, m_b; {
// Libération des ressources // Le destructeur est implicitement appelé pour e.
public : } }
void deplace(float dx, float dy);
void zoom(float z); void Ellipse::deplace(float dx, float dy)
float surface(); {
}; m_cX += dx; m_cY += dy;
}
etc …
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 [Link]
class Ellipse #include “ellipse.h” #include "cercle.h"
{
public : class Cercle : public Ellipse int main()
Ellipse(); { {
Ellipse (float x, float y, float a, float b);
public : Cercle c(5, 5, 15);
Ellipse(const Ellipse & e); Cercle(); c. affiche();
~Ellipse();
Cercle (float x, float y, float diametre); return 0;
protected :
~ Cercle(); }
float m_cX, m_cY; public :
float m_a, m_b; virtual void affiche();
};
public :
void deplace(float dx, float dy);
void zoom(float z);
[Link]
float surface(); #include <iostream.h>
Autorise la
virtual void affiche(); Le constructeur de la classe dérivée
redéfinition de #include “cercle.h”
}; appelle généralement un des
la fonction dans
les classes Cercle::Cercle() : public Ellipse() constructeurs de la classe de base.
dérivées {
[Link]
}
#include <iostream.h> Cercle::Cercle(float x, float y, float diametre) : public Ellipse( x, y, diametre,
diametre)
void Ellipse::affiche() {
{ }
std::cout << "Ellipse de grand axe " << m_a;
void Cercle::affiche()
std ::cout << " et de petitt axe " << m_b << "\n";
} {
std::cout << "Cercle de rayon " << m_a / 2 << "\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
class Ellipse #include “ellipse.h” [Link]
{
#include " cercle.h"
public : class Cercle : public Ellipse
Ellipse(); {
Ellipse (float x, float y, float a, float b); public : int main() La fonction deplace() n’est pas
Cercle(); { redéfinie dans la classe Cercle,
Ellipse(const Ellipse & e); appelle celle de Ellipse.
~Ellipse(); Cercle (float x, float y, float d); Ellipse e(0, 0, 8.5, 10.2);
~ Cercle(); [Link](-1, 1); La fonction affiche() est redéfinie
protected : public : [Link](); dans la classe Cercle, appelle celle
float m_cX, m_cY; virtual void affiche(); de Cercle.
float m_a, m_b; }; Cercle c(-2.5, 2.5, 7,4);
[Link](0.5, 1.5);
public : c. affiche();
void deplace(float dx, float dy); [Link] Appelle la fonction affiche()
void zoom(float z); de la classe Ellipse.
#include “cercle.h” Ellipse *p1;
float surface();
virtual void affiche();
p1 = new Ellipse;
}; Cercle::Cercle() : public Ellipse() p1 ->affiche();
{
} Ellipse *p2;
Si la fonction affiche() est
p2 = new Cercle; virtuelle et est redéfinie dans
Cercle::Cercle(float x, float y, float d) : p2->affiche(); la classe Cercle, appelle celle
[Link] public Ellipse( x, y, d, d)
de Cercle bien que le pointeur
{
#include <iostream.h>
return 0; soit de type Ellipse. C'est le
}
} mécanisme de polymorphisme
void Ellipse::affiche() d'héritage.
void Cercle::affiche()
{ {
std::cout << "Ellipse de grand axe " << m_a; std::cout << "Cercle de rayon ";
std ::cout << " et de petit axe " << m_b << "\n"; std::cout << m_a / 2 << "\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.