Vous êtes sur la page 1sur 109

Programmation Orientée Objet

(II 1)

Chapitre 3 – Classes C++


Plan
• Déclaration, Définition et Utilisation d’une classe
• Structure du programme
• Protection et visibilité des données
• Constructeur & Destructeur
• Affectation d’objets
• Clonage d’objets : Constructeur de recopie
• Membres statiques
• Généricité

2
Déclaration d’une classe
• Partie interface de la classe : regroupe la déclaration des attributs et les prototypes
des méthodes publiques de la classe.
• Se fait dans un fichier en-tête d’extension .h qui porte le nom de la classe et qui se
présente sous la forme suivante :
class Nom_classe
{
public:
//Déclarations des attributs et des méthodes publiques
private:
//Déclarations des attributs et des méthodes privées
};

On peut mettre protected (en cas d’héritage, à voir plus tard)

3
Définition d’une classe
• Partie implémentation de la classe : contient les définitions complètes des méthodes
de la classe (corps des fonctions).
• Se fait dans un fichier source d’extension .cpp qui porte le nom de la classe et qui se
présente sous la forme suivante :
On écrit #include<fichier.h>
pour les fichier standards et
#include "Nom_classe.h" #include "fichier.h"
pour les fichiers écrits par le
programmeur
// pour chaque méthode

type_retour Nom_Classe::nom_methode(paramètres)
{
// corps de la méthode
}
Opérateur résolution de portée::
La méthode nom_methode de la
classe nom_classe

4
Structure du programme
• Généralement en C++, les programmes seront organisés de la manière suivante :
• Pour chaque classe :
• un fichier .h contenant sa déclaration (attributs et prototypes des méthodes)
• un fichier .cpp contenant sa définition (implémentation des méthodes)
• un fichier .cpp contenant le traitement principal (fonction main)

5
Structure du programme

6
Exemple : Une première manière d’organiser le programme
Fichier Point.h
Fichier Point.cpp

Fichier Main.cpp

7
Exemple : Une deuxième manière d’organiser le programme
Fichier Main.cpp

Suite Fichier Main.cpp

8
Exemple : Une troisième manière d’organiser le programme
Fichier Main.cpp

Suite Fichier Main.cpp

9
Visibilité des données
• En général, on cherche à respecter le principe d'encapsulation des données, quitte à
prévoir des fonctions membres appropriées pour y accéder (accesseurs).
class point
{
// déclaration des membres privés
private :
int x ; int y ;
// déclaration des membres publics
public :
void initialise (int, int) ; void affiche () ;
private :
void deplace (int, int) ;
} ;
void main()
{
Point p;
p.initialise(5,2);
p.x = 0; //error: 'int Point::x' is private within this context
p.deplace(3,6); //error: 'int Point::deplace' is private within this context
}
10
Visibilité des données
• Les mots clés public et private peuvent apparaitre à plusieurs reprises dans
la définition d'une classe, comme dans cet exemple :
class X
{ private :
...
public :
...
private :
...
} ;
• Si aucun de ces deux mots n'apparait au début de la définition, tout se passe
comme si private y avait été placé, c’est le niveau de visibilité par défaut en
C++.

11
Constructeur/
Destructeur

12
Constructeur
• Méthode spéciale appelée automatiquement à chaque création d’un objet et qui
permet de l’initialiser.
• Le constructeur porte toujours le même nom que la classe.
• Il ne retourne rien (pas de mention d’un type de retour, même pas void).
• Il peut prendre plusieurs paramètres en fonction des attributs à initialiser.
• Une classe peut avoir plusieurs constructeurs à condition que les règles de surcharge
soient respectées.

13
Exemple

14
Constructeur par défaut
• Lorsqu'une classe ne comporte pas de constructeur, le compilateur génère un
constructeur par défaut qui ne prend aucun paramètre.
class Point int main()
{ {
private: Point a;
int x; a.afficher () ;
int y; }
void initialiser(int abs, int ord)  Le programme affiche:
{ Coordonnées du point : (0,0)
x = abs ;
y = ord ;
}
void afficher ()
{
cout << "Coordonnées du point : ("
<< x << ", " << y << ")" << endl ;
}
};

• Point a;  J’appelle le constructeur par défaut (On n’écrit pas Point a(); )
• Ce constructeur initialise les paramètres à leurs valeurs par défaut (des Zeros ici)

15
Constructeur par défaut
• A partir du moment où une classe possède un ou plusieurs constructeurs, il n’est plus
possible de faire appel au constructeur par défaut.
class Point int main()
{ {
private: Point a;
int x; }
int y;  error: no matching function
… for call to 'Point::Point()'
Point(int n){ Point a;
x = n; y = n; ^
} note: candidate:
Point::Point(int)
};

• Si on veut vraiment un constructeur sans argument dans cet exemple, il faut qu’on
l’ajoute explicitement:
Point(); //déclaration
Point::Point() // Définition
{ x = 0; y = 0; }
Point p; //Appel

16
Constructeur
• Les initialisations des attributs peuvent se faire par affectations dans le corps du
constructeur (tel que défini précédemment) ou bien grâce aux listes d’initialisations.
• La syntaxe d’une liste d’initialisation est la suivante :
attribut_1(valeur_1), ... , attribut_n(valeur_n)
• Elle se place juste après l’en-tête du constructeur.
• Exemple :
Point::Point(int n, int p) : x(n), y(p)
{
//autres instructions
}

17
Constructeur
• Un objet peut comporter aussi bien des attributs statiques que des attributs
dynamiques.
• Dans ce cas, le rôle du constructeur ne se limite pas uniquement à l’initialisation des
attributs statiques, mais doit aussi prendre en charge l’allocation de la mémoire pour
les attributs dynamiques.

18
Exercice class Complexe
• Ecrire la classe Complexe caractérisé par :
• Deux attributs privés: partie réelle et partie imaginaire (de type float)
• Un constructeur sans paramètres qui met les attributs à 0
• Un constructeur paramétré pour initialiser les attributs
• Les méthodes suivantes :
• Les getters
• Les setters
• lis : pour lire les attributs entrés au clavier
• affiche : afficher les attributs de la classe
• ajouter : renvoie un nombre complexe qui est la somme de l’objet courant et un objet passé en
paramètres.

• Ecrire la fonction main pour tester votre classe permettant de :


• Construire un complexe : z1 d’attributs (0,1)
• Mettre à 5 la partie imaginaire de z1
• Construire un complexe z2 d’attributs nuls.
• Afficher z1
• Lire au clavier les attributs de z2
• Construire z3=z1+z2
• Afficher z3

19
Construction d’objets
• La construction d’un objet se fait par appel au constructeur. Elle peut se faire de
plusieurs manières selon le type de l’objet à créer.
• Il existe différents types d’objets :
• Les objets statiques
• Les objets automatiques
• Les objets dynamiques

20
Construction d’objets
Objets statiques
• Ils sont déclarés globalement, en dehors de toute fonction ou
bloc d’instructions.
• A leur création, leurs données membres (attributs) sont
automatiquement initialisées à des valeurs nulles.
• Les objets statiques sont créés avant le début de l’exécution de
la fonction main() et détruits après la fin de son exécution.

• Syntaxe : NomClasse nomObjet;


ou bien : NomClasse nomObjet(arguments);

21
Construction d’objets
Objets automatiques
• Ils sont déclarés localement à l’intérieur d’une fonction ou d’un
bloc d’instructions.
• A leur création, leurs données membres (attributs) ne sont pas
initialisées sauf en cas d’appel d’un constructeur défini à cet
effet.
• Les objets automatiques sont créés à la rencontre de leur
déclaration et détruits à la fin de l’exécution de la fonction ou à
la sortie du bloc où ils sont déclarés.

• Syntaxe : NomClasse nomObjet;


ou bien : NomClasse nomObjet(arguments);

22
Construction d’objets

appel à Point()

appel à Point(int, int)


appel à Point(int)

appel à Point(int, int)

23
Construction d’objets
Objets dynamiques
• Ils sont alloués dynamiquement et accessibles à travers un
pointeur (comme toute autre variable dynamique).
• A leur création, leurs données membres (attributs) ne sont pas
initialisées sauf en cas d’appel d’un constructeur défini à cet
effet.
• Les objets dynamiques sont créés par appel à l’opérateur new et
doivent être détruits par appel explicite à l’opérateur delete.

• Syntaxe : NomClasse* nomObjet = new NomClasse;


ou bien : NomClasse* nomObjet = new NomClasse(arguments);

24
Construction d’objets
Objets dynamiques (suite)
• L’accès aux membres d’un objet dynamique ne se fait plus par .
mais par -> comme suit : nomObjet->membre

Le destructeur de Point est exécuté

25
Exemple

26
Exemple

27
Destructeur
• Méthode spéciale appelée automatiquement à chaque
destruction d’un objet et qui permet d’effectuer les opérations
de nettoyage de l’espace mémoire occupé par les membres de
l’objet.
• Le destructeur porte le même nom que la classe précédée du
symbole ~.
• Il ne retourne rien (pas de mention d’un type de retour, même
pas void) et ne prend pas de paramètres.
• Une classe ne peut avoir qu’un seul destructeur.
• Lorsqu'une classe ne comporte pas de destructeur, le
compilateur génère un destructeur par défaut.
• L’appel du destructeur se fait automatiquement et pas
explicitement.

28
Destructeur
• Un objet peut comporter aussi bien des attributs statiques que
des attributs dynamiques.
• La définition explicite d’un destructeur n’est pas obligatoire
lorsque tous les attributs de l’objet sont statiques.
• Elle devient indispensable en cas de présence d’attributs
dynamiques. La libération de ces derniers doit apparaitre
explicitement dans le corps du destructeur (utilisation de
l’opérateur delete).

29
Destructeur

30
Destructeur
• La destruction des objets automatiques se fait
automatiquement à la sortie de la fonction ou du bloc dans
lequel ils ont été créés.
• La destruction des objets statiques se fait automatiquement à
la fin de l’exécution du programme principal.
• La destruction des objets dynamiques doit se faire
explicitement avec l’appel à l’operateur delete.

31
Destructeur

32
Affectation
d’Objets

33
Affectation d’objets
Cas des objets statiques et automatiques
• Soit la déclaration suivante :
• Point a, b; //a et b sont deux objets de Point
• L’instruction b = a ; provoque la recopie des valeurs des
attributs de a dans les attributs correspondants de b.
• Elle est équivalente aux instructions suivantes qui sont quant à
elles interdites lorsque les attributs de la classe Point sont
privés donc inaccessibles.
b.x = a.x ;
b.y = a.y ;

34
Affectation d’objets
Cas des objets statiques et automatiques
• Remarques
• L’affectation b = a est toujours légale, quel que soit le statut
(public ou prive) des attributs de a et de b. On peut considérer
qu’elle ne viole pas le principe d’encapsulation, dans la mesure
où les données privées de b restent toujours inaccessibles de
manière directe.
• Attention: Une simple recopie à l’aide de l’opérateur = s’avère
insuffisante lorsque l’objet comporte des pointeurs sur des
emplacements dynamiques, dans ce cas la recopie ne
concernera pas cette partie de l’objet, elle sera "superficielle"
(copie de l’adresse contenue dans le pointeur uniquement).

35
Affectation d’objets

36
Affectation d’objets
Cas des objets dynamiques
• Soit les déclarations suivantes :
• Point* a = new Point;
• Point* b; //a et b sont deux objets dynamiques issus de
Point
• L’instruction b = a; fait pointer b sur le même objet pointé par
a.

37
Constructeur
de recopie

38
Clonage d’objets : Constructeur de recopie
• L’opération d’affectation d’objets s’est avérée insuffisante pour
effectuer convenablement la copie d’un objet.
 Solution : Constructeur de recopie
• Le constructeur de recopie est un constructeur spécial qui
permet de créer un objet à partir d’un autre objet (un clone).
• Le constructeur de recopie doit prévoir d’effectuer :
• Les allocations de mémoire nécessaires pour les attributs
dynamiques
• Les copies des valeurs des attributs dynamiques.
• Lorsqu'une classe ne comporte pas de constructeur de recopie,
le compilateur en génère un par défaut mais qui effectue une
copie superficielle d’où la nécessite de définir explicitement
pour chaque classe un constructeur de recopie.

39
Constructeur de recopie
• Déclaration :
nomClasse(nomClasse&);
• Definition :
nomClasse::nomClasse(nomClasse& obj) : ...
{
//opérations d’allocation de la mémoire
//opérations de copie des attributs dynamiques
}
• Utilisation :
nomClasse nomClone = nomObjet; Attention !
nomClasse nomClone(nomObjet); Ne pas confondre avec une affectation
d’objets, il s’agit d’une initialisation qui
provoque l’appel automatique du
constructeur de recopie.
Objeta=objetb;  c’est une affectation d’objets,
n’appelle pas le constructeur de recopie.
40
Constructeur de recopie

41
Constructeur de recopie

42
Constructeur de recopie
• Le rôle du constructeur de recopie ne se limite pas uniquement à la création
de copies d’un même objet.
• On distingue deux autres cas où l’existence d’un constructeur de recopie est
indispensable :
• Passage par valeur d’un objet à une fonction
type function(Object a);  appeler le constructeur de recopie de Object
pour construire a et le passer à function
• Retour par valeur d’un objet par une fonction
Object a = function();  appeler le constructeur de recopie pour copier
l’objet à retourner

43
Constructeur de recopie
• Ce qui se passe sans constructeur de recopie
class Tab Tab::Tab(int d) : dim(d)
{ private: { adr = new int[dim];
int dim; cout << "Construction usuelle, adr. objet : " << this
int* adr; << endl;
public: }
Tab(int); Tab::~Tab()
~Tab(); { delete[] adr;
}; cout << "Destruction, adr. objet : " << this
<< " adr. tableau : " << adr << endl;
}

void fct(Tab a)
{
cout << "Appel de fonction" << endl; Résultat de l’exécution
} Construction usuelle, adr. objet : 0x22ff20
int main() Appel de fonction
{ Destruction, adr. objet : 0x22ff10 adr.
Tab t(5); tableau : 0x7c2ef8
fct(t); Destruction, adr. objet : 0x22ff20 adr.
} tableau : 0x7c2ef8

44
Constructeur de recopie
• Ce qui se passe sans constructeur de recopie
• Dans la classe Tab, aucun constructeur de recopie n’a ete defini, le compilateur se
charge alors d’en generer un. Ce dernier effectue une copie superficielle de t dans a
lors de l’appel de la fonction fct() (copie du parametre effectif dans le parametre
formel)

• A la sortie de la fonction fct(), le paramètre formel a est détruit, ce qui engendre


une destruction de l’attribut dynamique de t et donc engendre une altération de l’objet
t!
• Aussi, à la fin de l’exécution de la fonction main, le destructeur est appelé pour t,
ce qui libère... Le même emplacement.
 Cette tentative constitue une erreur d'exécution dont les conséquences varient avec
l’implémentation.
 Il vaut mieux coder le constructeur de copie pour éviter ce problème

45
Constructeur de recopie
• Ce qui se passe avec constructeur de recopie
class Tab Tab::Tab(int d) : dim(d)
{ private: { ... }
int dim; Tab::Tab(Tab& t) : dim(t.dim)
int* adr; { adr = new int[dim];
public: for (int i=0;i<dim;i++) *(adr+i) = *(t.adr+i);
Tab(int); cout << "Construction recopie, adr. objet : "
Tab(Tab&); << this << endl; }
~Tab(); Tab::~Tab()
}; { ... }

void fct(Tab a)
{
cout << "Appel de fonction" << endl; Résultat de l’exécution
} Construction usuelle, adr. objet : 0x22ff20
int main() Construction recopie, adr. objet : 0x22ff10
{ Appel de fonction
Tab t(5); Destruction, adr. objet : 0x22ff10 adr.
fct(t); tableau : 0x2d2f18
} Destruction, adr. objet : 0x22ff20 adr.
tableau : 0x2d2ef8

46
Constructeur de recopie
• Ce qui se passe avec constructeur de recopie
• Dans la classe Tab, le constructeur de recopie a été explicitement défini. Ce dernier
effectue une copie profonde de t dans a lors de l’appel de la fonction fct() (copie du
paramètre effectif dans le paramètre formel)

• A la sortie de la fonction fct(), le paramètre formel a est détruit, ce qui est sans effet
sur le paramètre effectif t !

47
Quelques
remarques sur les
constructeurs

48
Le pointeur "this"

class A
{public :
int f();
void f(int);
private : void f(int i)
int i; {
}; this->i = i;
}
this peut être interprété comme un identifiant
de l'objet. Il fait toujours référence à
l'instance pour laquelle la méthode est
invoqué.
15
this …
….

49
Constructeur ayant 1 seul paramètre
• Déclaration d’un constructeur ayant un unique paramètre
et l’utiliser pour faire la conversion.
• Ici, conversion de int vers A

Equivalent à: A a1(37): A avec attribut = 37

A avec attribut = 47

A avec attribut = 57

A avec attribut = 67

A avec attribut = 77 est passé à f

50
Constructeur ayant 1 seul paramètre
 Il faut que le type de l’objet source soit le même que le
type du premier argument de la classe
A(int);
char * s;
A a1 = s; (error: invalid conversion from 'char*' to 'int‘)

 Si on ajoute explicit: interdiction de la conversion


implicite
• explicit A(int i){};
• A a1 = 56; //error: conversion from 'int' to non-
scalar type ‘A' requested

• A a2 = (A) 57; //ok , conversion explicite forcée


• A a1(56); //ok

51
Attribut objet
class Constr{
class A{
private:
private:
int s;
int i;
double d;
public:
A a;
int j = 0;
public :
A(){j=90;};
void print(){
A(int x){i=x;}
cout << "s = " << s << " d
~A() {};
= " << d << " a.j = " << a.j<<
int f()
endl;
{
}
return i;
};
}
int main(){
};
Constr co;//appel implicite à
Constr()qui appelle A() pour
initialiser son 3eme attribut
co.print();
}
Ce programme affiche:
s = -2145069216 d = 2.12199e-314 a.j=90
(appel a A())
52
Attribut objet
• Pour construire objC, il faut construire ses deux attributs objA et objB 
appel des constructeurs par défaut A::A() et B::B()
• Erreur si ces constructeurs n’existent pas.

class A {
int i;
public: class C {
A(): i(10){} A objA;
B objB;
}; };


C objC;
class B {
int j;
public:
B(int x): j(x){}  ABCD.cpp:106:7: error: no
matching function for call to
}; 'B::B()'
ABCD.cpp:104:14: note:
candidate: B::B(int)
public : B(int x) : j(x) {
} ^~
53
Attributs et
méthodes
statiques

54
Attributs statiques (static)
• Généralement, lorsqu’on crée différents objets à partir d’une même classe, chaque
objet possède ses propres attributs

class maClasse
{
int n;
float x;
...
};
...
maClasse a, b;

55
Attributs statiques (static)
• Un attribut statique est un attribut partagé entre toutes les instances d’une classe.
Un tel attribut est déclaré en utilisant le qualificatif static.
• Exemple :
class maClasse
{
static int n;
float x;
...
};
...
maClasse a, b;
• Si un objet modifie l’attribut statique, tous les autres objets ont instantanément
accès a la nouvelle valeur de cet attribut.

56
Attributs statiques (static)
• Un attribut statique existe en un unique exemplaire indépendamment des objets de
la classe (même si aucun objet n’a été instancié) : attribut de classe.
• Un attribut statique doit être initialisé explicitement à l'extérieur de la déclaration de
la classe en utilisant l’opérateur de résolution de portée ::
• Exemple :
class maClasse
{
static int n;
float x;
...
};
int maClasse::n = 5; //initialisation de l’attribut statique (en dehors de la classe)
Utilisation:
• cout << m.n << endl; //accès via l’instance m de la classe
• cout << maClasse::n << endl; //accès via le nom de la classe,
préférée (la variable n ne dépend d’aucune instance)
57
Attributs statiques (static)
Des exemples qui ne marchent pas:
• Interdit au niveau de la déclaration des attributs (ISO C++ forbids in-class
initialization of non-const static member 'C::s‘)
• Pas de sens de mettre l’initialisation dans le constructeur (la variable statique est
indépendante de l’instantiation de la classe)

class maClasse class maClasse


{ {
static int s = 6; // NOT OK static int s
//error: ISO C++ forbids in-class float x;
initialization of non-const static int j = 88; //membre non
member 'maClasse::s' static int s = 6; statique
float x; public:
int j = 88; //OK, membre non
statique maClasse(){s=9;} //undefined
public: reference to `maClasse::s'
…. …

}; //Erreur à la compilation }; //Erreur d’exécution 58


Attributs statiques (static)
Un exemple qui fonctionne: Il faut définir les variables d’instance en dehors de la
définition de classe)

#include <iostream>
using namespace std;
class maClasse
{
public:
static int s;
static string ch;
float x;
int j = 88;
public:
maClasse(){s=9;}
void print(){ cout<<" s= " << s << ", ch=" << ch ;}
};

int maClasse::s;
string maClasse::ch = "Hi";

int main()
{
maClasse m;
m.print();  9, Hi
}
59
Attributs statiques (static)
Qu’affiche ce programme?

#include <iostream>
using namespace std;
class maClasse
{
static int s;
static string ch;
public:
maClasse(){s=9;}
void print(){ cout<<" s= " << s << ", ch=" << ch ;}
void setS(int d){ s = d;}
};

int maClasse::s;
string maClasse::ch = "Hi";

int main()
{
maClasse m1, m2;
m1.print();
m2.print();
m1.setS(5);
m1.print();
m2.print();
return 0;} 60
Attributs statiques (static)
Qu’affiche ce programme?

#include <iostream>
using namespace std;
class maClasse
{
static int s;
static string ch;
public:
maClasse(){s=9;}
void print(){ cout<<" s= " << s << ", ch=" << ch ;}
void setS(int d){ s = d;}
}

int maClasse::s;
string maClasse::ch = "Hi"; s= 9, ch=Hi
s= 9, ch=Hi
int main()
{ s= 5, ch=Hi
maClasse m1, m2; s= 5, ch=Hi
m1.print();
m2.print();
m1.setS(5);
m1.print();
m2.print();
return 0;} 61
Attributs statiques (static)
• Ecrire une classe Compteur qui permet de calculer le nombre d’instances de la
classe Compteur existant dans le programme à tout moment.
Fichier Compteur.h

62
Attributs statiques (static)
• Ecrire une classe Compteur qui permet de calculer le nombre d’instances de la
classe Compteur existant dans le programme à tout moment.
Fichier Compteur.cpp

63
Attributs statiques (static)
• Ecrire une classe Compteur qui permet de calculer le nombre d’instances de la
classe Compteur existant dans le programme à tout moment.

64
Attributs statiques (static)
• Ecrire une classe Compteur qui permet de calculer le nombre d’instances de la
classe Compteur existant dans le programme à tout moment.

65
Méthodes statiques (static)
• Une méthode statique est une méthode appartenant à une
classe et définie indépendamment de toute instanciation de
cette dernière.
• Une méthode statique est déclarée en utilisant le qualificatif
static.
• Ex. static void fonction();
• Elle ne peut manipuler que des attributs statiques (définis avec
le mot clé static)
• Une méthode statique peut être appelée indépendamment des
objets de la classe (même si aucun objet n’a été instancié) :
méthode de classe.
• Dans ce cas, elle est appelée en utilisant le nom de la classe à
laquelle elle appartient et l’opérateur de résolution de portée ::
(accès direct sans passer par une instance).
66
Méthodes statiques (static)
• Exemple
• Ajouter a la classe Point définie précédemment, une méthode permettant d’afficher
le nombre de ses instances existantes.

Point::Point(int n, int p) : x(n), y(p)


{ cpt++; }
class Point
Point::~Point()
{
{ cpt--; }
private:
void Point::compter()
static int cpt;
{ cout<<"nombre d’objets:"<<cpt<<endl; }
int x, y;
int main()
public:
{
Point(int, int);
Point::compter(); // 0
~Point();
Point a(1,6);
static void compter();
a.compter(); // 1
};
Point b(3,5);
int Point::cpt = 0;
Point::compter(); // 2
}

67
Fonctions amies

68
Fonctions amies
• POO encapsulation des données : attributs privés accessibles
uniquement à travers les méthodes publiques de la même
classe.
• Dans certains cas, une méthode non-membre d’un classe B a
besoin d’accéder aux attributs de cette classe A. Solutions
possibles :
• Rendre les attributs publics   compromettre leur
protection
• Passer par des méthodes d’accès   perdre en temps
d’exécution
• Solution intéressante : notion de fonction amie : Lors de la
définition d'une classe, il est possible de déclarer qu'une ou
plusieurs fonctions extérieures à la classe sont des "amies" ce
qui les autorise à accéder aux attributs privés au même titre que
n'importe quelle fonction membre.
69
Fonctions amies
• Pour déclarer une fonction amie d’une classe il suffit d’insérer son prototype
dans la définition de la classe en le faisant précéder par le mot clé friend.
class maClasse
{ // Attributs
public:
// Méthodes
friend type_retour nom_fonction(arguments);
}
• Plusieurs situations d’« amitié » :
1. Une fonction indépendante, amie d’une classe
2. Une méthode d’une classe, amie d’une autre classe
3. Une fonction amie de plusieurs classes
4. Toutes les méthodes d’une classe amies d’une autre classe

70
Fonctions amies
Une fonction indépendante, amie d’une classe
class point {
private:
int x,y;
public:
point (int abs=0, int ord=0) {
x=abs; y=ord;}
friend int coincide(point, point);
};

int coincide(point p, point q) {


if ((p.x == q.x) && (p.y == q.y)) return 1;
else return 0;
}

int main(void) {
point a(1,0), b(1), c;
if (coincide (a,b))
cout<<"a coincide avec b"<<endl;
else cout << "a et b ne coincident pas"<<endl;
return 0;
} - La fonction « coincide» est une fonction amie de
la classe « point».
- Déclarer la fonction « coincide » « private » ou «
public » importe peu
71
Fonctions amies
Une méthode d’une classe, amie d’une autre classe(1/2)

#include<iostream>
using namespace std;
// déclaration de la classe A, le compilateur sait que la classe A
existe.
class A;

// définition de la classe B.
class B {
public:
void exemple (A&);
};

• Remarque: La fonction exemple est uniquement déclarée. Comme


argument, elle prend une référence à un objet de la classe A. On ne
peut pas définir la fonction exemple à ce niveau, car si elle utilise
un membre donné de la classe A, sachant que A n'a pas été encore
définie, on aura des problèmes à la compilation.

72
Fonctions amies
Une méthode d’une classe, amie d’une autre classe(2/2)
// définition de la classe A
class A {
int i;
friend void B::exemple (A&);
/* On connaît déjà le contenu de la classe B, exemple est une
fonction membre de cette classe, elle est déclarée aussi, à ce
niveau, comme amie de la classe A. Elle aura donc accès aux données
privées de la classe A. */
public:
A(int e=100):i(e) {} // constructeur qui initialise i à
la valeur de e, par défaut 100.
};

// A et B étant définies, on peut définir la fonction exemple de la


classe B.

void B::exemple(A& a) { cout << a.i << endl; }

int main() {
A x; B y;
y.exemple(x);
return 0;
} // en sortie: 100
73
Fonctions amies
Fonction amie de plusieurs classes:
int main(void) {
class BB; AA aa;
class AA { BB bb;
int i; f(aa, bb);
friend void f (AA, BB); return 0;
public: }
AA(int e=500):i(e) {}
};

class BB {
int j;
public:
inside f, a.i = 500,
BB(int e=5):j(e) {}
friend void f (AA,BB); b.j=5
};

void f(AA a, BB b){


cout << "inside f, a.i = " <<a.i << ",
b.j=" << b.j << endl;
}

74
Fonctions amies
Toutes les méthodes d’une classe amies d’une autre classe (classe amie d’une autre classe)
(1/2)

#include<iostream>
using namespace std;
// déclaration de la classe A.
class A;

// définition de la classe B.
class B {
public:
void change (A&);
void affiche(A&);
};

class A {
int i;
/* La classe B est amie la classe A. De ce fait, les fonctions
membres de la classe B c.-à-d. (change & affiche) sont amies de la
classe A. */
friend class B;
public:
A(int e=100):i(e) {} // constructeur de A.
};

75
Fonctions amies
Toutes les méthodes d’une classe amies d’une autre classe (classe amie d’une autre classe)
(2/2)

// définition de change, fonction membre de la classe B.


void B::change(A& a) {
a.i = 200;
}

// définition de affiche, fonction membre de la classe B.


void B::affiche(A& a) {
cout << a.i << endl;
}

int main() {
A xx;
B yy;
yy.affiche(xx); // en sortie: 100
yy.change(xx);
yy.affiche(xx); // en sortie: 200
return 0;
}

76
Surcharge des
opérateurs

77
Surcharge des opérateurs
• En plus de la possibilité de surcharge des fonctions, C++ permet
de surcharger des opérateurs (+, *, /, ++, --, =, +=, new, [], …).
• Surcharger un opérateur : lui permettre d’être appliqué et de se
comporter différemment avec divers types d’opérandes.
• Exemple : l’opérateur + est déjà surchargé en C++ puisque dans
une expression telle que a + b le symbole + peut designer,
suivant le type de a et b :
• l'addition de deux entiers
• l’addition de deux réels (float)
• l’addition de deux réels double précision (double)
• etc.

78
Surcharge des opérateurs
• Il est possible de surcharger encore l’opérateur + pour qu’il soit
applicable à des objets issus de classes définies par le
programmeur.
• Avantage : permettre de créer à travers des classes de
nouveaux types munis d’opérateurs comme les types de base.
• Exemple : Définir une classe "Complexe" et pouvoir donner une
signification à des expressions telles que : a+b, a-b, a*b, a/b,
…où a et b sont des objets
• Remarque : la plupart des opérateurs en C++ peuvent être
surchargés à l’exception de:
• :: (résolution de portée)
• . (accès à un membre)
• : ? (operateur conditionnel)
• sizeof (taille en octets)

79
Surcharge des opérateurs
• Pour surcharger un opérateur en C++, on doit définir une
fonction nommée operator suivie du symbole de l’opérateur à
surcharger.
• Cette fonction peut être :
• Une méthode de la classe à laquelle s’applique l’opérateur
• Une fonction indépendante amie avec cette classe
• Exemple : pour surcharger l’opérateur + et l’appliquer à des
objets de type Point :

Point Point::operator+(Point) //méthode de la classe Point


Point operator+(Point, Point) //fonction amie de Point

80
Surcharge des opérateurs c=a.operator+(b)

81
Surcharge des opérateurs c=operator+(a,b)

82
Surcharge des opérateurs
Cas des opérateurs ++ et --
• Notation préfixée ++a ≠ Notation postfixée a++
Notation prefixée ++a Notation postfixée a++
int i, j = 5; int i, j = 5;
i = ++j; // i=6 et j=6 i = j++; // i=5 et j=6

• Besoin de définir à la fois un opérateur ++ utilisable en notation


préfixée et un autre utilisable en notation postfixée.
• Pour ce faire, il existe une convention qui consiste à ajouter un
argument fictif supplémentaire à la version postfixée de la
fonction operator++.

83
Surcharge des opérateurs
Cas des opérateurs ++ et --
• Dans le cas où ++ est défini sous la forme d’une fonction
membre :
• La fonction nomClasse operator ++() sera utilisée en cas de
notation préfixée.
• La fonction nomClasse operator ++(int) sera utilisée en cas de
notation postfixée.
• L’argument de type int est totalement fictif, il permet
uniquement au compilateur de choisir l'opérateur à utiliser mais
aucune valeur ne sera réellement transmise lors de l'appel.

84
Surcharge des opérateurs
Cas des opérateurs ++ et --
• Dans le cas où ++ est défini sous forme d’une fonction amie :
• La fonction nomClasse operator ++(nomClasse) sera utilisée
en cas de notation préfixée.
• La fonction nomClasse operator ++(NomClasse, int) sera
utilisée en cas de notation postfixée.
• Dans les 2 cas (méthode de classe ou fonction amie), les mêmes
considérations s'appliquent à l'opérateur --.

85
Surcharge des opérateurs

86
Exercice
Soit une classe vecteur3d définie par trois attributs de type float x, y et z et un
constructeur paramétré pour initialiser les attributs.
Définir les opérateurs == et != de manière qu’ils permettent de tester la
coïncidence ou la non-coïncidence de deux vecteurs (égalité des attributs):
1. en utilisant des fonctions membre;
2. en utilisant des fonctions amies.

87
Exercice

88
Surcharge des opérateurs
Cas de <<:
• Si on veut afficher un objet de classe Point, une première alternative: ajouter une
fonction membre:
void Point::print()
{std::cout << "Point(" << x << ", " << y << ", " << z << ')'; }
Utilisation:
std::cout << "My point is: ";
point.print();
• Si on veut simplifier en écrivant: std::cout << "My point is: " << point;
 Il faut surcharger l’opérateur <<
 Les opérandes sont: std::cout (type=std::ostream) et point(type=Point)

friend std::ostream& operator<< (std::ostream &out, const Point &point);

89
Surcharge des opérateurs
Cas de <<:

90
Exercice
Soit la classe vecteur3d ainsi définie :
class vecteur3d
{
float x, y, z ;
public :
vecteur3d (float c1=0.0, float c2=0.0, float c3=0.0)
{ x = c1 ; y = c2 ; z = c3 ; }
} ;

• Définir l’opérateur binaire + pour qu’il fournisse la somme de deux vecteurs,


et l’opérateur binaire * pour qu’il fournisse le produit scalaire de deux
vecteurs. On choisira ici des fonctions amies.
• Définir l’opérateur << pour l’affichage des coordonnées du vecteur.
• Tester avec une fonction main

91
Exercice

92
Exercice

93
Surcharge des opérateurs
Cas de l’opérateur =
class vect
{
int nelem ; // nombre d'éléments
double * adr ; // pointeur sur ces éléments
public :
vect (int n) // constructeur "usuel"
{ adr = new double [nelem = n] ;
cout << "+ const. usuel - adr objet : " << this
<< " - adr vecteur : " << adr << "\n" ;
}
~vect () // destructeur
{ cout << "- Destr. objet - adr objet : "
<< this << " - adr vecteur : " << adr << "\n" ; delete adr ;
}
} ;
int main()
{ vect a(5) , b(3);
b=a;
5
return 0;
}
+ const. usuel - adr objet : 0xffffcba0 - adr vecteur : 0x6000128b0
+ const. usuel - adr objet : 0xffffcb90 - adr vecteur : 0x6000128e0
- Destr. objet - adr objet : 0xffffcb90 - adr vecteur : 0x6000128b0
- Destr. objet - adr objet : 0xffffcba0 - adr vecteur : 0x6000128b0
RUN FAILED (exit value 1, total time: 13s) 94
Surcharge des opérateurs
Cas de l’opérateur =
• Solution : surcharge de l’operateur d’affectation =
• Démarche : (pour une affectation b = a correcte)
• Libération de la partie dynamique de b;
• Création dynamique d'un nouvel emplacement dans lequel on recopie les
valeurs de l'emplacement pointé par a.
• Copie des valeurs des attributs de b.
• Prise en compte de l’affectation d’un objet à lui-même (a=a).
• Prise en compte des affectations multiples (a=b=c).

95
Surcharge des opérateurs
Cas de l’opérateur =
Passage d’arguments par
référence pour éviter la
copie d’arguments

class vect
{
int nelem ; // nombre d'éléments
double * adr ; // pointeur sur ces éléments
public :
vect (int n) {…} // constructeur "usuel"
~vect () { …} // destructeur
vect & operator = (const vect & v)
{ if (this != &v)
{ cout << " effacement vecteur dynamique en " << adr << "\n" ;
delete adr ;
adr = new double [nelem = v.nelem] ;
cout << " nouveau vecteur dynamique en " << adr << "\n" ;
for (int i=0 ; i<nelem ; i++)
adr[i] = v.adr[i] ;
}
else cout << " on ne fait rien \n" ;
return *this ;
}
};
96
Généricité

97
Généricité
• En C++, il est possible de définir des modèles génériques de fonctions et de
classes qu’on appelle ≪ patron ≫ ou ≪ template ≫
• La notion de généricité permet de définir des modules paramétrés par le
type qu'ils manipulent Un type apparaît comme paramètre dans la
définition d’une classe ou d’une fonction
• Patron (template) de fonctions : modèle de fonctions ou fonction générique
écrire une seule fois la définition d'une fonction qui sera adaptée
automatiquement par le compilateur à n'importe quel type.
• Patron (template) de classes : modèle de classes ou classe générique
écrire une seule fois la définition d’une classe qui sera adaptée
automatiquement par le compilateur à n'importe quel type.

98
Généricité
Patron de fonction
template <class T> T minV (T a, T b) {
return ((a < b)? a : b);
}
• Ainsi elle peut être utilisée indifféremment pour n’importe quel type de données.

99
Généricité
Patron de fonction
#include <iostream>
using namespace std ;
template <class T> T minimum (T a, T b)
{ if (a < b) return a; else return b; }
class Vecteur
{ int x, y;
public:
Vecteur(int abs=0, int ord=0) { x=abs; y=ord; }
void affiche() { cout << x << " " << y; }
int operator<(Vecteur w) { return (x < w.x && y < w.y); } //necessaire parce que
minimum compare des vecteurs, donc il faut surcharger < dans la classe Vecteur
};
int main()
{ Vecteur u(3,2), v(4,1), w;
w = minimum(u,v);
cout << "minimum(u,v) = ";
100
w.affiche() ; }
Généricité
Patron de fonction
• Exercice: Créer un patron de fonctions permettant de calculer le carré d’une valeur
de type quelconque (le résultat possédera le même type). Écrire un petit programme
utilisant ce patron.

101
Généricité
Patron de fonction
• Exercice : Créer un patron de fonctions permettant de calculer le carré d’une valeur
de type quelconque (le résultat possédera le même type). Écrire un petit programme
utilisant ce patron.

#include <iostream>
using namespace std ;

template <class T> T carre (T a)


{ return a * a ;
}
main()
{
int n = 5 ;
float x = 1.5 ;
cout << "carre de " << n << " = " << carre (n) << "\n" ;
cout << "carre de " << x << " = " << carre (x) << "\n" ;
}
102
Généricité
Patron de fonction
• Exercice : Soit cette définition de patron de fonctions :
template <class T, class U> T fct (T a, U b, T c)
{ ..... }
Avec les déclarations suivantes : int n, p, q ; float x ; char t[20] ; char c ;
Quels sont les appels corrects et, dans ce cas, quels sont les prototypes des fonctions
instanciées ?
1. fct (n, p, q) ; // appel I
2. fct (n, x, q) ; // appel II
3. fct (x, n, q) ; // appel III
4. fct (t, n, &c) ; // appel IV

103
Généricité
Patron de fonction
• Exercice : template <class T, class U> T fct (T a, U b, T c)
{ ..... }
int n, p, q ; float x ; char t[20] ; char c ;
• fct (n, p, q) ; // appel I
correct, le compilateur instancie la foction int fct(int, int, int)
• fct (n, x, q) ; // appel II
correct, le compilateur instancie la fonction int fct(int, float, int)
• fct (x, n, q) ; // appel III
incorrect
• fct (t, n, &c) ; // appel IV
correct, le compilateur instancie la fonction char* fct(char*, int, char*)

104
Généricité
Patron de classe
• But: Eviter de définir plusieurs classes similaires pour décrire un même concept
appliqué à plusieurs types de données différents.
template <class T> class NomClasse {
T i;
NomClasse(T , T);
void f(T);
};
• Le definition de la méthode f:
template <class T> void NomClasse<T>::f(T x){
……
}

105
Généricité
Patron de classe

106
Généricité
Patron de classe

107
Généricité
Patron de classe
• Exercice:
1. Créer un patron de classes nommé pointcol, tel que chaque classe instanciée
permette de manipuler des points colorés (deux coordonnées et une couleur) pour
lesquels on puisse « choisir » à la fois le type des coordonnées et celui de la couleur.
On se limitera à deux fonctions membre : un constructeur possédant trois
arguments (sans valeur par défaut) et une fonction affiche affichant les
coordonnées et la couleur d’un « point coloré ».
2. Dans quelles conditions peut-on instancier une classe patron pointcol pour des
paramètres de type classe ?

108
Généricité
Patron de classe
• Q2: Il suffit que le type classe en question ait convenablement surdéfini l’opérateur <<,
afin d’assurer convenablement l’affichage sur cout des informations correspondantes.
template <class T, class U> class pointcol
{
T x, y ; // coordonnees
U coul ; // couleur
public :
pointcol (T abs, T ord, U cl) { x = abs ; y = ord ; coul = cl ;}
void affiche ()
{ cout << "point colore - coordonnees " << x << " " << y << " couleur " << coul << "\n" ; }
};
point colore - coordonnees 5 5 couleur 2
int main(){ point colore - coordonnees 4 6 couleur 2
pointcol <int, short int > p1 (5, 5, 2) ; p1.affiche () ; point colore - coordonnees 1 5 couleur 2

pointcol <float, int> p2 (4, 6, 2) ; p2.affiche () ;


pointcol <double, unsigned short> p3 (1, 5, 2) ; p3.affiche () ;
} 109

Vous aimerez peut-être aussi