Voici le chapitre sur les classes et les objets en C++.
Si vous avez des remarques ou suggestions afin de le parfaire.
N’hésitez pas à me contacter via mon email:
pr.azizdarouichi@gmail.com.
Bonne lecture.
2. 2
Notion de classe
Portée des attributs
Définition des méthodes, Définition externe des méthodes
Actions et Prédicats
Déclaration et utilisation d’objets
Appel aux méthodes
Accès aux attributs et méthodes
Encapsulation et interface
Accesseurs et manipulateurs
Masquage/Shadowing
Pointeur sur l’objet courant : this
Constructeur, Liste d’initialisation, Chainage des constructeurs
Constructeur de copie, Destructeur
Création des instances dynamiques
Membres de classe
Typologie des méthodes d’une classe
Q & A
Notion de classe
Portée des attributs
Définition des méthodes, Définition externe des méthodes
Actions et Prédicats
Déclaration et utilisation d’objets
Appel aux méthodes
Accès aux attributs et méthodes
Encapsulation et interface
Accesseurs et manipulateurs
Masquage/Shadowing
Pointeur sur l’objet courant : this
Constructeur, Liste d’initialisation, Chainage des constructeurs
Constructeur de copie, Destructeur
Création des instances dynamiques
Membres de classe
Typologie des méthodes d’une classe
Q & A
Chapitre 5: Classes et Objets
2
7. 7
Définition d’une classe
Exemple 1:
class Rectangle
{
double hauteur;
double largeur;
double surface(){
return hauteur*largeur;
}
double perimetre() {
return 2*(hauteur+largeur);
}
};
Nom de la classe
Attributs/champs (fields)
Méthodes
7
8. 8
Définition d’une classe
Exemple 2:
…
class Point
{
double x;
double y;
void translater(double dx, double dy){
x += dx;
y += dy;
}
double distance() {
return sqrt(x*x+y*y);
}
};
Attributs/champs (fields)
Nom de la classe
Méthodes
8
10. 10
Attributs : c'est le nom que l'on donne aux variables contenues
dans des classes.
Exemple 1:
Les attributs hauteur et largeur, de type double, de la classe
Rectangle pourront être déclarés par :
class Rectangle {
double hauteur;
double largeur;
};
Déclaration des attributs
10
11. 11
Exemple 2:
Nous supposerons ici qu’un objet de type Point sera représenté
par deux coordonnées entières.
Ils nous suffira de les déclarer ainsi :
int x ; // abscisse
int y ; // ordonnée
Déclaration des attributs
11
12. Les attributs sont des variables « globales » au module que constitue
la classe :
Ils sont directement accessibles dans toutes les méthodes de la
classe.
On parle de « portée de classe » (« variables globales à la classe »).
Il n’est donc pas nécessaire de les passer comme arguments des
méthodes.
Portée des attributs
1212
13. Exemple:
class Point
{
double x;
double y;
void translater(double dx, double dy){
x += dx;
y += dy;
}
double distance() {
return sqrt(x*x+y*y);
}
};
Portée des attributs
1313
14. Définition des méthodes
Une implémentation (ou définition) de méthode définit du code
exécutable qui peut être invoqué, en passant éventuellement un
nombre fixé de valeurs comme arguments.
Les méthodes sont :
des fonctions propres à la classe
qui ont accès aux attributs de la classe
1414
15. Définition des méthodes
La syntaxe de la définition des méthodes d’une classe est la syntaxe
normale de définition des fonctions :
Syntaxe :
<typeDeRetour> nomDeLaMethode(<liste_des_paramètres> ) {
<corps de la méthode>
}
Elles sont simplement définies dans la classe elle-même.
1515
16. Définition des méthodes
<typeDeRetour>
Quand la méthode renvoie une valeur indique le type de la valeur
renvoyée (type simple ou nom d'une classe)
double min(double a, double b)
vector<int> premiers(int n)
void si la méthode ne renvoie pas de valeur (procédure):
void afficher(double m[], int N)
1616
17. Définition des méthodes
<liste_des_paramètres>
Vide si la méthode n’a pas de paramètres
void afficher()
Une suite de couples type identificateur séparés par des virgules
double max(double a, double b)
double moyenne(array<double, 7> tab)
1717
18. Définition des méthodes
<corps de la méthode>
Suite de déclarations de variables locales et d’instructions
Si la méthode a un type de retour le corps de la méthode doit contenir
au moins une instruction return expression où expression délivre une
valeur compatible avec le type de retour déclaré.
double min(double a, double b) {
double vMin; // variable locale
if (a < b) vMin = a;
else vMin = b;
return vMin; //instruction de retour
}
1818
19. Définition des méthodes
return sert aussi à sortir d’une méthode sans renvoyer de valeur
(méthode ayant void comme type retour).
void afficherPosition(double tab[], int taille, double val) {
for(int i(0); i < taille; i++){
if (tab[i] == val){
cout << "La position de " << val << " est " << i << endl;
return;
}
}
cout << val << " n’est pas présente dans le tableau" << endl;
}
1919
20. Définition des méthodes
Les variables locales sont des variables déclarées à l’intérieur d’une
méthode:
elles conservent les données qui sont manipulées par la méthode.
elles ne sont accessibles que dans le bloc dans lequel elles ont été
déclarées, et leur valeur est perdue lorsque la méthode termine
son exécution.
void method1(...) {
int i;
double y;
int tab[5];
...
}
double method2(...) {
double x;
double y;
double tab[5];
...
}
Possibilité d’utiliser le même identificateur dans
deux méthodes distinctes pas de conflit, c’est la
déclaration locale qui est utilisée dans le corps
de la méthode.
2020
21. La récursivité des méthodes
C++ autorise la récursivité des appels de méthodes.
Exemple:
unsigned long facto(unsigned int n){
if (n == 0) return 1;
else return facto(n-1)*n;
}
2121
22. Définition des méthodes
Exemple 1:
La méthode surface() de la classe Rectangle :
class Rectangle{
double hauteur;
double largeur;
double surface(){
return hauteur * largeur;
}
//…
};
Il ne faut pas passer les attributs comme arguments aux méthodes de la
classe.
2222
23. Définition des méthodes
Exemple 2 :
Les méthodes peuvent avoir des paramètres :
class Point{
// attributs
int x;
int y;
// méthodes
void initialise(int abs, int ord){
x = abs ;
y = ord ;
}
//…
};
2323
24. Définition des méthodes
Exemple 3 :
Les méthodes peuvent avoir des paramètres :
class Test{
//…
//…
double min(double a, double b) {
if (a < b) return a;
else return b;
}
};
2424
25. Portée des variables
De manière générale
Variable visible à l'intérieur du bloc (ensembles des instructions entre
{ … }) où elle est définie.
class Visibilite{
int x;
void methodeA() {
float z, w; ...
i = …;
}
void methodeB(float y) {
int z;
do {
...
float w;
z++;
} while (z < i);
x = z +w;
}
};
x : int z : float w : float
x : int y : float z : int
x : int y : float z : int w: float
w: non défini
2525
26. Portée des variables
Remarque:
La redéfinition d’une variable masque la définition au niveau du bloc
englobant.
class Visibilite {
int x;
void methodeA() {
float z, w; ...
i = …;
}
void methodeB(float y) {
int z;
float x;
do {
...
float w;
z++;
} while (z < i);
x = z +…;
}
};
x : int z : float w : float
x : float y : float z : int
x : float y : float z : int w: float
2626
27. Définition externe des méthodes
Il est possible d’écrire les définitions des méthodes à l’extérieur de
la déclaration de la classe.
Meilleure lisibilité du code, modularisation
Pour relier la définition d’une méthode à la classe pour laquelle elle
est définie, il suffit d’utiliser l’opérateur :: de résolution de portée :
La déclaration de la classe contient les prototypes des méthodes
Les définitions correspondantes spécifiées à l’extérieur de la
déclaration de la classe se font sous la forme :
typeDeRetour NomDeLaClasse::nomMethode(<liste_des_paramètres>)
{
<corps de la méthode>
}
2727
28. Définition externe des méthodes
28
Exemple 1:
La méthode surface() de la classe Rectangle définie externe :
class Rectangle {
double hauteur;
double largeur;
double surface(); // prototype
//...
};
// définition de la méthode
double Rectangle::surface(){
return hauteur * largeur;
}
28
29. Actions et Prédicats
29
En C++, on peut distinguer les méthodes qui modifient l’état de l’objet
(« actions ») de celles qui ne changent rien à l’objet (« prédicats »).
On peut pour cela ajouter le mot const après la liste des paramètres de
la méthode :
typeDeRetour nomDeLaMethode(liste_des_paramètres) const
29
30. Actions et Prédicats
30
Exemple:
class Rectangle {
double hauteur;
double largeur;
double surface() const; // prototype
//...
};
// définition de la méthode
double Rectangle::surface() const
{
return hauteur * largeur;
}
30
31. Déclaration d’objets
31
Déclaration d’un objet (ou une instance d’une classe) se fait de
façon similaire à la déclaration d’une variable:
NomDeLaClasse nom_instance;
Exemple 1:
Rectangle rect; //déclare une instance rect de la classe Rectangle.
Point p; //déclare une instance p de la classe Point.
31
32. Utilisation des objets
Pour accéder aux membres d'un objet on utilise une notation
pointée (.):
nom_instance.nom_du_membre
Le membre pouvant être soit un attribut soit une méthode.
3232
33. Accès aux attributs
33
L’accès aux valeurs des attributs d’une instance de nom
nom_instance se fait comme pour accéder aux champs d’une
structure :
nom_instance.nom_attribut
Exemple :
La valeur de l’attribut hauteur d’une instance rect de la classe
Rectangle sera référencée par l’expression :
rect.hauteur
33
35. Appel aux méthodes
35
L’appel aux méthodes définies pour une instance de nom
nom_instance se fait à l’aide d’expressions de la forme :
nom_instance.nom_methode(arg1,…)
Exemples:
rect.surface() //rect est une instance de la classe Rectangle
tableau.size()
35
37. Accès aux attributs et méthodes
37
Chaque instance a ses propres attributs : aucun risque de confusion d’une
instance à une autre.
rect1.surface() : méthode surface de la classe Rectangle s’appliquant à rect1
rect1.surface() Rectangle::surface(&rect1)
37
39. Encapsulation et interface
39
Tout ce qu’il n’est pas nécessaire de connaître à l’extérieur d’un objet
devrait être dans le corps de l’objet et identifié par le mot clé private :
class Rectangle
{
double surface() const;
// Tout ce qui suit est privé (inaccessible depuis l'extérieur)
private:
double hauteur;
double largeur;
};
Attribut d’instance privée = inaccessible depuis l’extérieur de la classe.
C’est également valable pour les méthodes.
Si aucun droit d’accès n’est précisé, par défaut, tous les éléments d'une
classe sont private.
39
40. Encapsulation et interface
40
L’interface, qui est accessible de l’extérieur, se déclare avec le mot-clé public:
class Rectangle
{
// Tout ce qui suit est public (accessible depuis l'extérieur)
public:
double surface() const;
// Tout ce qui suit est privé (inaccessible depuis l'extérieur)
private:
// ...
};
40
41. Encapsulation et interface
41
Best practice: dans la plupart des cas :
Privé :
Tous les attributs (encapsulation)
La plupart des méthodes
Public :
Quelques méthodes bien choisies (interface)
Règle d'or en POO:
Encapsulation : tous les attributs d'une classe doivent toujours être
privés.
41
42. Encapsulation et interface
42
En règle générale, il est recommandé de masquer les attributs
(champs) en les déclarant private et -seulement si nécessaire- de
définir des méthodes publiques pour accéder à ces attributs.
Ces méthodes sont appelées accesseurs/mutateurs ou
getters/setters, généralement nommées getxxx() et setxxx().
Il est utile de proposer des méthodes pour accéder aux attributs:
Lecture = getter
Écriture = setter
42
43. Encapsulation et interface
43
Getters/Setters
Donc, si le programmeur le juge utile, il inclut les méthodes publiques nécessaires:
Accesseurs (« méthodes get » ou « getters ») :
Consultation (i.e. « prédicat »)
Retour de la valeur d’une variable d’instance précise
Exemple 1:
double getHauteur() const { return hauteur; }
double getLargeur() const { return largeur; }
43
44. Accesseurs et manipulateurs
44
Getters/Setters
Manipulateurs (« mutateurs » ou « méthodes set » ou « setters ») :
Modification (i.e. « action »)
Affectation de l’argument à une variable d’instance précise
Exemple:
void setHauteur(double h) { hauteur = h; }
void setLargeur(double l) { largeur = l; }
44
47. Masquage/Shadowing
47
masquage = un identificateur « cache » un autre identificateur
int main(){
int i(12);
for (int i(0); i < MAX; ++i) {
cout << i << endl;
}
cout << i << endl;
return 0;
}
47
48. Masquage/Shadowing
48
Situation typique en POO :
Un paramètre cache un attribut:
//…
class Rectangle{
public:
void setHauteur(double hauteur) //paramètre hauteur
{
hauteur = hauteur; //ambiguïté
}
//…
private:
double hauteur; //attribut hauteur
double largeur;
};
48
49. Masquage et pointeur this
49
Si, dans une méthode, un attribut est masqué alors la valeur de
l’attribut peut quand même être référencée à l’aide du mot réservé
this.
this est un pointeur sur l’instance courante
this « adresse de l’objet courant »
Syntaxe pour spécifier un attribut en cas d’ambiguïté :
this -> nom_attribut
Exemple :
void setHauteur(double hauteur) {
this->hauteur = hauteur;
}
L’utilisation de this est obligatoire dans les situations de masquage.
49
50. Pointeur sur l’objet courant : this
Exemple 1: this et variables d’instance
Implicitement quand dans le corps
d’une méthode un attribut est utilisé,
c’est un attribut de l’objet courant.
this essentiellement utilisé pour
lever les ambiguïtés.
50
class Point{
double x;
double y;
void translater(int dx, int dy) {
x += dx;
y += dy;
}
double distance() {
return sqrt(x*x+y*y);
}
void placerAuPoint(double x, double y1){
this->x = x;
y = y1;
}
};
50
51. Pointeur sur l’objet courant : this
Exemple 2: this et variables d’instance
int main(){
Point a(1, 3) ;
Point b(2, 5) ;
Point c(1, 3) ;
cout << "a et b : " << a.coincide(b) <<endl;
cout << "a et c : " << a.coincide(c) <<endl;
}
51
class Point{
private:
int x, y;
public Point(int abs, int ord){
x = abs ;
y = ord ;
}
public:
bool coincide(Point pt){
return ((pt.x == x) && (pt.y == y)) ;
}
};
51
52. Pointeur sur l’objet courant : this
Exemple 2: this et variables d’instance
52
class Point{
private:
int x, y;
public Point(int abs, int ord){
x = abs ;
y = ord ;
}
public:
bool coincide(Point pt){
return ((pt.x == this->x) && (pt.y == this->y)) ;
}
};
int main(){
Point a(1, 3) ;
Point b(2, 5) ;
Point c(1, 3) ;
cout << "a et b : " << a.coincide(b) <<endl;
cout << "a et c : " << a.coincide(c) <<endl;
}
52
53. Classes et Objets
53
Exemple 1:
#include <iostream>
using namespace std;
// définition de la classe
class Rectangle{
// définition des méthodes
public:
double surface() const { return hauteur * largeur; }
double getHauteur() const { return hauteur; } //Accesseur correspondant à hauteur.
double getLargeur() const { return largeur; } //Accesseur correspondant à largeur.
void setHauteur(double h) { hauteur = h; } //Manipulateur pour hauteur
void setLargeur(double l) { largeur = l; } //Manipulateur pour hauteur
// déclaration des attributs
private:
double hauteur;
double largeur;
};
53
54. Classes et Objets
54
Exemple 1 : (suite)
//utilisation de la classe
int main(){
Rectangle rect;
double h, l;
cout << "Saisir la hauteur du rectangle ? " << endl;
cin >> h;
rect.setHauteur(h);
cout << "Saisir la largeur du rectangle ? " << endl;
cin >> l;
rect.setLargeur(l);
cout << "Surface du rectangle = " << rect.surface() << endl;
return 0;
}
54
55. 55
Exemple 1:
Output:
Saisir la hauteur du rectangle ?
6
Saisir la largeur du rectangle ?
7
Surface du rectangle = 42
Classes et Objets
55
56. 56
Définitions externes à la classe
Exemple 2:
//Prototype de la classe
class Rectangle{
public:
// prototypes des méthodes
double surface() const;
// accesseurs
double getHauteur() const;
double getLargeur() const;
// manipulateurs
void setHauteur(double);
void setLargeur(double);
private:
// déclaration des attributs
double hauteur;
double largeur; };
Classes et Objets
56
57. 57
Définitions externes à la classe
Exemple 2: (suite)
//Définition de la classe
double Rectangle::surface() const {
return hauteur*largeur;
}
double Rectangle::getHauteur() const{
return hauteur;
}
double Rectangle::getLargeur() const{
return largeur;
}
void Rectangle::setHauteur(double h){
hauteur = h;
}
void Rectangle::setLargeur(double l){
largeur = l;
}
Classes et Objets
57
58. Best practice (1/2)
58
Définitions externes à la classe
//Prototype de la classe:
class Rectangle{
public:
// Prototypes des méthodes:
double surface() const;
// accesseurs
double hauteur() const;
double largeur() const;
// manipulateurs
void hauteur(double);
void largeur(double);
private:
// Déclaration des attributs:
double hauteur_;
double largeur_;
};
58
59. Best practice (2/2)
59
Définitions externes à la classe
//Définition de la classe:
double Rectangle::surface() const{
return hauteur_ * largeur_;}
// définition des accesseurs
double Rectangle::hauteur() const{
return hauteur_;}
double Rectangle::largeur() const{
return largeur_;}
// définition des manipulateurs
void Rectangle::hauteur(double h){
hauteur_ = h;
}
void Rectangle::largeur(double l){
largeur_ = h;
}
59
60. Constructeurs
60
Initialisation des attributs
Première solution : affecter individuellement une valeur à chaque attribut.
Exemple:
class Rectangle {
public:
double surface() const;
double getHauteur() const;
double getLargeur() const;
void setHauteur(double h);
void setLargeur(double l);
private:
double hauteur;
double largeur;
};
60
61. Constructeurs
61
Initialisation des attributs
Première solution : affecter individuellement une valeur à chaque attribut.
Exemple: (suite)
Rectangle rect;
double h, l;
cout << "Quelle hauteur ? ";
cin >> h;
rect.setHauteur(h);
cout << "Quelle largeur ? ";
cin >> l;
rect.setLargeur(l);
61
62. Constructeurs
62
Initialisation des attributs
Première solution : affecter individuellement une valeur à chaque attribut.
Ceci est une mauvaise solution dans le cas général :
elle implique que tous les attributs fassent partie de l’interface (public) ou soient
assortis d’un manipulateur.
casse l’encapsulation
oblige le programmeur-utilisateur de la classe à initialiser explicitement tous
les attributs.
risque d’oubli
62
63. Constructeurs
63
Initialisation des attributs
Deuxième solution : définir une méthode dédiée à l’initialisation des
attributs:
class Rectangle{
public:
void init(double h, double L){
hauteur = h;
largeur = L;
}
...
private:
double hauteur;
double largeur;
};
63
64. Constructeurs
64
Pour faire ces initialisations, il existe en C++ des méthodes
particulières appelées constructeurs.
Un constructeur est une méthode :
invoquée automatiquement lors de la déclaration d’un objet;
chargée d’effectuer toutes les opérations requises en « début de vie »
de l’objet (dont l’initialisation des attributs).
64
66. Constructeurs
66
Un constructeur est une sorte de méthode qui sera invoquée lors de
la création d’un objet de cette classe.
Un constructeur doit porter le nom de la classe et ne doit pas
comporter de type de retour (pas même void) dans sa déclaration.
Un constructeur sera invoqué automatiquement à chaque fois
qu’une instance est créée.
Le but de constructeur est d’initialiser l’objet (notamment la valeur
de ses champs).
66
67. Constructeurs
67
Comme les autres méthodes :
les constructeurs peuvent être surchargés
on peut donner des valeurs par défaut à leurs paramètres
Une classe peut donc avoir plusieurs constructeurs, pour peu que
leur liste de paramètres soit différente.
67
68. Constructeurs
Initialisation par constructeur
La création d'une instance (objet) est simple et répond à la syntaxe
suivante :
Syntaxe :
NomDeLaClasse nom_instance(arg1, ..., argN);
où arg1, ..., argN sont les valeurs des arguments du constructeur.
Exemple :
Rectangle r(18.0, 5.3); // invocation du constructeur à 2 paramètres
6868
70. Instanciation des objets
70
Exemple (2/2):
// ...
int main(){
double h, l;
cout << "Quelle hauteur ? ";
cin >> h;
cout << "Quelle largeur ? ";
cin >> l;
Rectangle rect1(h,l); // instanciation de l’objet rect de type Rectangle
cout << "Surface = " << rect1.surface();
Rectangle rect2(4.0, 5.0);
cout << "Surface = " << rect2.surface();
}
70
71. Appel aux constructeurs des attributs
71
Un constructeur devrait normalement contenir une section d’appel
aux constructeurs des attributs....
...ainsi que l’initialisation des attributs de type de base.
C’est ce qu’on appelle la « liste d’initialisation » du constructeur.
Syntaxe générale:
NomDeLaClasse(liste_des_paramètres)
// liste d’initialisation
: attribut1(...), // appel au constructeur de attribut1
...
, attributN(...) // appel au constructeur de attributN
{
// corps du constructeur contenant d’autres opérations
}
71
72. Liste d’initialisation
72
Cette section introduite par « : » est optionnelle mais recommandée.
Par ailleurs :
les attributs non-initialisés dans cette section:
• prennent une valeur par défaut si ce sont des objets ;
• restent indéfinis s’ils sont de type de base ;
les attributs initialisés dans cette section peuvent être changés dans
le corps du constructeur.
72
73. Liste d’initialisation
73
Utilisation mixte de liste d'initialisation et affectations dans le corps
du constructeur.
Exemple:
Rectangle(double h, double L)
: m_hauteur(h) //initialisation
{
// largeur a une valeur indéfinie jusqu'ici
m_largeur = 2.0 * L + h; // par exemple...
// la valeur de largeur est définie à partir d'ici
}
73
74. Liste d’initialisation
74
Deux petites choses à retenir impérativement concernant les listes
d'initialisation :
Les attributs doivent apparaître dans leur ordre de déclaration.
Les initialisations réalisées dans la liste d'initialisation sont effectuées
avant les instructions situées dans le code du corps du constructeur.
74
75. Construction des attributs
75
Que se passe-t-il si les attributs sont eux-mêmes des objets ?
class RectangleColor{
private:
Rectangle m_rectangle;
Couleur m_couleur;
//...
};
Mauvaise solution :
RectangleColor(double h, double L, Couleur c){
m_rectangle = Rectangle(h, L);
m_couleur = c;
}
Il faut initialiser directement les attributs en faisant appel à leurs
propres constructeurs !
75
76. Appel aux constructeurs des attributs
76
Exemple:
class Rectangle{
Rectangle(double h, double L);
// ...
};
class RectangleColor {
RectangleColor(double h, double L, Couleur c)
: rectangle(h, L), couleur(c)
{}
private:
Rectangle m_rectangle;
Couleur m_couleur;
};
76
77. Constructeur par défaut
77
Le constructeur par défaut est un constructeur qui n’a pas de
paramètre ou dont tous les paramètres ont des valeurs par défaut.
Exemple 1:
// Constructeur par défaut avec liste d’initialisation
Rectangle() : m_hauteur(2.0), m_largeur(3.0)
{}
// Constructeur par défaut sans liste d’initialisation
Rectangle() {
m_hauteur = 2.0;
m_largeur = 3.0;
}
…
int main(){
Rectangle rect1; // sans parenthèses
}
77
78. Constructeur par défaut
78
Autre façon de faire : regrouper 2 constructeurs en utilisant les
valeurs par défaut des paramètres :
Exemple 2:
// DEUX constructeurs dont le constructeur par défaut
Rectangle(double c = 1.0) : m_hauteur(c), m_largeur(2.0*c)
{}
// 3ème constructeur
Rectangle(double h, double L) : m_hauteur(h), m_largeur(L)
{}
…
int main(){
Rectangle rect1, rect2(5.5), rect3(2.5,3.5);
…
}
78
79. Constructeur par défaut
79
Exemple 3:
// TROIS constructeurs dont le constructeur par défaut
Rectangle(double h = 1.0, double L = 1.0)
: m_hauteur(h), m_largeur(L)
{}
79
80. Constructeur par défaut par défaut
80
Si aucun constructeur n’est spécifié, le compilateur génère
automatiquement une version minimale du constructeur par défaut
qui :
appelle le constructeur par défaut des attributs objets.
laisse non initialisés les attributs de type de base.
Dès qu’au moins un constructeur a été spécifié, ce constructeur par
défaut par défaut n’est plus fourni.
Si donc on spécifie un constructeur sans spécifier de constructeur par
défaut, on ne peut plus construire d’objet de cette classe sans les
initialiser puisqu’il n’y a plus de constructeur par défaut.
Depuis C++11, mais on peut le rajouter si on veut.
80
85. Réactivation du constructeur par défaut par défaut
85
Dès qu’au moins un constructeur a été spécifié, ce constructeur par
défaut par défaut n’est plus fourni.
C’est très bien si c’est vraiment ce que l’on veut (c’est-à-dire forcer
les utilisateurs de la classe à utiliser nos constructeurs).
Mais si l’on veut quand même avoir le constructeur par défaut par
défaut, depuis C++11 on peut le réactiver en écrivant dans la
définition de la classe :
NomDeLaClasse() = default;
85
86. Réactivation du constructeur par défaut par défaut
86
Exemple:
class Rectangle {
public:
Rectangle() = default; //réactivation du constructeur par défaut par défaut
// 2ème constructeur
Rectangle(double h, double L) : h_hauteur(h), m_largeur(L) {}
// Méthode d’instance
double surface() const { return m_hauteur * m_largeur; }
// Variables d’instance
private:
double m_hauteur;
double m_largeur;
};
86
87. Méthodes default et delete depuis C++11
87
Ce que l’on a fait précédemment (= default) pour le constructeur
par défaut se généralise :
à toute méthode (pour laquelle cela est pertinent)
à la suppression de méthode, via la syntaxe « = delete »
Exemple:
class DemoClasse{
public:
double f(double x) { ... }
double f(int) = delete;
};
87
88. Chaînage des constructeurs
88
C++11 autorise les constructeurs d’une classe à appeler n’importe
quel autre constructeur de cette même classe.
Dans les classes définissant plusieurs constructeurs, un constructeur
peut invoquer un autre constructeur de cette classe.
88
90. Initialisation par défaut des attributs
90
C++11 permet de donner directement une valeur par défaut aux
attributs.
Si le constructeur appelé ne modifie pas la valeur de cet attribut,
ce dernier aura alors la valeur indiquée.
Exemple:
class Rectangle {
// ...
private:
double hauteur = 0.0;
double largeur = 0.0;
// ...
};
90
91. Constructeur de copie
91
C++ offre un moyen de créer la copie d’une instance en utilisant le
constructeur de copie lors de l’initialisation:
Rectangle r1(11.3, 12.5);
Rectangle r2(r1); // constructeur de copie: création de l'objet r2
// et son initialisation avec les données de r1.
Ou
Rectangle r2 = r1; // constructeur de copie: création de l'objet r2
// et son initialisation avec les données de r1.
Ou
Rectangle* r2 = new Rectangle(r1); // constructeur de copie: création de l'objet
// dynamique r2 et son initialisation avec les données de r1.
r1 et r2 sont deux instances distinctes mais ayant des mêmes valeurs
pour leurs attributs.
91
92. Constructeur de copie
92
Le constructeur de copie est appelé dans trois situations:
1. Lors de l’initialisation d’un objet:
Création d'un nouvel objet initialisé comme étant une copie d'un
objet existant.
2. Lors d’un passage de paramètres par valeur :
double maFonction(Rectangle r); //prototype de maFonction
...
x = maFonction(r1); //appel de maFonction
Copie de r1 dans r donc invocation du constructeur de copie.
3. Lors de retour de fonction par valeur
Rectangle g() {
Rectangle rect;
…
return rect; // retour par valeur de rect
}
92
93. Constructeur de copie
93
Le constructeur de copie permet d’initialiser une instance en
copiant les attributs d’une autre instance du même type.
Deux syntaxes possibles :
NomDeLaClasse(NomDeLaClasse const& autre) { ... }
ou
NomDeLaClasse(NomDeLaClasse& autre) { ... }
Exemple:
Rectangle(Rectangle const& autre)
: m_hauteur(autre.m_hauteur), m_largeur(autre.m_largeur)
{}
93
94. Constructeur de copie
94
Le but de ce type de constructeur est d'initialiser un objet lors de
son instanciation à partir d'un autre objet.
Toute classe dispose d'un constructeur de copie par défaut
généré automatiquement par le compilateur s’il n’est pas
explicitement défini (constructeur de copie par défaut).
Le seul but est de recopier les attributs de l'objet un à un dans
attributs de l'objet à instancier (si l’attribut est un objet le
constructeur de cet objet est invoqué).
Donc, le rôle du constructeur de copie est de copier la valeur de
tous les attributs du premier objet dans le second.
94
95. Constructeur de copie
95
Ce constructeur se contente d'effectuer une copie de chacun
des membres. On retrouve là une situation analogue à celle qui est
mise en place (par défaut) lors d'une affectation entre objets de même
type. Elle posera donc les mêmes problèmes pour les objets contenant
des pointeurs sur des emplacements dynamiques.
On aura simplement affaire à une "copie superficielle", c'est-à-dire
que seules les valeurs des pointeurs seront recopiées, les
emplacements pointés ne le seront pas.
copie de surface ou copie superficielle
95
96. Constructeur de copie
96
Cette copie de surface suffit dans la plupart des cas.
Cependant, il est parfois nécessaire de redéfinir le constructeur
de copie, en particulier lorsque certains attributs sont des
pointeurs.
Il faut alors allouer une nouvelle zone mémoire, on parle alors de
copie profonde (allocation puis recopie).
96
97. Constructeur de copie
97
La règle de trois :
Si vous touchez à l’un des trois parmi:
constructeur de copie,
destructeur,
opérateur d’affectation (operator=),
alors pensez aux deux autres.
97
98. Constructeur de copie
98
Remarque:
Il faut faire attention entre copie et affectation.
Rectangle rect1, rect2; // création de deux objets rect1 et rect2.
rect1 = rect2; // pas de copie mais uniquement l'affectation
// car l’objet est déjà créé.
98
99. Suppression du constructeur de copie
99
Depuis C++11, si l’on souhaite interdire la copie, il suffit de
supprimer le constructeur de copie par défaut avec la commande
« = delete ».
Exemple:
class Incopiable {
/*... */
Incopiable(Incopiable const&) = delete;
};
99
100. Destructeur
100
Si l’initialisation des attributs d’une instance implique la mobilisation
de ressources : fichiers, périphériques, portions de mémoire
(pointeurs), etc.
Il est alors important de libérer ces ressources après usage.
Comme pour l’initialisation, l’invocation explicite de méthodes de
libération n’est pas satisfaisante (fastidieuse, source d’erreur,
affaiblissement de l’encapsulation).
100
101. Destructeur
101
C++ offre une méthode appelée destructeur invoquée
automatiquement en fin de vie de l’instance.
Il s'exécute à la fin du programme ou d'un bloc où des objets locaux
ont été définis.
Un destructeur a pour rôle de libérer la mémoire.
Il est appelé automatiquement lorsque l’objet n’est plus utilisé,
fermeture d’accolade, …
101
102. Destructeur
102
La syntaxe de déclaration d’un destructeur pour une classe
NomDeLaClasse est :
˜NomDeLaClasse()
{
// opérations (de libération)
}
Le destructeur d’une classe est une méthode sans paramètre donc
pas de surcharge possible
Son nom est celui de la classe, précédé du signe ˜ (tilda).
Si le destructeur n’est pas défini explicitement par le programmeur,
le compilateur en génère automatiquement une version minimale.
102
103. Constructeur et destructeur
103
Exemple 1:
Supposons que l’on souhaite compter le nombre d’instances d’une
classe actives à un moment donné dans un programme.
int main(){
// compteur = 0
Rectangle r1;
// compteur = 1
{
Rectangle r2;
// compteur = 2
// ...
}
// compteur = 1
return 0;
} // compteur = 0
103
Appel du constructeur
Appel du destructeur pour r2 et r1
104. Constructeur et destructeur
104
Exemple 1:
Utilisons comme compteur une variable globale de type entier :
Le constructeur incrémente le compteur
long compteur(0);
class Rectangle{
//...
Rectangle(): m_hauteur(0.0), m_largeur(0.0) //constructeur
{
++compteur;
}
// ...
104
105. Constructeur et destructeur
105
Exemple 1:
On est obligé ici de définir explicitement le destructeur
int main(){
// compteur = 0
Rectangle r1;
// compteur = 1
{
Rectangle r2;
// compteur = 2
// ...
}
// compteur = 2
return 0;
} // compteur = 2
105
106. Constructeur et destructeur
106
Exemple 1:
Le constructeur incrémente le compteur
Le destructeur le décrémente
long compteur(0);
class Rectangle {
//...
Rectangle(): m_hauteur(0.0), m_largeur(0.0) {//constructeur
++compteur;
}
~Rectangle() {// destructeur
--compteur;
} // ...
106
108. Constructeur et destructeur
108
Exemple 1:
Que se passe-il si l’on souhaite utiliser la copie d’objet ?
int main(){
// compteur = 0
Rectangle r1;
// compteur = 1
{
Rectangle r2;
// compteur = 2
Rectangle r3(r2);
// compteur = ??
}
// compteur =
return 0;
} // compteur =
108
Appel du constructeur
Appel du constructeur de copie
Appel du destructeur pour r2, r3 et r1
109. Constructeur et destructeur
109
Exemple 1:
La copie d’un rectangle échappe au compteur d’instances car il n’y a
pas de définition explicite du constructeur de copie.
Il faudrait donc encore ajouter au code précédent, la définition
explicite du constructeur de copie :
Rectangle(Rectangle const& r)
: m_hauteur(r.m_hauteur), m_largeur(r.m_largeur)
{ ++compteur; }
Règle générale :
Si on doit toucher à l’un des trois parmi destructeur, constructeur
de copie et opérateur d’affectation (=), alors on doit certainement
également toucher aux deux autres (ou alors au moins se poser la
question !).
109
110. Constructeur et destructeur
110
Exemple 2:
class Chaine{ // Implémente une chaîne de caractères.
private:
char* s; // Le pointeur sur la chaîne de caractères.
public:
Chaine(); // Le constructeur par défaut.
Chaine(int); // Le constructeur. Il n'a pas de type.
~Chaine(); // Le destructeur.
};
Chaine::Chaine(){
s = nullptr; } // La chaîne est initialisée avec le pointeur nullptr.
Chaine::Chaine(int Taille){
s = new char[Taille+1]; // Alloue de la mémoire pour la chaîne.
s[0]='0'; // Initialise la chaîne à "".
}
Chaine::~Chaine(){
if (s!=nullptr) delete[] s; // Restitue la mémoire utilisée si nécessaire.
}
110
111. Création des instances dynamiques
111
Dans le cas d'une instance dynamique, il faut commencer par déclarer
un pointeur, puis appeler l’opérateur new suivi du nom du constructeur
avec ses paramètres.
Syntaxe:
NomDeLaClasse *nom_instance;
nom_instance = new NomDeLaClasse(arg1,..., argN);
Ou directement
NomDeLaClasse *nom_instance = new NomDeLaClasse(arg1,...,argN);
où arg1, ..., argN sont les valeurs des arguments du constructeur.
111
112. Création des instances dynamiques
112
Exemple:
Rectangle *r1(nullptr), *r2(nullptr); // Déclaration d'un pointeur sur un objet de type Rectangle
r1 = new Rectangle(4.0, 5.0);// Instanciation de l'objet dynamique avec arguments explicites
r2 = new Rectangle; // Instanciation de l'objet dynamique avec arguments par défaut
delete r1; // On n'oublie pas de rendre la mémoire !
delete r2; // Idem
112
113. Création des instances dynamiques
113
Appel de méthodes d’instance
La syntaxe d'appel d'un membre d’instance par un objet dynamique est:
nom_instance -> nom_membre
Il faut juste changer le "." par une flèche "->".
Exemple:
Rectangle *rect1 = new Rectangle(2.5, 3.5);
Rectangle *rect2 = new Rectangle;
cout << "Hauteur = "<< rect1-> m_hauteur << endl;
cout << "Largeur = "<< rect1-> m_largeur << endl;
cout << "Surface du rectangle 1 = "<< rect1-> surface() << endl;
cout << "Surface du rectangle 2 = "<< (*rect2).surface() << endl;
113
114. Envoi de messages
Pour "demander" à un objet d'effectuer une opération (exécuter
l'une de ses méthodes) il faut lui envoyer un message.
Un message est composé de trois parties:
un identificateur permettant de désigner l'objet à qui le
message est envoyé.
le nom de la méthode à exécuter (cette méthode doit bien
entendu être définie dans la classe de l'objet).
les éventuels paramètres de la méthode.
114114
115. Envoi de messages
Envoi de message similaire à un appel de fonction
les instructions définies dans la méthode sont exécutées (elles
s’appliquent sur les attributs de l’objet récepteur du message).
puis le contrôle est retourné au programme appelant.
Les objets communiquent entre eux par échange de messages.
115115
116. Envoi de messages
Syntaxe :
nom_instance.nomDeMethode(<liste_paramètres_effectifs>)
Si la méthode ne possède pas de paramètres, la liste est vide, mais comme en langage C les
parenthèses demeurent.
class Point{
private:
double x{0.0};
double y{0.0};
public:
void translater(double dx, double dy){
x += dx;
y += dy;
}
double distance(){
return sqrt(x*x+y*y);
}
}; // Point
Point p1, p2;
p1.translater(10.0,10.0);
cout << “Distance de p2 à l’origine : “<< endl;
cout << p2.distance();
116116
117. Mise en œuvre d’un programme comportant
plusieurs fichiers
Un fichier entête Classe.h qui définit la classe (appelé interface) va contenir
le prototype de la classe, c.à.d., la déclaration de la classe avec les attributs et
les prototypes des méthodes.
Un fichier source Classe.cpp contient la définition de la classe, c’est là
qu'on va implémenter les méthodes membres.
L’implémentation des méthodes peut se faire dans l’entête.
Un fichier source (.cpp) contient la fonction main().
Exemple:
Point.h: contient le prototype de la classe (interface);
Point.cpp: contient la définition de la classe, c.à.d., l'implémentation des
méthodes membres de la classe.
TestPoint.cpp: contient la fonction main().
117117
118. Mise en œuvre d’un programme comportant
plusieurs fichiers
Exemple (1/2):
Point.h Point.cpp
#ifndef POINT_H_INCLUDED
#define POINT_H_INCLUDED
class Point
{
public:
void translater(double dx, double dy);
double distance();
void afficher();
private:
double x;
double y;
};
#endif // POINT_H_INCLUDED
#include "Point.h“
#include<iostream>
#include<cmath>
using namespace std;
void Point::translater(double dx, double dy){
x += dx;
y += dy;
}
double Point::distance() {
return sqrt(x*x+y*y);
}
void Point::afficher(){
cout << "(" << x << ", "<< y << ") " << endl;
}
118118
119. Mise en œuvre d’un programme comportant
plusieurs fichiers
Exemple (2/2):
TestPoint.cpp
#include "Point.h“
#include<iostream>
using namespace std;
int main(){
Point p1, p2;
p1.translater(10.0,10.0);
p1.afficher();
cout << “distance de p2 à l’origine“ << p1.distance() << endl;
}
119119
120. Mise en œuvre d’un programme comportant
plusieurs classes
Plusieurs classes dans un même fichier source
TestPoint.cpp
120120
#include<iostream>
#include<cmath>
using namespace std;
class Point {
public:
void translater(double dx, double dy); double distance();
void afficher();
private:
double x;
double y;};
void Point::translater(double dx, double dy){ x += dx; y += dy;}
double Point::distance() { return sqrt(x*x+y*y);}
void Point::afficher(){ cout << "(" << x << ", "<< y << ") " << endl; }
int main(){
Point p1, p2;
p1.translater(10.0,10.0); p1.afficher();
cout << “distance de p2 à l’origine“ << p1.distance() << endl; }
121. Surcharge des méthodes
Surcharge (overloading) pas limitée aux constructeurs, elle est
possible pour n'importe quelle méthode.
Possible de définir des méthodes possédant le même nom mais
dont les arguments diffèrent.
Lorsque qu'une méthode surchargée est invoquée le compilateur
sélectionne automatiquement la méthode dont le nombre et le type
des arguments correspondent au nombre et au type des paramètres
passés dans l'appel de la méthode.
Des méthodes surchargées peuvent avoir des types de retour
identiques mais à condition qu'elles aient des arguments différents.
121121
122. Surcharge des méthodes
Exemple
class Point{
private:
double x;
double y;
public:
// constructeur
Point(double x, double y): x(x), y(y){}
// destructeur
~Point(){cout<< “Destructeur“ <<endl;}
// méthodes
double distance() { return sqrt(x*x+y*y); }
...
}
double distance(Point p){
return sqrt((x - p.x)*(x - p.x) + (y - p.y)*(y - p.y));
}
…
};
Point p1(10,10);
Point p2(1.5,1.4);
cout<<“Distance =”<<p1.distance();
cout<<“Distance entre p1 et p2 =”;
cout<<p1.distance(p2);
122122
124. Variables et méthodes d’instance
Variables d’instance ou attributs d’instance
Déclarées par accès: type nomDeVariableInstance
Référencées par Objet.nomDeVariableInstance
Une copie pour chaque instance (objet)
Chaque objet possède ses propres membres donnée.
Méthodes d’instance
Déclarées par accès: TypeRetour nomDeLaMethodeInstance(…)
Référencées par objet.nomDeLaMethodeInstance(…)
124124
127. Variables de classe: static
Il peut être utile de définir pour une classe des attributs
indépendamment des instances : nombre de Points crées
Les variables statiques sont enregistrées au niveau de la classe et elles
peuvent être accédées et manipulées par toutes les instances (objets)
de cette classe.
Utilisation des variables de classe comparables aux “variables
globales“ partagées par toutes les instances.
Variables dont il n’existe qu’un seul exemplaire associé à sa classe de
définition.
Variables existent indépendamment du nombre d’instances de la
classe qui ont été créés.
Variables utilisables même si aucune instance de la classe n’existe.
127127
128. Variables de classe
Déclarées par accès: static type nomDeLaVariableClasse
Référencées par NomDeLaClasse::nomDeLaVariableClasse
Une seule copie pour toutes les instances
Les variables statiques appartiennent à la classe et non aux objets
créés à partir de la classe.
Un attribut static, bien qu'il soit accessible de l'extérieur, peut très
bien être déclaré private ou protected
N. B:
Variables de classe = Variables statiques = Attributs de classe = Attributs statiques.
128128
129. Variables de classe
Initialisation d’une variable statique
Il faut initialiser l'attribut statique dans l'espace global, c'est-à-dire en
dehors de toute classe ou fonction, en dehors du main() notamment.
Un attribut déclaré comme statique se comporte comme une variable
globale, c'est-à-dire une variable accessible partout dans le code.
Un attribut de classe doit être initialisé explicitement à l’extérieur de
la classe.
Les attributs de classe sont très pratiques lorsque différents objets
d’une classe doivent accéder à une même information.
Ils permettent notamment d’éviter que cette information soit
dupliquée au niveau de chaque objet.
Exemple:
// Initialiser l'attribut en dehors de toute fonction ou classe (espace global)
int MaClasse::attributStatique = 10;
129129
130. Variables de classe
Exemple:
class Point {
private:
double x;
double y;
static int nbPointCrees;
public:
Point(double x, double y){
this->x = x;
this->y=y;
}
…
};
//initialisation de l’attribut statique
int Point::nbPointCrees=0;
130130
132. Méthodes de classe
Usage
Ce sont des méthodes qui ne s'intéressent pas à un objet particulier
Utiles pour des calculs intermédiaires internes à une classe
Utiles également pour retourner la valeur d'une variable de classe
Elles sont déclarées comme les méthodes d'instances, mais avec le mot-
clé static:
public: static double vitesseMaxToleree();
Elles sont définies hors de la classe sans static:
double Voiture::vitesseMaxToleree() {
return vitesseMaxAutorisee*1.10; }
Pour y accéder, il faut utiliser non pas un identificateur d'objet mais le
nom de la classe et de l’opérateur de résolution de portée « :: » (idem
pour les variables de classe):
Voiture::vitesseMaxToleree(); // Voiture une classe
132132
133. Méthodes de classe
Exemple (1/2):
133
#include<iostream>
using namespace std;
class Point {
private:
double x;
double y;
static int compteur;
public:
Point(double x, double y);
~Point();
static int nombreInstances(); //Renvoie le nombre d'objets créés
};
//initialisation de l’attribut statique
int Point::compteur=0;
//Quand on crée un point, on ajoute 1 au compteur
Point:: Point(double x, double y):x(x), y(y){++compteur;}
//Et on enlève 1 au compteur lors de la destruction
Point:: ~Point(){--compteur;}
int Point::nombreInstances(){
return compteur; //On renvoie simplement la valeur du compteur
}
133
134. Méthodes de classe
Exemple (2/2):
Output:
Il y a actuellement 2 points construits.
134
int main()
{
//On crée deux points
Point p1(1.0, 2.0);
Point p2(3.0, 4.0);
//Et on consulte notre compteur
cout << "Il y a actuellement " << Point::nombreInstances() << " points construits." << endl;
return 0;
}
134
135. 135135
Propriétés des fonctions membres
Les fonctions membres (y compris les constructeurs) peuvent:
être surdéfinies,
avoir des arguments par défaut,
être public ou private,
être inline.
Les fonctions membres ont accès à tous les membres de tous les
objets de la classe.
136. Surdéfinition des fonctions membre
C++ autorise la surdéfinition (ou surcharge) des fonctions
membres y compris les constructeurs (mais pas aux destructeurs);
Exemple (1/2):
class Point{
private :
double x, y;
public :
Point(); // constructeur 1 sans arguments
Point(double); // constructeur 2 avec un argument
Point(double, double); // constructeur 3 avec deux arguments
void afficher(); // fonction afficher() sans arguments
void afficher(char*); // fonction affiche() avec un argument
};
Propriétés des fonctions membres
136
137. Propriétés des fonctions membres
Surdéfinition des fonctions membre
Exemple (2/2):
Point::Point(): x(0), y(0){}
Point::Point(double abs) : x(abs), y(abs) {}
Point::Point(double abs, double ord): x(abs), y(ord) {}
void Point::afficher(){
cout << "(" << x << ", " << y << ") " << endl;
}
void Point::afficher(char *msg) {
cout << msg << endl;
afficher();
}
137
138. Arguments par défaut
Tout comme une fonction C++ classique, il est possible de définir
des arguments par défaut. Ceux-ci permettent à l'utilisateur de ne
pas renseigner certains paramètres.
On peut modifier notre classe Point pour qu’elle ne possède qu’une
seule fonction affiche à un seul argument de type chaîne.
void afficher(char* = " " ); // fonction afficher(): un argument par défaut
void Point::afficher(char *msg) {
cout << msg << "(" << x << "," <<y << ")" <<endl;
}
Point A;
A.afficher(); //affichage: (0, 0)
Point B(1.5, 2.5);
B.afficher(" Point B"); // affichage : Point B(1.5, 2.5)
Propriétés des fonctions membres
138
139. Les fonctions membre en ligne
Pour rendre en ligne une fonction membre, on peut:
Soit fournir directement la définition de la fonction dans la
déclaration même de la classe, dans ce cas le qualificatif inline
n’a pas à être utilisé (implicite).
Soit procéder comme pour une fonction ’’ordinaire’’ en
fournissant une définition en dehors de la déclaration de la
classe; dans ce cas, le qualificatif inline doit apparaître à la fois
devant la déclaration et devant l’en tête (explicite).
Propriétés des fonctions membres
139
140. Les fonctions membre en ligne
Exemple:
class Rectangle{
…
public :
inline Rectangle();
…
}; // Fin de la classe
inline Rectangle(): hauteur(0.0), largeur(0.0)
{
}
Propriétés des fonctions membres
140
141. Typologie des méthodes d’une classe
141
Parmi les différentes méthodes que comporte une classe, on a souvent
tendance à distinguer :
Les constructeurs et les destructeurs;
Les méthodes d’accès (en anglais accessor/getter), en bon français
accesseurs, qui fournissent des informations relatives à l’état d’un
objet, c’est-à-dire aux valeurs de certains de ses champs (généralement
privés), sans les modifier ;
Les méthodes d’altération (en anglais mutator/setter), en bon
français manipulateur ou mutateur, qui modifient l’état d’un objet,
donc les valeurs de certains de ses champs.
141