Académique Documents
Professionnel Documents
Culture Documents
Classes et objets
PRG1-v1.5
Plan du chapitre 7
Pourquoi la POO ?
▪ Au tout début de PRG1, vous écriviez tout votre code dans la fonction main()
4
Pourquoi la POO ?
▪ Au tout début de PRG1, vous écriviez tout votre code dans la fonction main()
▪ Quand vos programmes sont devenus trop grands pour cela, vous avez
appris à les organiser en les divisant en fonctions qui résolvent des sous-
problèmes
4
Pourquoi la POO ?
▪ Au tout début de PRG1, vous écriviez tout votre code dans la fonction main()
▪ Quand vos programmes sont devenus trop grands pour cela, vous avez
appris à les organiser en les divisant en fonctions qui résolvent des sous-
problèmes
▪ S’ils grandissent encore, il devient difficile de maintenir une collection de
fonctions de plus en plus grande
4
Pourquoi la POO ?
▪ Au tout début de PRG1, vous écriviez tout votre code dans la fonction main()
▪ Quand vos programmes sont devenus trop grands pour cela, vous avez
appris à les organiser en les divisant en fonctions qui résolvent des sous-
problèmes
▪ S’ils grandissent encore, il devient difficile de maintenir une collection de
fonctions de plus en plus grande
▪ Il devient tentant, voire nécessaire, d’utiliser des variables globales… ☹
4
Pourquoi la POO ?
5
Pourquoi la POO ?
5
Pourquoi la POO ?
5
Pourquoi la POO ?
Fonction
Fonction
5
Pourquoi la POO ?
Fonction
▪ Mais…
Fonction
Pourquoi la POO ?
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
6
Pourquoi la POO ?
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
6
Pourquoi la POO ?
Fonction
▪ Un grand nombre de fonctions sont
Fonction
potentiellement affectées
Données
Fonction
globales
Fonction
Fonction
Fonction
6
Pourquoi la POO ?
Fonction
▪ Un grand nombre de fonctions sont
Fonction
potentiellement affectées
Données
Fonction
globales
Fonction
Fonction
Fonction
6
Pourquoi la POO ?
Fonction
▪ Un grand nombre de fonctions sont
Fonction
potentiellement affectées
Données
Fonction
globales
▪ Vous devrez toutes les réécrire
Fonction
Fonction
Fonction
6
Pourquoi la POO ?
Fonction
▪ Un grand nombre de fonctions sont
Fonction
potentiellement affectées
Données
Fonction
globales
▪ Vous devrez toutes les réécrire
Fonction
6
Pourquoi la POO ?
Fonction
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
8
Programmation fonctionnelle -> POO
Déterminer quelles données vont avec quelles fonctions
Fonction
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
8
Programmation fonctionnelle -> POO
Déterminer quelles données vont avec quelles fonctions
Fonction
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
8
Programmation fonctionnelle -> POO
Créer un objet pour chaque sous-ensemble de données
Fonction
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
9
Programmation fonctionnelle -> POO
Objet
Fonction membre
Données
Fonction membre membres
Fonction
Fonction
Fonction
Données
Fonction
globales
Fonction
Fonction
Fonction
Fonction
Objet
Fonction Fonction membre Données
Données membres
Fonction
globales
Fonction
Fonction
Fonction
Fonction
Objet
Fonction Fonction membre Données
Données membres
Fonction
globales
Fonction
Fonction membre
Fonction
Données
Fonction membre membres
Fonction membre
9
Objet
Fonction membre
Objet
Fonction membre
Données
Fonction membre membres
Fonction membre
9
Encapsulation et classes
10
Encapsulation et classes
10
Encapsulation et classes
10
Encapsulation et classes
10
Encapsulation et classes
10
Encapsulation et classes
10
Éléments de POO déjà vus
11
Éléments de POO déjà vus
11
Éléments de POO déjà vus
11
Autres concepts de POO après PRG1
12
2. Classes
13
Que contient une classe en C++ ?
14
Déclaration
15
Déclaration
15
Exemple – Rectangle
16
Exemple – Rectangle
!
llé
private: double largeur; double longueur;
ei
public: double surface() const; public:
private: double longueur; ns void setDims(double la, double lo);
co
Dé
▪ Une fois la classe Rectangle définie (avec class), on peut utiliser son nom
comme n’importe quel nom de type
▪ Une variable (objet) de type Rectangle s’utilise comme n’importe quelle autre
variable du C++
▪ peut-être variable ou constante
▪ allocation statique, automatique ou dynamique
▪ passage par valeur, référence ou référence constante
▪ On accède aux membres (données ou fonctions) avec la notation pointée
Rectangle r;
r.setDims(2.0, 3.0);
cout << r.surface();
18
▪ S’il est nécessaire d’y accéder, il faut définir des accesseurs, i.e. des
fonctions membres permettant de
▪ lire la donnée privée
(un « sélecteur » ou « getter ») double getLargeur() const {
return largeur;
}
▪ modifier la donnée privée
(un « modificateur » ou « setter ») void setLargeur(double val) {
largeur = val;
}
19
❖ this (parfois appelé autoréférence) ne peut être utilisé qu’au sein d’une fonction
membre et représente un pointeur sur l’objet ayant appelé la fonction.
20
this
▪ Une variable pointeur (ou simplement un pointeur) est une variable contenant
l’adresse mémoire d’une autre variable, appelée variable pointée.
▪ Pour pouvoir accéder (en lecture ou en écriture) à un champ donné d'un objet
pointé par un pointeur, il faut réaliser deux opérations :
1. Déréférencer le pointeur (c’est-à-dire accéder à l’objet pointé) en utilisant
l'opérateur de déréférencement (appelé aussi opérateur d’indirection) : *
2. Utiliser la notation pointée (opérateur .) pour accéder au champ voulu.
21
this
▪ Ecrire (*this).champ s’avère peu pratique.
▪ L’opérateur -> permet de simplifier les choses. En effet :
▪ Remarques :
1. N’abusez pas du mot-clé this. class Rectangle {
Sur le slide 20, par exemple, il suffit d’écrire : public:
return largeur * longueur; void setDims(double largeur, double longueur) {
this->largeur = largeur;
Il n’est pas nécessaire d’écrire : this->longeur = longueur;
return this->largeur * this->longueur; }
private:
2. Utiliser this permet d’accéder aux membres double largeur;
même si une variable locale ou un paramètre double longueur;
porte le même nom (voir exemple ci-contre) };
22
Définition des fonctions membres
23
24
3. Constructeurs
25
Motivation
▪ La première ligne crée l’objet mais les valeurs des données membres sont
indéterminées
▪ si on appelle à la 2e ligne r.surface() alors on obtient un résultat indéterminé
▪ il faudrait pouvoir initialiser l’objet lors de sa création
▪ C’est possible ! Il suffit de définir une ou plusieurs fonctions membres
particulières, appelées constructeurs
26
Constructeur
27
Constructeur
6 35
28
Surcharge de constructeurs
Rectangle::Rectangle(double cote) {
largeur = longueur = cote;
}
Rectangle::Rectangle() {
largeur = longueur = 0.;
}
29
Surcharge de constructeurs
30
31
C c1(100);
C c2; // No matching constructor for initialization of 'C'
❖ Il faudrait écrire C(){ } dans la zone publique de la déclaration de C pour définir
le constructeur vide, ou écrire plus explicitement : C() = default;
❖ Inversement, on peut aussi l’interdire en écrivant C() = delete;
32
33
et cela est possible aussi avec une partie des arguments seulement
▪ Il peut paraître plus simple d’initialiser les membres avec une valeur par
défaut ainsi : class Rectangle {
public:
void setDims(double la, double lo);
double surface() const;
private:
double largeur = 0;
double longueur = 0;
▪ Mais… };
▪ Ce n’est possible que depuis C++11
▪ L’initialisation par un constructeur est plus flexible
▪ Elle est prioritaire si les deux sont présentes
35
36
38
4. Compilation séparée
39
Organisation du code par classe
40
41
maClasse.h (suite)
…
void fonctionMembre(int n);
void fonctionMembreEnLigne() { /*...*/ }
static void fonctionStatique(int n);
static void fonctionStatiqueEnLigne() { /*...*/ }
private:
int variable;
const double constante;
static char variableStatique;
static const short CONSTANTE_STATIQUE;
};
#endif /* MACLASSE_H */
À noter que seules les données membres static const s’écrivent entièrement en majuscules.
42
maClasse.cpp
#include "maClasse.h"
// membres statiques
char MaClasse::variableStatique = 'A';
const short MaClasse::CONSTANTE_STATIQUE = 0;
// un des constructeurs
MaClasse::MaClasse(int n, double x) : variable(n), constante(x) { /* ... */ }
44
45
Nécessité de la surcharge d’opérateurs
46
Opérateurs qu’on peut surcharger
▪ Les classes définissent de nouveaux types C++
▪ Pour les types simples, nous utilisons souvent des opérateurs tels que =, +,
-, *, ++, <, ==, <<, …
▪ Pourrait-on les appliquer aussi à nos classes ?
Oui, on peut surcharger pour nos classes les opérateurs suivants :
+ - * / = < > += -= *= /= << >>
<<= >>= == != <= >= ++ -- % & ^ ! |
~ &= ^= |= && || %= [] () , ->* -> new
delete new[] delete[]
▪ Note 1 : on ne peut pas changer leur priorité ni le nombre d’arguments, et on ne peut pas
définir de nouveaux opérateurs
▪ Note 2 : l’opérateur d’affectation (=) est défini par défaut pour les classes
47
Syntaxe de la surcharge
48
Exemple
49
operator+
operator+
▪ Il faut évidemment aussi définir cette fonction membre
CVector CVector::operator+(const CVector& v) const {
CVector temp;
temp.x = this->x + v.x; // this est facultatif
temp.y = this->y + v.y; // dans les 2 lignes
return temp;
}
À noter que le code ci-dessus peut s’écrire en une ligne :
return CVector(x + v.x, y + v.y); ou encore return CVector{x + v.x, y + v.y};
51
Utilisation de CVector::operator+
CVector v3 = v1.operator+(v2);
… mais ce n’est pas intuitif et personne ne le fait !
52
operator*
Rien n’oblige à ce que les deux opérandes d’un opérateur soient du même type.
Exemple : multiplication d’un CVector par un réel
class CVector {
double x, y;
public:
… // idem exemple précédent
CVector operator*(double d) const;
};
53
operator*
54
Surcharge par fonction non membre
▪ Il est aussi possible de surcharger des opérateurs par une fonction « simple », et non
pas une fonction membre
▪ Par exemple, la fonction membre
CVector CVector::operator*(double d) const { ... }
peut être remplacée par la fonction
55
56
56
57
friend
class CVector {
friend CVector operator*(double lhs, const CVector& rhs);
public:
… // idem exemple précédent
private:
double x, y;
};
friend
Une fonction amie peut aussi être définie en ligne, dans le code de la classe.
Elle reste une fonction non membre de la classe.
class CVector {
friend CVector operator*(double lhs, const CVector& rhs) {
CVector temp;
temp.x = lhs * rhs.x;
temp.y = lhs * rhs.y;
return temp;
}
public:
… // idem exemple précédent
private:
… // idem exemple précédent
};
59
friend
class A;
class B {
▪ L’amitié n’est pas réservée aux opérateurs int n;
public:
▪ On peut déclarer comme amie d’une classe B(int n) : n(n) {}
▪ une fonction void changer(const A& a);
};
▪ une fonction membre d’une autre
class A {
classe (voir exemple ci-contre) friend void B::changer(const A& a);
int n;
▪ une autre classe dans son ensemble public:
A(int n) : n(n) {}
▪ Les fonctions ou classes peuvent être les };
amies de plusieurs classes
void B::changer(const A& a) {
n = a.n; // a.n est un membre
// privé de la classe A
}
60
Commutativité
▪ L’exemple précédent a
class CVector {
permis d’introduire les friend CVector operator*(double lhs, const CVector& rhs);
notions de surcharge public:
d’opérateur par fonction amie CVector operator*(double d) const;
… // idem exemples précédents
▪ Mais la solution obtenue };
n’est pas satisfaisante :
CVector operator*(double lhs, const CVector& rhs) {
il y a duplication de code return rhs * lhs;
entre les deux versions }
d’operator*
CVector CVector::operator*(double d) const {
▪ Pour un opérateur CVector temp;
commutatif, il est plus propre temp.x = x * d;
temp.y = y * d;
qu’une version de l’opérateur return temp;
appelle l’autre }
61
62
operator<<
▪ La surcharge de cet opérateur nous permet d’écrire le client suivant
int main() {
CVector v1(3, 1);
const CVector V2(1, 2);
cout << v1 << endl
<< V2 << endl
<< v1 + V2 << endl;
}
63
operator<<
▪ La surcharge de cet opérateur nous permet d’écrire le client suivant
int main() {
CVector v1(3, 1);
const CVector V2(1, 2);
cout << v1 << endl
<< V2 << endl 3,1
<< v1 + V2 << endl; 1,2
} 4,3
▪ Passer le deuxième paramètre en référence constante (const CVector&
rhs) permet d’afficher tant la variable v1 que la constante V2 ou l’expression
v1 + V2
▪ Retourner la référence au flux permet d’enchaîner les <<
63
▪ Pour s’assurer que les deux opérateurs sont cohérents, on implémente typiquement
l’opérateur binaire en utilisant l’opérateur d’affectation composée
▪ L’opérateur d’affectation retourne typiquement une référence vers l’objet qu’il affecte,
alors que l’opérateur binaire retourne typiquement l’objet par valeur
64
public:
X& operator+=(const X& rhs) {
// ici modifier les données en y ajoutant rhs
return *this;
}
};
65
Précisions sur la forme canonique
▪ Surcharge de + grâce à += class X {
▪ pour ne pas dupliquer le code (surtout s’il // …
est complexe) friend X operator+(X lhs, const X& rhs) {
▪ ce serait faux (et inutile) de passer lhs par lhs += rhs; // appel à +=
référence, car la somme doit être un return lhs;
nouvel objet }
▪ pourrait aussi être implémentée en tant
que fonction membre constante, mais en public:
tant que fonction amie permet de traiter lhs X& operator+=(const X& rhs) {
et rhs de manière symétrique // ici modifier les données
// en y ajoutant rhs
▪ Surcharge de += comme fonction membre return *this;
pour retourner une référence à l’objet (X&) }
▪ (a += 2) += 3; // possible }; // À UTILISER DONC COMME MODÈLE !
▪ X& n’est pas const car il doit être modifié
(affecté)
▪ permet certaines optimisations …
66
Opérateurs relationnels
friend bool operator==(const X& lhs, const X& rhs) {/* comparaison == à écrire ici */}
friend bool operator!=(const X& lhs, const X& rhs) {return !(lhs == rhs);}
67
operator++
▪ Pour surcharger l’opérateur ++ (ou --), il faut écrire deux fonctions. L’une
pour l’opérateur préfixe, l’autre pour le postfixe. Comment les distinguer ?
class A {
▪ L’opérateur préfixe ne prend // …
pas de paramètre et retourne public:
A& operator++() { // préfixe
une référence vers l’objet lui- // incrémenter les données
même return *this;
}
▪ L’opérateur postfixe prend
formellement un paramètre A operator++(int) { // postfixe
A temp = *this;
entier (dont la valeur n’est pas // incrémenter les données
utilisée) et retourne une copie return temp;
de l’objet avant incrémentation }
};
68
operator++
69
70
Autres opérateurs
71
Classe foncteur
72
73
Utilité des foncteurs
▪ On veut compter le nombre d’entiers pairs et le nombre d’entiers multiples de 3 dans
un vecteur avec std::count_if de la librairie <algorithm>
▪ Pour cela, il faut écrire 2 fonctions : est_pair et est_multiple_de_3
bool est_pair(int i) {return (i % 2) == 0;}
bool est_multiple_de_3(int i) {return (i % 3) == 0;}
int main() {
vector<int> v{1, 4, 5, 2, 9, 5, 6, 8};
cout << count_if(v.begin(), v.end(), est_pair) << endl; // 4
cout << count_if(v.begin(), v.end(), est_multiple_de_3) << endl; // 2
▪ Pour} éviter de dupliquer du code, on voudrait écrire :
▪ Maisbool est_multiple_de_n(int
est_multiple_de_n i, d’arguments
a trop int n) {return (i être
pour % n) utilisée
== 0;} par std::count_if
74
Utilité des foncteurs
int main() {
vector<int> v{1, 4, 5, 2, 9, 5, 6, 8};
cout << count_if(v.begin(), v.end(), Est_multiple_de(2)) << endl; // 4
cout << count_if(v.begin(), v.end(), Est_multiple_de(3)) << endl; // 2
}
75
6. Membres constants
et
membres statiques
76
Membres constants
▪ Un membre d’une classe peut être déclaré const. Il ne peut donc pas subir
d’affectation après son initialisation.
▪ La valeur initiale peut éventuellement varier d’un objet à l’autre d’une même
classe. L’initialisation se fait donc lors de la construction de l’objet, via la liste
d’initialisation du constructeur :
class V {
public:
V(int c) : cste(c) {};
private:
const int cste;
};
V v(3);
77
Membres constants
▪ C’est une valeur par défaut : elle est utilisée pour les objets créés avec des
constructeurs qui n’initialisent pas explicitement la constante dans leurs listes
d’initialisation
class V {
public:
V(int n) : n(n) {};
private:
int n;
const int cste = 1;
};
Membres statiques
79
Membres statiques
▪ Contrairement à une variable statique locale déclarée dans le corps de sa fonction, un
membre statique d’une classe est déclaré dans la déclaration de la classe
▪ Dans un contexte de compilation séparée, class V {
sa déclaration est donc potentiellement static int n;
incluse dans plusieurs fichiers source. public:
double x, y;
On ne peut donc initialiser ce membre à static int m;
l’endroit de sa déclaration. // ...
};
▪ On l’initialise donc explicitement à
l’extérieur de la déclaration, typiquement int V::n = 1; On ne répète pas le
mot static dans la
avec les définitions des fonctions int V::m = 2;
partie définition
membres
❖ Attention, il n’y a pas d’initialisation à zéro par défaut
❖ Un membre static constant peut être initialisé lors de sa déclaration mais uniquement s'il est
de type entier (char, short, int, …)
80
Fonctions membres statiques
81
7. Membres particuliers
82
Membres particuliers
Destructeur ~C();
83
Constructeur par défaut (rappel)
C c;
84
C c1(100);
C c2; // No matching constructor for initialization of 'C'
85
Destructeur
▪ Le destructeur est une fonction sans type de retour, sans paramètre, de même nom
que la classe mais précédée d’un tilde : ~
~C() {}
86
Constructeur de copie
▪ Le constructeur de copie est un constructeur dont le seul paramètre est un objet du
même type.
Sa signature est typiquement C(const C&);
87
Opérateur d’affectation
88
Déplacement
▪ Pour être complet, notons que depuis C++11, il est également possible de définir des
constructeurs et opérateurs de déplacement, notés
C(C&&); // move-constructor
C& operator=(C&&); // move-assignment
▪ Il s’agit d’une optimisation permettant d’éviter d’effectuer une copie inutile d’un objet
temporaire – valeur de retour d’une fonction, résultat d’une conversion de type, etc. –
qui disparaîtrait juste après. Au lieu de cela, on déplace (move) tous ses membres.
L'étude des constructeurs et opérateurs de déplacement sort du cadre de PRG1.
Ceci sera étudié dans le cours ASD.
89
8. Résumé
90
Résumé
91
Résumé
92