Présentation générale
Fichier header: NomClasse.h
class NomClasse
{
public:
// Méthodes
NomClasse(); // Constructeur
type methode1(arguments);
type methode2(arguments);
...
protected:
// Attributs
// Méthodes
private:
// Attributs
type attribut1;
type attribut2;
...
// Méthodes
};
- public: on peut accéder à cette zone depuis l’extérieur de l’objet. On n’y place généralement que des méthodes
- protected: on ne peut pas accéder à cette zone depuis l’extérieur. Seules les classes filles ont accès à cette zone.
- private: seule la classe elle-même peut accéder à cette zone.
Implémentation: NomClasse.cpp
#include "NomClasse.h"
type NomClasse::methode1(arguments)
{
code
...
}
type NomClasse::methode2(arguments)
{
...
}
- Les attributs doivent toujours être privés ou protégés. On y accède uniquement par des méthodes (c’est l’encapsulation)
- Un nom de classe commence habituellement par une majuscule
- On peut remplacer le mot-clé
classparstruct. Dans ce cas, les méthodes et attributs sont publics par défaut. - Les constructeur et destructeur ne doivent renvoyer aucune valeur (même pas
void)
Constructeur
NomClasse::NomClass()
{
...
}
- Le constructeur est une méthode appelée lorsque l’objet est créé
- On se sert du constructeur pour initialiser les attributs de l’objet (On ne peut pas le faire dans la déclaration de la classe)
- Allocation de la mémoire pour les pointeurs :
ptr = new type/Classe;
- Allocation de la mémoire pour les pointeurs :
- On peut également effectuer d’autres actions nécessaires lors de la création de l’objet.
- Dans le cas d’une classe dérivée (héritage), le constructeur de la classe mère est appelé en premier, puis le constructeur de la classe fille
- en cas d’héritage multiple, on construit la classe ancêtre en 1er et la classe fille en dernier
Constructeur avec liste d’initialisation
NomClasse::NomClasse() : Attribut1(Valeur), Attribut2(Valeur), ...
{
...
}
- Initialise des attributs de l’objet.
- L’utilisation de la liste d’initialisation est obligatoire pour initialiser les éléments suivants:
- Initialisation d’un membre constant (Comme il est constant, une fois créé on ne peut plus modifier sa valeur)
- Initialisation d’un membre qui est une référence
- Initialisation d’un objet membre: Le constructeur des objets membre est appelé avant d’exécuter le corps du constructeur
- Appel d’un constructeur spécifique de la classe mère (dans le cas d’une classe dérivée)
Surcharge du constructeur
NomClasse::NomClasse(type param1, type param2, ...) [ : liste d'initialisation]
{
...
}
- Si le constructeur n’a pas d’argument, il s’agit du constructeur sans argument
- Le compilateur fournit un constructeur sans argument par défaut
- Il est conseillé de redéfinir ce constructeur sans argument
- Si au moins un constructeur surchargé est défini, le compilateur ne fournit plus le constructeur sans argument par défaut
- Si le constructeur possède des arguments, c’est une surcharge du constructeur.
- Si l’objet est déclaré sans argument, le constructeur par défaut est utilisé
NomClasse nomObjet; - Si l’objet est déclaré avec des arguments, le constructeur surchargé est utilisé
NomClasse nomObjet(arg1, arg2);
Constructeur de copie
Le constructeur de copie est le constructeur utilisé lorsqu’on crée un objet à partir d’un autre.
Il est également utilisé
- lorsqu’on passe un objet en tant que paramètre d’une fonction sans utiliser de référence. L’objet est dupliqué (grâce au constructeur de copie) pour être utilisé à l’intérieur de la fonction.
- lorsqu’une méthode retourne un objet, une copie de l’objet est créée pour être retournée à la fonction appelante
NomClasse objet2(objet1);
NomClasse objet2 = objet1;
- Par défaut, le compilateur crée automatiquement un constructeur de copie. Ce constructeur se contente de copier tous les attributs
- Si l’objet copié comprend un pointeur: avec le constructeur par défaut, les pointeurs des 2 objets pointent sur les mêmes éléments.
- Dans le 2ème cas (signe
=), il s’agit d’un constructeur de copie et non d’une surcharge de l’opérateur d’affectation=. Le constructeur de copie est appelé à l’initialisation de la variable lors de sa création. L’opérateur d’affectation est appelé lorsque qu’on ré-affecte l’objet en cours d’exécution du programme. - On peut définir un constructeur de copie personnalisé
NomClasse::NomClasse(NomClasse const& source)
Constructeur par délégation
Pour éviter de dupliquer du code, un constructeur peut appeler un autre contructeur.
NomClasse::NomClasse(arguments) : AutreConstructeur() {
...
}
Destructeur
NomClasse::~NomClass()
{
...
}
- Le destructeur est une méthode appelée lorsque l’objet est détruit
- On se sert du destructeur pour libérer la mémoire qui est utilisée pas les attributs de l’objet.
- Ne pas oublier de libérer la mémoire utilisée par les pointeurs :
delete(ptr);
- Ne pas oublier de libérer la mémoire utilisée par les pointeurs :
- On peut également effectuer d’autres actions nécessaires lors de la destruction de l’objet.
- Le destructeur ne peut pas être surchargé
- Dans le cas d’une classe dérivée, le destructeur de la classe fille est appelé en 1er puis le destructeur de la classe mère
Elements constants
Méthodes constantes
NomClasse::methode(arguments) const
{
...
}
- Une méthode constante est une méthode qui ne modifie pas les attributs de l’objet
Argument constant
fonction(const type arg1, type arg2) {
...
}
- L’argument
arg1est déclaré constant, il NE pourra PAS être modifié par la fonction
Valeur de retour constante
const type fonction(arguments) {
...
}
- La valeur (ou l’objet) de retour ne pourra pas être modifié
- Utilisé en général conjointement avec le retour par référence
Eléments statiques
IMPORTANT: Les éléments statiques sont des spécificités du langage C++.
Méthodes statiques
- Méthode définie dans une classe qui peut être appelée sans que la classe ne soit instanciée (i.e. La méthode est exécutée sans être liée à un objet)
Déclaration:
class ''nomClasse''
{
public:
static type nomMethode;
}
Utilisation:
nomClasse::nomMethode();
Attributs statiques
- attribut appartenant à la classe et non à l’objet
- Déclaré avec les autres attributs de la classe avec le mot clé
static - La variable est rattachée à la classe mais est accessible depuis tous les objets créés
- Doit être initialisé en dehors de toute fonction (à la manière d’une variable globale). Généralement, est initialisée dans le fichier cpp de la classe correspondante.
type nomClasse::nomAttribut = valeur;
Amitié
IMPORTANT: L’amitié est une spécificité du langage C++.
Une méthode amie d’une classe a le droit d’accéder à tous les membres de la classe, même les éléments private ou protected.
- Dans la classe, on déclare toutes les fonctions amies. Pour cela, on réécrit le propotype de la fonction amie, précédé du mot clé
friend - on peut déclarer comme amis une fonction, une méthode, une classe entière ou des templates
- La déclaration des amis peut se faire dans n’importe quelle section: public, protected ou private
class nomClasse
{
private:
'''friend''' ''type'' autreClasse::methode(nomClasse obj);
'''friend''' class autreClasse;
}
Règles générales de l’amitié
- une fonction amie ne doit pas modifier ni supprimer l’instance de la classe
- l’amitié ne doit être utilisée qu’en dernier recours
Observations
En C++, l’amitié ne s’hérite pas. Si une classe B est déclarée en tant qu’amie de la classe A, alors la classe B peut accéder aux membres privés de la classe A. En revanche, une classe C déclarée comme classe fille de la classe B, n’aura pas accès aux membres privés de la classe A. La classe C n’est pas amie de la classe A, sauf à la déclarer explicitement.
Accesseurs : Getter & Setter
- Getter : Méthode renvoyant la valeur d’un attribut
- Setter : Méthode modifiant la valeur d’un attribut
Surcharge d’opérateur
Surcharge de l’opérateur +
- Surcharger l’opérateur + revient à créer la fonction
operator+ - 2 options (mutuellement exclusives):
- On surcharge la fonction
operator+: Ce n’est pas une méthode de la classe mais une fonction (ne pas oublier son prototype) - On définit la méthode
operator+de la classe
- On surcharge la fonction
- On écrit cette fonction de telle façon à faire référence à la surcharge de l’opérateur
+=(qui lui est une méthode de la la classe) - Valable pour les opérateurs, +, -, *, / et %
NomClasse operator+ (NomClasse const& a, NomClasse const& b) // Surcharge de la fonction
{
Duree copie(a);
copie += b;
return copie;
}
ou
NomClasse NomClasse::operator+ (NomClasse const& b) // Définition de la méthode
{
Duree copie(*this);
copie += b;
return copie;
}
Surcharge de l’opérateur “moins unaire”
- L’opérateur moins unaire renvoie l’opposé d’un nombre (-a)
- Son prototype est différent car il n’admet qu’un seul argument
NomClasse operator-(NomClasse const& a);
Surcharge de l’opérateur +=
- La surcharge de l’opérateur
+=est une méthode de la classe - Cette méthode sert à définir comment réaliser l’opération désirée avec des objets de la classe
- Valable pour les opérateurs, +, -, *, / et %
NomClasse& NomClasse::operator+=(NomClasse const& a)
{
...
return *this;
}
Mélange de types
- On peut surcharger encore plus les opérateurs pour effectuer une opération sur des types différents
- Il suffit de surcharger l’opérateur avec les arguments adéquats
Surcharge de la fonction de l’opérateur:
NomClasse operator+(NomClasse const& a, ''type'' b)
Surcharge de la méthode
NomClasse& NomClasse::operator+=(''type'' a)
Surcharge de l’opérateur d’affectation:=
- La surcharge de l’opérateur
=est une méthode de la classe - Il faut vérifier qu’on ne tente pas de copier l’objet lui-même
(nomObjet = nomObjet;) - la méthode doit retourner
*this - Si l’objet contient des pointeurs, il faut penser à libérer la mémoire avant de la ré-allouer avec une copie de l’objet source
- Lors de la copie des pointeurs, on alloue la mémoire en copiant l’objet pointé. Attention à ne pas copier le pointeur mais bien l’objet pointé.
NomClasse& NomClasse::operator=(NomClasse const& source)
{
if (this != &source) // on vérifie qu'on ne tente pas de copier un objet dans lui-même)
{
m_attribut = source.m_attribut;
delete m_ptr; // On libère la mémoire avant de changer la valeur du pointeur
m_ptr = new NomClasse2(*(source.m_ptr)); // on alloue la mémoire pour le pointeur et on l'initialise en copiant l'objet pointé par le pointeur de l'objet source
}
}
Surcharge des opérateurs de flux: << et >>
- Lorsqu’on inclue la librairie
iostream, un objetcoutde typeostreamest automatiquement créé. - Ecrire
cout << texte;revient à manipuler un objet de typeostream. - Pour surcharger les opérateurs de flux avec une classe, il suffit de définir la fonction suivante:
ostream& operator<<(ostream &flux, NomClasse const& nomObjet)
{
...
return flux;
}
- Il se peut que l’on ait besoin d’accéder aux attributs de l’objet à l’intérieur de la fonction surchargée (ce qui n’est pas possible car les attributs sont privés)
- On utiliser les getter
- On utilise le concept d’amitié
- On ajoute une méthode à notre classe. Cette méthode renvoie les attributs dans flux.
void NomClasse::methodeAffiche(std::ostream& flux) const // Le const est nécessaire pour éviter des erreurs de compilation
{
flux << ''attribut1'' << ''texte'' << ''attribut2'' << endl;
}
Surcharge des opérateurs de comparaison: ==, !=, <, >, <= et >=
option 1 : méthode membre de la classe
- Créer une méthode
operator==dans la classe
bool NomClasse::operator==(NomClasse const& obj)
La méthode faisant partie intégrante de la classe, elle a accès à tous les membres (yc ceux privés) pour effectuer la comparaison.
option 2 : fonction indépendante
- Surcharger l’opérateur
==revient à créer la fonctionoperator== - La surcharge de l’opérateur
==n’est pas une méthode de la classe mais une fonction (ne pas oublier son prototype) - Valable pour les opérateurs !=, <, <=, > et >=
bool operator==(NomClasse const& a, NomClasse const& b)
- Pour comparer les 2 objets, on peut avoir besoin de comparer les attributs. Or les attributs sont privés. Il faut donc écrire une méthode qui réalise cette comparaison
return a.estEgalA(b); // La méthode appelée sur ''a'' compare l'objet lui-même avec ''b''
bool NomClasse::estEgal(NomClasse const& obj)
{
return (''attribut'' == obj.''attribut'');
}
Important: Au sein de l’objet, on a accès à ses attributs mais on a aussi accès aux attributs d’un autre objet de la même classe.
Astuce: Seules 2 méthodes sont importantes: == et <. Tous les autres opérateurs en découlent.
- a != b : !(a.estEgalA(b))
- a > b : b.estPlusPetitQue(a)
- a <= b : !(b.estPlusPetitQue(a))
- a >= b : !(a.estPlusPetitQue(b))
Héritage
- Une classe fille hérite d’une classe mère
- La classe fille hérite de toutes les méthodes et tous les attributs de la classe mère
- Une classe fille peut hériter d’une classe mère quand elle correspond à un cas particulier de la classe mère (par exemple voiture peut hériter de la classe véhicule car une voiture est un véhicule)
- Penser à inclure le header de la classe mère dans le header de la classe fille
#include "NomClasseMere.h"
classe NomClasseFille : public NomClasseMere
{
public:
methodes
private:
methodes
attributs
};
Le type d’héritage peut être:
- public: les attributs private de la classe mère ne sont pas accessible, Les attributs public et protected de la classe mère restent tel quel
- protected: les attributs private de la classe mère ne sont pas accessible, Les attributs public et protected de la classe mère deviennent protected
- private: les attributs private de la classe mère ne sont pas accessible, Les attributs public et protected de la classe mère deviennent private
Héritage multiple
- Une classe peut être dérivée de plusieurs classes mères
- Elle hérite des membres de chacune des classes mères
- Attention aux doublons, surtout dans le cas d’un héritage en losange
- Les constructeurs sont appelés dans l’ordre de la liste de déclaration. Les destructeurs dans l’ordre inverse.
class ClasseDerive : public ClasseMere1, public ClasseMere2 {
...
}
- Pour spécifier la classe mère dans laquelle se trouve l’attribut mentionné, on utilise la syntaxe
ClasseMere1::membre(avec membre un attribut ou une méthode) - En ajoutant le mot clé
virtualdevant le type d’héritage, on évite les problèmes de doublon
Dérivation de type
- Dans toutes les méthodes où un objet de la classe mère est attendu, on peut le substituer par un objet de la classe fille.
- Attention: Le contraire n’est pas possible.
- On peut écrire:
ptrClasseMere = ptrClasseFille;- Dans ce cas
ptrClasseMerepointe sur un objet de la classe fille. - La classe fille possède plus d’attributs et de méthodes que la classe mère.
- En utilisant
ptrClasseMere, on ne peut accéder qu’aux attributs et méthodes définies dans la classe mère. Même si l’objet pointé possède des attributs et des méthodes supplémentaires, on n’y accède pas par le pointeur de type classe mère.
- Dans ce cas
Constructeurs de classe héritée
- Lors de la déclaration d’un objet, le constructeur de la classe mère est appelé en premier, puis le constructeur de la classe fille
- On peut appeler manuellement le constructeur de la classe mère. Ceci est utile pour lui passer des paramètres.
NomClasseFille::NomClasseFille(arguments) : NomClasseMere(arguments), attribut(valInit)
Note: les méthodes de la classe fille n’ont pas le droit d’accéder aux attributs private de la classe mère mais uniquement à ceux protected.
Masquage
- Toutes les méthodes de la classe mère sont héritées dans la classe fille
- Si on redéfinit une méthode dans la classe fille (même nom, arguments identiques ou différents), celle-ci masque la méthode de la classe mère. La méthode de la classe fille sera appelée au lieu de la méthode la classe mère.
- On peut appeler la méthode de la classe mère depuis la classe fille (on parle de démasquage). Pour cela, on précise la portée à utiliser.
typeRetour NomClasseFille::nomMethode1(arguments)
{
NomClasseMere::nomMethode2(arguments); // On appelle explicitement la méthode de la classe mère, même si elle est masquée
code
}
Fonctions virtuelles & Polymorphisme
Lorsqu’une fonction manipule un objet, pour être générique cette fonction manipule un objet de la classe mère même si l’objet lui-même est d’une classe fille. Par conséquent toutes les méthodes appelées de l’objet sont celles implémentées dans la classe mère. On peut souhaiter que ce soit les méthodes redéfinies dans la classe fille qui soient exécutées au lieu de méthodes de la classe mère. C’est le polymorphisme
- Une fonction peut appeler une méthode d’un objet passé en paramètre. Pour être générique, la fonction admet en paramètre un objet de type classe mère. Du coup, la méthode appelée est toujours celle définie dans la classe mère (même si l’objet est de type classe fille). Or on peut vouloir que la méthode appelée soit celle de la classe fille. Pour cela, on utilise les fonctions virtuelles.
- Une fonction virtuelle ne fonctionne qu’avec des références ou des pointeurs.
- Une fonction virtuelle se définit dans le prototype uniquement.
- Une fonction virtuelle pour la classe mère est également virtuelle pour la classe fille par héritage. Il n’est donc pas nécessaire de la re-déclarer virtuelle dans le prototype de la classe fille. On va néanmoins le faire afin de garder à l’esprit qu’il s’agit d’une méthode virtuelle.
public:
virtual typeRetour nomMethode(arguments);
- Un constructeur ne peut pas être virtuel
- Un destructeur doit être virtuel si on utilise le polymorphisme.
Fonctions virtuelles pures
- Une fonction virtuelle pure est une méthode virtuelle héritée de la classe mère mais implémentée uniquement dans les classes filles.
- Une classe qui contient au moins une méthode virtuelle pure est une classe abstraite.
- Une classe abstraite ne peut pas être instanciée.
- Les fonctions virtuelles pures doivent impérativement être redéfinies dans toutes les classes filles.
- Sinon, elle sera également virtuelle pure par héritage et la classe fille sera donc abstraite.
- Il n’y a pas d’implémentation d’une méthode virtuelle pure
- Le prototype de la méthode virtuelle pure doit obligatoirement être défini (pour pouvoir être hérité). Sa définition est la suivante:
virtual typeRetour nomMethode(arguments) = 0;