Différences entre les versions de « Langage C++/Objet »

22 674 octets supprimés ,  il y a 12 ans
aucun résumé de modification
| numero = 13
| précédent = [[../Structures, unions et champs de bits/]]
| suivant = [[../Classe/]]
| niveau =
}}
 
En développement on parle plus souvent d''''instance de classe''' pour désigner un objet.
 
 
== La Classe ==
 
En programmation orienté objet, tout est basé sur le concept de la classe. La classe est une entité autonome capable de maintenir une fois instanciée la cohérence des données qu'elle est chargée d'entretenir. (nous mettrons en évidence la syntaxe dans la partie implémentation)
 
 
=== Les Visibilités : Niveaux de visibilité dans une classe ===
 
Il existe trois niveaux de visibilité dans une classe :
*Privé (Niveau par défaut) : Permet de masquer complètement les données et méthodes à des classes tiers même aux classes dérivées.On parle d'encapsulation. (Peut être utilisé par les méthodes mais est surtout destiné aux variables d'une classe)
*Protégé : Permet de partager des données uniquement aux classes dérivés. (Normalement uniquement utilisé pour les méthodes)
*Public : Permet de partager les données avec toutes les classes tiers. (Normalement uniquement utilisé pour les méthodes)
 
 
=== Les Attributs : Variable de classes ===
 
La classe est une entité qui peut être composé de variables internes que l'on nome '''attributs''' membre.
 
La théorie veut que les attributs d'une classe ne soient accessibles directement qu'à cette classe. Bien qu'en C++ il est possible de définir des attributs membres de visibilité publique ou protégés, il est normalement indispensable de rendre les attributs membres privés (je n'ai jamais eu à rendre publique ou même protégé des attributs hormis pour des classes-énumérations que nous verrons plus tard).
 
En effet le paradigme objet étant de rendre la classe responsable de la gestion de ses attributs pour assurer la gestion correcte et la cohésion des données, il serais mal vu qu'une autre classe accède malencontreusement à ces attributs et vienne bouleverser l'organisation que la classe permet de maintenir entre eux. Quand une classe privatise un attribut on dit qu'elle l''''encapsule'''.
 
 
=== Les Méthodes : Méthodes de classes ===
 
Nous avons déjà vu les méthodes auparavant.
 
Les méthodes déterminent le comportement qu'une classe est capable de réaliser. La classe possède au moins trois méthodes spéciales :
* Un constructeur sans paramètres appelé aussi '''constructeur par défaut''' ou, selon le cas, un '''constructeur paramétré''' qui oblige à fournir des paramètres pour créer la classe.
* Un '''constructeur par copie''' qui prend en paramètre une référence sur la même classe (dans la majorité des cas elle est fournie par défaut par le compilateur mais il peut être fréquemment nécessaire de la définir manuellement).
* Un '''destructeur''' qui est chargé d'appliquer un traitement pour éventuellement nettoyer la mémoire. Bien qu'il soit souvent vide l'implémenter explicitement et systématiquement permet de faire fonctionner le mécanisme d''''héritage polymorphe'''.
Les méthodes donnant accès aux attributs sont appelés suivant le sens d'accès :
* Des '''accesseurs''' pour les méthodes accédant en lecture seule.
* Des '''mutateurs''' pour les méthodes accédant en écriture seule.
* Des '''propriétés''' pour les méthodes accédant en écriture et en lecture.
La théorie veux que tout attribut qui aurais besoins d'être transmis ou modifié, le soit par l'une de ces méthodes d'accès. (Je n'ai jamais eu à faire autrement.)
Les méthodes définissent aussi l''''interface''' d'une classe.
 
 
=== Généralisation de classes ===
 
En programmation orienté objet, on peut structurer une hiérarchie de classes en arborescence. Les classes du sommet de l'arbre sont les classes les plus abstraites et générales. Les classes les plus profondes sont les plus concrètes et les plus spécialisés.
 
Pour illustrer ces propos supposons les figures suivantes :
 
*Carre
*Rectangle
*Parallélogramme
*Losange
*Quadrilatere
*Cercle
*Ovale
 
Imaginons maintenant que nous devions réaliser des classes basé sur ces figures. Supposons que chaque classe doit être capable de de dessiner. Supposons aussi que nous devions gérer chacune de ces classes avec le même (et unique) pointeur et appeler la même méthode pour le déssin de toutes les classes.
 
Dans les figure imposés on peut voir que "Carre" est un cas particulier de (ou "une sorte de") "Rectangle" qui est lui même un cas particulier du "Parallélogramme" qui est lui même un "Quadrilatere". Quant à "Losange" c'est un "Quadrilatere"
 
On peut voir aussi que "Cercle" est un cas particulier de "Ovale".
 
Cependant rien ne lie Ovale et Quadrilatere.
 
Nous allons donc devoir généraliser Ovale et Quadrilatère en "Figure"
 
Ainsi Quadrilatère et Ovale sont des Figures ainsi un pointeur sur figure est capable de manipuler n'importe quelle classe sous-jacente Puisqu'après tout un carré est une figure tout comme un cercle.
 
Traitons maintenant le problème de la méthode unique. En fait depuis que l'on a créé la classe Figure ce n'est plus un problème.
 
En effet il suffit de définir la méthode "Dessine()" dans la classe Figure pour que toutes les autres classes en soient dotés.
 
 
=== Abstraction de classes ===
 
Lors de généralisations successives il est courent de se rendre compte qu'une classe est trop "abstraite" pour avoir suffisamment de données exploitable pour pouvoir en générer une instance. C'est le cas pour notre classe "Figure". En effet elle est trop générique et rien d'intéressant ne peut en sortir cependant elle fournis une interface que toutes les autres classes devront reproduire fidèlement. La méthode "Déssiner" ne représente pas grand choses pour une Figure. Nous ne pouvons pas décrire de comportement pour cette méthode dans cette classe. Nous ne pouvons donc pas instancier la classe car cela n'aurais pas de sens. Nous allons donc devoir rendre cette classe abstraite. Cela signifie que la classe n'est pas instanciable en l'état mais qu'une classe hérité non abstraite peut être interprété comme cette classe (via l'utilisations de pointeurs ou de références.)
 
 
=== Héritage de classes ===
 
L'héritage est la faculté qu'à une classe de pouvoir transmettre ses attributs et méthodes à ses classes dérivés et sous certaines conditions permettre à ses classes dérivées de redéfinir ses méthodes pour pouvoir les améliorer et les spécialiser.
 
Tout d'abord il faut savoir qu'en C++ une classe dérivé reçoit toujours une copie de l'intégralité des attributs et des méthodes de sa classe ancêtre.
En C++, bien que l'on puisse choisir la façon dont sont copiés ces membres de la classe ancêtre la théorie objet veux que l'on hérite toujours des classes ancêtre de manière publique affin que tous les membres public de la classe ancêtre soient aussi disponibles de manière publique dans la classe dérivé.
L'héritage permet de ne pas réécrire eternellement les mêmes codes, de réutiliser les objets, de pouvoir en spécialisermettre en œuvre le polymorphisme.
 
 
=== Polymorphisme de classes ===
 
Le polymorphisme en informatique se traduit par la capacité qu'a un pointeur de classe ancêtre présentant une interface donnée, à appeler la méthode de l'instance de la classe dérivé correspondant à la méthode de la classe ancêtre. En C++ la mise en œuvre du polymorphisme se fait à l'aide du mot clé "virtual".
Dans la pratique et pour reprendre l'exemple vu précédemment :
 
Si l'on créé un pointeur sur Figure que l'on lui assigne l'adresse de l'instance d'un carré et que l'on demande au pointeur figure de se dessiner alors le pointeur vas appeler la méthode virtuelle et par le biais de l'héritage virtuel appeler la méthode implémenté dans la classe Carré.
 
=== Implémentation ===
 
Les classes sont donc la représentation physique du concept d'objet. Voici en C++ comment implémenter ces classe conformément à la théorie de l'objet.
 
{{Définition
| contenu =
'''Syntaxe:''' <source lang="cpp">
class <NomNouvelleClasse> : [public: <ClasseAncêtre>[, <AutreClasseAncêtre>][, ...]]
{
[ [<Visibilitée>:] <TypeAttribut1> <NomAttribut1>;]
[<Visibilitée>:] <TypeAttributN> <NomAttributN>;] ]
[<Visibilitée>:] <TypeMethode1> <NomMethode1>(<TypeParamettre> <NomParamettre>[ = <ValeurParDefaut>][, ...]);
[ [<Visibilitée>:] <TypeMethodeN> <NomMethodeN>(<TypeParamettre> <NomParamettre>[ = <ValeurParDefaut>][, ...]); ]
};
</source>
}}
 
Où <NomNouvelleClasse> est le nom de la classe, <ClasseAncêtre> est une classe ancêtre tout comme <AutreClasseAncêtre> et sont facultatives si la classe n'a pas à avoir d'ancêtre, <TypeAttribut1> et <TypeAttributN> sont les types des attributs, <NomAttribut1> et <NomAttributN> sont les noms des attributs, les attributs sont facultatifs, <TypeParamettre> et <NomParamettre> sont les paramètres des methodes de la classe, <TypeMethode1> et <TypeMethodeN> sont les types de retours des méthodes de la classe, <NomMethode1> et <NomMethodeN> sont les noms des méthodes de la classe, , <Visibilitée> peut prendre trois valeurs: public, protected ou private (par défaut). Un membre déclaré public peut être manipulé par n'importe quelle classe, un membre protected ne peut être manipulé que par la classe et ses dérivés tandis qu'un membre private ne peut être manipulé que par les méthodes de la classe.
 
Les méthodes sont soit définies directement dans la déclaration de classe (au quel cas ce sont des Macro), soit définies en dehors de la déclaration dans un fichier source séparé de la manière suivante:
 
{{Définition
| contenu =
'''Syntaxe:''' <source lang="cpp">
<TypeRetour> [<NomClasse>::]<NomMethode>([<TypeParametre> <NomParametre>[,<...>]])
{
[<Instructions>;]
}
</source>
}}
 
Voici un exemple de la classe la plus simple à réaliser. Il faut dire aussi qu'elle ne fait strictement rien.
 
{{exemple | contenu =
dans le ".h"
<source lang="cpp">
#ifndef FAINEANTE_H
#define FAINEANTE_H
 
class Faineante;
{
public:
// Constructeur par défaut
Faineante();
 
// Constructeur par copie
Faineante(Faineante& pCopie);
 
// Destructeur
virtual ~Faineante(); // "virtual" active le polymorphisme
};
 
#endif //FAINEANTE_H
 
</source>
Dans le ".cpp"
<source lang="cpp">
 
Faineante::Faineante()
{
}
 
Faineante::Faineante(Faineante& pCopie)
{
}
 
Faineante::~Faineante()
{
}
 
</source>
}}
 
=== Exemples: ===
 
Maintenant reprenons nos exemples de figures de tout à l'heure.
 
{{exemple | contenu =
dans "Carre.h"
<source lang="cpp">
#ifndef Carre_H
#define Carre_H
 
#include "Rectangle.h"
// Un carré est un rectangle dont toutes les arrêtes sont égales.
class Carre : public Rectangle
{
public:
// Constructeur paramétré
Carre(double pArrete);
// Destructeur
virtual ~Carre();
virtual void mDessine();
};
 
</source>
dans "Carre.cpp"
<source lang="cpp">
 
#include "Carre.h"
 
// Constructeur paramétré
// Appelle le constructeur de la classe ancêtre en le paramétrant correctement
Carre::Carre(double pArrete):Rectangle(pArrete, pArrete)
{
}
 
// Destructeur
Carre::~Carre()
{
}
 
void Carre::mDessine()
{
//Dessine ici le Carre (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Carre :\n" << endl;
Rectangle::mDessine();
}
 
</source>
dans "Rectangle.h"
<source lang="cpp">
#ifndef Rectangle_H
#define Rectangle_H
 
#include "Parallelogramme.h"
 
//Un Rectangle est un Prarallelogramme avec tout ses angles droits.
class Rectangle : public Parallelogramme
{
public:
Rectangle(double pArreteAB, double pArreteBC);
virtual ~Rectangle();
virtual void mDessine();
};
 
</source>
dans "Rectangle.cpp"
<source lang="cpp">
#include "Rectangle.h"
 
// Appelle le constructeur de la classe ancêtre en le paramétrant correctement
Rectangle::Rectangle(double pArreteAB, double pArreteBC):Parallelogramme(pArreteAB, pArreteBC, 90.0)
{
}
 
// Destructeur
Rectangle::~Rectangle()
{
}
 
void Rectangle::mDessine()
{
//Dessine ici le Rectangle (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Rectangle :\n" << endl;
Parallelogramme::mDessine();
}
 
</source>
dans "Parallelogramme.h"
<source lang="cpp">
#ifndef Parallelogramme_H
#define Parallelogramme_H
 
#include "Quadrilatere.h"
 
// Un Parallélogramme est un Quadrilatère dont les cotés parallèles sont égaux.
class Parallelogramme: public Quadrilatere
{
private:
double& mCorrectionAngle(double& pAngle);
 
protected:
virtual void mAngleA(double& pAngle);
virtual void mAngleB(double& pAngle);
virtual void mAngleC(double& pAngle);
virtual void mAngleD(double& pAngle);
 
public:
// Constructeur parametré
Parallelogramme(double pArreteAB, double pArreteBC, double pAngleA);
// Destructeur
virtual ~Parallelogramme();
// Méthode de dessin des parallélogrammes.
virtual void mDessine();
};
 
</source>
dans "Parallelogramme.cpp"
<source lang="cpp">
#include <iostream>
#include "Parallelogramme.h"
 
using namespace std;
 
double& mCorrectionAngle(double& pAngle)
{
// Corriger les dépassements
double vAngleMaximal = 180.0;
pAngle %= vAngleMaximal;
// Si l'angle est
if(pAngle < 0)
{
// Corriger le signe.
pAngle *= -1;
}
return pAngle;
}
 
virtual void mAngleA(double& pAngle)
{
Quadrilatere::mAngleA(pAngle);
}
 
virtual void mAngleB(double& pAngle)
{
Quadrilatere::mAngleB(pAngle);
}
 
virtual void mAngleC(double& pAngle)
{
Quadrilatere::mAngleC(pAngle);
}
 
virtual void mAngleD(double& pAngle)
{
Quadrilatere::mAngleD(pAngle);
}
 
// Constructeur parametré
Parallelogramme::Parallelogramme(double pArreteAB, double pArreteBC, double pAngleA):Quadrilatère(pArreteAB, pArreteBC, pArreteAB, pArreteBC, 0.0, 0.0, 0.0, 0.0)
{
// Dans un Parallélogramme les arrêtes :
// AB = CD et BC = AD.
// Les angles :
// A = C, B = D et A + B + C + D = 360°.
// Ici nous passons par les méthodes de Parallélogramme pour garantir que les angles seront inférieur ou égal à 180.
 
double vAngleMax = 180.0;
this->mAngleA(pAngleA);
this->mAngleB(vAngleMax - this->mCorrectionAngle(pAngleA));
this->mAngleC(pAngleA);
this->mAngleD(vAngleMax - this->mCorrectionAngle(pAngleA));
}
 
// Destructeur
virtual Parallelogramme::~Parallelogramme()
{
}
// Méthode de dessin des parallélogrammes.
virtual void Parallelogramme::mDessine()
{
//Dessine ici le Parallelogramme (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Parallelogramme :\n" << endl;
Quadrilatere::mDessine();
}
</source>
dans "Losange.h"
<source lang="cpp">
#ifndef Losange_H
#define Losange_H
 
#include "Quadrilatere.h"
 
class Losange : public Quadrilatere
{
private:
double& mCorrectionAngle(double& pAngle);
 
protected:
virtual void mAngleA(double& pAngle);
virtual void mAngleB(double& pAngle);
virtual void mAngleC(double& pAngle);
virtual void mAngleD(double& pAngle);
 
public:
Losange(double pArreteAB, double pArreteBC, double pAngleA);
virtual ~Losange();
virtual void mDessine();
};
 
</source>
dans "Losange.cpp"
<source lang="cpp">
#include "Losange.h"
 
// L'angle maximal d'un angle de losange est : 180°
double& Losange::mCorrectionAngle(double& pAngle)
{
// Corriger les dépassements
double vAngleMaximal = 180.0;
pAngle %= vAngleMaximal;
// Si l'angle est
if(pAngle < 0)
{
// Corriger le signe.
pAngle *= -1;
}
return pAngle;
}
 
void Losange::mAngleA(double& pAngle)
{
// Mettre à jour l'angle.
Quadrilatere::mAngleA(mCorrectionAngle(pAngle));
}
 
void Losange::mAngleB(double& pAngle)
{
// Mettre à jour l'angle.
Quadrilatere::mAngleB(mCorrectionAngle(pAngle));
}
 
void Losange::mAngleC(double& pAngle)
{
// Mettre à jour l'angle.
Quadrilatere::mAngleC(mCorrectionAngle(pAngle));
}
 
void Losange::mAngleD(double& pAngle)
{
// Mettre à jour l'angle.
Quadrilatere::mAngleD(mCorrectionAngle(pAngle));
}
 
Losange::Losange(double pArreteAB, double pArreteBC, double pAngleA):Quadrilatere(pArreteAB, pArreteBC, pArreteBC, pArreteAB, 0.0, 0.0, 0.0, 0.0)
{
// Dans un losange les arrêtes :
// AB = DA et BC = CD.
// Les angles :
// A = C, B = D et A + B + C + D = 360°.
// Ici nous passons par les méthodes de Losange pour garantir que les angles seront inférieur ou égal à 180.
 
double vAngleMax = 180.0;
this->mAngleA(pAngleA);
this->mAngleB(vAngleMax - this->mCorrectionAngle(pAngleA));
this->mAngleC(pAngleA);
this->mAngleD(vAngleMax - this->mCorrectionAngle(pAngleA));
}
 
~Losange::Losange()
{
}
 
void Losange::mDessine()
{
//Dessine ici le Losange (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Losange :\n" << endl;
Quadrilatere::mDessine();
}
 
</source>
dans "Quadrilatere.h"
<source lang="cpp">
#ifndef Quadrilatere_H
#define Quadrilatere_H
 
#include "Figure.h"
 
class Quadrilatere : public Figure
{
private:
double aArreteAB;
double aArreteBC;
double aArreteCD;
double aArreteDA;
double aAngleA;
double aAngleB;
double aAngleC;
double aAngleD;
double& mCorrectionAngle(double& pAngle);
double& mCorrectionArrete(double& pArrete);
protected:
void mArreteAB(double& pArrete);
void mArreteBC(double& pArrete);
void mArreteCD(double& pArrete);
void mArreteDA(double& pArrete);
 
virtual void mAngleA(double& pAngle);
virtual void mAngleB(double& pAngle);
virtual void mAngleC(double& pAngle);
virtual void mAngleD(double& pAngle);
 
public:
Quadrilatere(double& pArreteAB, double& pArreteBC, double& pArreteCD, double& pArreteDA, double& pAngleA, double& pAngleB, double& pAngleC, double& pAngleD);
virtual ~Quadrilatere();
virtual void mDessine();
double mArreteAB();
double mArreteBC();
double mArreteCD();
double mArreteDA();
double mAngleA();
double mAngleB();
double mAngleC();
double mAngleD();
};
 
</source>
dans "Quadrilatere.cpp"
<source lang="cpp">
#include <math.h>
#include "Quadrilatere.h"
 
// L'angle maximal d'un angle de quadrilatère est : 360°
double& Quadrilatere::mCorrectionAngle(double& pAngle)
{
// Corriger les dépassements
double vAngleMaximal = 360.0;
pAngle %= vAngleMaximal;
// Si l'angle est
if(pAngle < 0)
{
// Corriger le signe.
pAngle *= -1;
}
return pAngle;
}
 
// Propriété AngleA de Quadrilatere. Remarquez comment la propriété contraint l'intégrité des données de la classe via la méthode mCorrectionAngle.
void Quadrilatere::mAngleA(double& pAngle): Figure()
{
// Mettre à jour l'angle.
this->aAngleA = mCorrectionAngle(pAngle);
}
 
void Quadrilatere::mAngleB(double& pAngle)
{
// Mettre à jour l'angle.
this->aAngleB = mCorrectionAngle(pAngle);
}
 
void Quadrilatere::mAngleC(double& pAngle)
{
// Mettre à jour l'angle.
this->aAngleC = mCorrectionAngle(pAngle);
}
 
void Quadrilatere::mAngleD(double& pAngle)
{
// Mettre à jour l'angle.
this->aAngleD = mCorrectionAngle(pAngle);
}
 
// Une arrête n'est pas négative
double& Quadrilatere::mCorrectionArrete(double& pArrete)
{
if(pArrete < 0)
{
pArrete *= -1;
}
return pArrete;
}
 
void Quadrilatere::mArreteAB(double& pArrete)
{
this->aArreteAB = mCorrigeArrette(pArrete);
}
 
void Quadrilatere::mArreteBC(double& pArrete)
{
this->aArreteBC = mCorrigeArrette(pArrete);
}
 
void Quadrilatere::mArreteCD(double& pArrete)
{
this->aArreteCD = mCorrigeArrette(pArrete);
}
 
void Quadrilatere::mArreteDA(double& pArrete)
{
this->aArreteCD = mCorrigeArrette(pArrete);
}
 
Quadrilatere::Quadrilatere(double& pArreteAB, double& pArreteBC, double& pArreteCD, double& pArreteDA, double& pAngleA, double& pAngleB, double& pAngleC, double& pAngleD)
{
// Assure l'intégrité de la classe;
this->mArreteAB(pArreteAB);
this->mArreteBC(pArreteBC);
this->mArreteCD(pArreteCD);
this->mArreteDA(pArreteAD);
this->mAngleA(pAngleA);
this->mAngleB(pAngleB);
this->mAngleC(pAngleC);
this->mAngleD(pAngleD);
}
 
Quadrilatere::~Quadrilatere()
{
// rien à détruire.
}
 
void Quadrilatere::mDessine()
{
//Dessine ici le parallélogramme (vrais code de dessin non pertinent car trop volumineux pour le gain obtenu sur l'intérêt de la leçon).
cout << "Dessine Quadrilatere :\n" <<
"\tAB = " << this->mArreteAB() << ",\n" <<
"\tBC = " << this->mArreteBC() << ",\n" <<
"\tCD = " << this->mArreteCD() << ",\n" <<
"\tDA = " << this->mArreteDA() << ",\n" <<
"\tAngle A = " << this->mAngleA() << ",\n" <<
"\tAngle B = " << this->mAngleB() << ",\n" <<
"\tAngle C = " << this->mAngleC() << ",\n" <<
"\tAngle D = " << this->mAngleD() << endl;
}
 
double Quadrilatere::mArreteAB()
{
return this->aArreteAB;
}
 
double Quadrilatere::mArreteBC()
{
return this->aArreteBC;
}
 
double Quadrilatere::mArreteCD()
{
return this->aArreteCD;
}
 
double Quadrilatere::mArreteDA()
{
return this->aArreteDAB;
}
 
double Quadrilatere::mAngleA()
{
return this->aAngleA;
}
 
double Quadrilatere::mAngleB()
{
return this->aAngleB;
}
 
double Quadrilatere::mAngleC()
{
return this->aAngleC;
}
 
double Quadrilatere::mAngleD()
{
return this->aAngleD;
}
 
</source>
dans "Figure.h"
<source lang="cpp">
#ifndef _H
#define _H
 
class Figure
{
public:
Figure();
virtual ~Figure();
virtual void mDessine() = 0; // "virtual void mDessine() = 0;" est une méthode dite "virtuelle pure" (à cause du "= 0").
// Elle n'a pas de corps et rend donc de fait la classe "Figure" abstraite. Cela signifie
// que l'on ne peux pas instancier cette classe dirrectement. Pour pouvoir instancier cette
// classe il faut la dériver et implémenter la méthode (lui donner un corps).
};
 
</source>
dans "Figure.cpp"
<source lang="cpp">
#include "Figure.h"
 
Figure::Figure()
{
}
 
Figure::~Figure()
{
}
 
// Le corps de mDessine n'apparait pas car c'est une méthode virtuelle pure
 
</source>
}}
 
{{Bas de page
| leçon = [[Langage C++]]
| précédent = [[../Structures, unions et champs de bits/]]
| suivant = [[../Classe/]]
}}
 
Utilisateur anonyme