Vous êtes sur la page 1sur 36

Université Ibn-Zohr

Ecole Nationale des Sciences Appliquées


Département Génie Informatique

Polycopié du cours
__________________________________
Programmation Orientée Objet
__________________________________

Matière : Programmation Orientée Objet


Module : Structure de données et programmation orienté objet
1ème année du cycle d’ingénieur
Filière Finance et Ingénierie Décisionnelle

Prof : Abderrahmane ELYOUSFI

2021-2022
Table des matières

Chapitre 1 : Présentation du langage C++…..…….…………….2


Chapitre 2 : Classes et objets………..…………………...............10
Chapitre 3 : Les propriétés des fonctions membres……………17
Chapitre 4 : Construction, destruction et initialisation des
objets………………………………………………..22
Chapitre 5 : Le constructeur de recopie………….….…….…...25
Chapitre 6 : Les fonctions amies…………………..….………....28
Chapitre 7 : L’heritage…………………….………….…………31

1
Chapitre 1 :
Présentation du langage C++.

2
Le langage C++ a été conçu à partir de 1982 par Bjarne Stroustrup (AT&T Bell
Laboratories). C++ a connu plusieurs versions, jusqu’à sa normalisation par l’ANSI en
1998. D’après Bjarne Stroustrup, conception du langage C++ pour : Être meilleur que C,
Permettre les abstractions de données et permettre la programmation orientée-objet. C++
est un sur-ensemble de C. Certaines des extensions du C++ pourraient en fait être ajoutées
au langage C, sans qu’il soit pour autant « orienté objet ». C++ a introduit de nouvelles
possibilités d’entrées-sorties (basées sur la notion de flot) qui rendent superflues les
fonctions standards de C telles que printf ou scanf. C++ dispose d’opérateurs de gestion
dynamique (new et delete) qui remplacent avantageusement les fonctions malloc, calloc et
free du C. Comme tout langage, C++ dispose d’une bibliothèque standard, c’est-à-dire de
fonctions et de classes prédéfinies. Elle comporte notamment de nombreux patrons de
classes et de fonctions permettant de mettre en œuvre les structures de données les plus
importantes (vecteurs dynamiques, listes chaînées, chaînes...).

1. Les identificateurs

Les identificateurs servent à désigner les différentes « choses » manipulées par le


programme, telles: les variables, les fonctions, objets, structures, unions ou énumérations,
membres de classe, de structure ou d’union, types, étiquettes d’instruction goto, macros.
Comme dans la plupart des langages, ils sont formés d’une suite de caractères choisis
parmi les lettres ou les chiffres, le premier d’entre eux étant nécessairement une lettre. Le
caractère souligné (_) est considéré comme une lettre. Il peut donc apparaître au début
d’un identificateur. Les majuscules et les minuscules sont autorisées mais ne sont pas
équivalentes.

Certains « mots-clés » sont réservés par le langage à un usage bien défini et ne peuvent
pas être utilisés comme identificateurs.

2. Les commentaires

3
Comme tout langage évolué, C++ autorise la présence de commentaires dans vos
programmes source. Il existe deux types de commentaires : les commentaires « libres »,
hérités du langage C et les commentaires de fin de ligne (introduits par C++).

3. Création d’un programme en C++


Les grandes étapes de la création d’un programme, sont : édition du programme,
compilation et édition de liens. L’édition du programme (on dit aussi parfois « saisie »)
consiste à créer, à partir d’un clavier, tout ou partie du texte d’un programme qu’on
nomme « programme source ». En général, ce texte sera conservé dans un fichier que l’on
nommera « fichier source ». La plupart du temps, en C++, les fichiers source porteront
l’extension cpp.

La compilation consiste à traduire le programme source (ou le contenu d’un fichier


source) en langage machine, en faisant appel à un programme nommé compilateur.
L’opération de compilation comporte en fait deux étapes : Traitement par le
préprocesseur: Il exécute les directives qui le concernent (commencent par un caractère #).
Il produit, en résultat, un programme source en C++ pur. Compilation: C’est la traduction
en langage machine du texte C++ fourni par le préprocesseur. Le résultat de la
compilation porte le nom de module objet.

En général, un module objet créé ainsi par le compilateur n’est pas directement
exécutable. Il lui manquera, en effet, au moins les fonctions de la bibliothèque standard
dont il a besoin. La bibliothèque est une collection de modules objets organisée, suivant
l’implémentation concernée, en un ou plusieurs fichiers. Le rôle de l’éditeur de liens que
d’aller rechercher dans la bibliothèque standard les modules objets nécessaires. Le résultat
de l’édition de liens est ce que l’on nomme un programme exécutable, c’est-à-dire un
ensemble autonome d’instructions en langage machine.

4. Les types de base de C++


Un type définit l’ensemble des valeurs que peut prendre une variable, le nombre d’octets à
réserver en mémoire et les opérateurs que l’on peut appliquer dessus. Les types de base du
langage C++ se répartissent en quatre catégories en fonction de la nature des informations
qu’ils permettent de représenter : nombres entiers (mot-clé int), nombres flottants (mot-clé
float ou double), caractères (mot-clé char) et les valeurs booléennes, c’est-à-dire dont la
valeur est soit vrai, soit faux (mot-clé bool).

4
Tableau 1 : Les types entiers et les types flottants

5. Les entrées-sorties conversationnelles de C++


Les entrées/sorties en langage C s’effectue par les fonctions scanf et printf de la librairie
standard du langage C. Il est possible d’utiliser ces fonctions pour effectuer les
entrées/sorties de vos programmes, mais cependant les programmeurs C++ préfèrent les
entrées/sorties par flux (ou flot ou stream). L’opérateur (surchargé) << permet d’envoyer
des valeurs dans un flot de sortie, tandis que >> permet d’extraire des valeurs d’un flot
d’entrée.

cout et cin

Entrées/sorties fournies à travers la librairie iostream


cout << expr1 << … << exprn
Instruction affichant expr1 puis expr2, etc.
cout : « flot de sortie » associé à la sortie standard (stdout)
<< : opérateur binaire associatif à gauche, de première opérande cout et de 2ème
l’expression à afficher, et de résultat le flot de sortie
<< : opérateur surchargé (ou sur-défini) utilisé aussi bien pour les chaînes de caractères,
que les entiers, les réels etc.
Vitesse d’exécution plus rapide : la fonction printf doit analyser à l’exécution la chaîne de
formatage,tandis qu’avec les flots, la traduction est faite à la compilation.

6. Les structures de contrôle.


Le C++ utilise la même syntaxe que le langage C pour les différentes structures de
contrôle. Les structures de contrôle sont if/else, for et while (ou do … while).

7. Tableaux
5
Un tableau est une variable qui contient un ensemble de variable de même type.
Les éléments du tableau sont repérés, respectivement par un ensemble d’indices.
Les éléments de ce tableau sont rangés, respectivement en des positions mémoire
successives. C++ utilise la même syntaxe que le langage C pour les tableaux.

8. Les pointeurs
Toute variable manipulée dans un programme est stockée quelque part en mémoire
centrale. Cette mémoire est constituée d'octets qui sont identifiés de manière univoque par
un numéro qu'on appelle adresse. Pour retrouver une variable, il suffit donc de connaître
l'adresse de l'octet où elle est stockée (ou, s'il s'agit d'une variable qui recouvre plusieurs
octets contigus, l'adresse du premier de ces octets). Pour des raisons évidentes de lisibilité,
on désigne souvent les variables par des identificateurs, et non par leur adresse.

Notion de pointeur
Un pointeur est une variable permettant de stocker une adresse-mémoire

Déclaration :
type *pointeur. Où le type est celui des variables auxquelles permet d’accéder le pointeur

Affectation d’un pointeur :


 Affectation de l’adresse d’une variable :
int x, *px ;
px = &x ;
 Affectation de l’adresse d’un tableau :
int tb1[10], *p ;
p = tb1; /* p pointe sur la première case de tb1 */
ou
p = &(tb1[0]) ;

Zéro
Zéro est une constante intégral, virgule flottante, ou pointeur. Aucun objet n’est alloué
avec l’adresse 0. Par conséquent, 0 pour un pointeur indique que ce pointeur ne fait pas
référence à un objet.

Arithmétique de pointeurs
L’addition et la soustraction fonctionnent comme avec les adresses…

int tb1[10], *p1, *p2 ;


p1 = tb1 + 3 ; /* p1 pointe sur tb1[3] */
p2 = p1 – 1 ; /* p2 pointe sur tb1[2] */

Opérateur *
 Permet d’accéder au contenu d’une variable par un pointeur :

6
int x, y, *p ;
x = 10 ;
p = &x ;
y = *p ; /* y doit contenir 10 */
 Fonctionne aussi avec les adresses :
*(tab + 3) est équivalent à tab[3]

9. Pointeur et constante

Constante
Les habitués du C ont l’habitude d’utiliser la directive du préprocesseur #define pour
définir des constantes. Il est reconnu que l’utilisation du préprocesseur est une source
d’erreurs difficiles à détecter. En C++, l’utilisation du préprocesseur se limite aux cas les
plus sûrs : Le mot réservé const permet de définir une constante. Il est possible d’ajouter
le mot clé const à la déclaration d’un objet afin de le rendre constant. Une valeur ne
pouvant pas être attribuée à une constante, celle-ci doit être initialisée.

const int N = 10; // N est un entier constant.


const int t[]={1, 2, 3, 4, 5}; // t[i] est une constante.
const int x; // erreur: pas d’initialisateur

Les pointeurs et les constantes


L’utilisation d’un pointeur implique deux objets:
Le pointeur lui-même et l’objet pointé.

int y=20;
int x = 10;
const int *px = &x; // pointeur de constante
*px = 6; // erreur: pc pointe sur une constante
px = &y; // ok, pointeur pc n’est pas constant
-------------------------------------------------------------------------------------------------
int *const py = &y; // pointeur constant.
*py = 9; // ok
py = px; // erreur: cp n’est pas constant
-------------------------------------------------------------------------------------------------
const int *const pz = &z; // pointeur const de const
pz = 7; // erreur: cpc pointe sur une constante
pz = px; // erreur: cpc est une constante

L’utilisation d’un pointeur implique deux objets:


Le pointeur lui-même et l’objet pointé.
 En préfixant la déclaration d’un pointeur avec const, l’objet devient une constante.
 Pour déclarer le pointeur comme une constante, nous utilisons la déclaration *const
plutôt que simplement *.

char s[ ] = “Gorm”;
const char* pc = s; // pointeur de constante

7
pc[3] = ‘g’; // erreur: pc pointe sur une constante
pc = p; // ok, pointeur pc n’est pas constant
char *const cp = s; // pointeur constant.
cp[3] = ‘a’; // ok
cp = p; // erreur: cp n’est pas constant
const char *const cpc = s; // pointeur const de const
cpc[3] = ‘a’; // erreur: cpc pointe sur une constante
cpc = p; // erreur: cpc est une constante

Vous pouvez attribuer l’adresse d’une variable à un pointeur constant. L’adresse d’une
constante ne peut être attribuée que à un pointeur constant.

int a=1;
const int c=2;
const int* p1=&c; //ok
const int* p2=&a; //ok
int* p3=&c; // erreur: initialisation de int* avec const int*
*p3 = 7; // tente de modifier la valeur de c

10.La gestion dynamique : les opérateurs new et delete

Allocation Mémoire
Le C++ met à la disposition du programmeur deux opérateurs new et delete pour remplacer
respectivement les fonctions malloc et free .
(bien qu’il soit toujours possible de les utiliser).
 L’opérateur new
 L’opérateur delete
L’opérateur new
L’opérateur new réserve l’espace mémoire qu’on lui demande et l’initialise.
Il retourne:
 soit l’adresse de début de la zone mémoire allouée,
 soit 0 si l’opération à échouée.
L’opérateur new:
int *ptr1, *ptr2, *ptr3;

// allocation dynamique d’un entier


ptr1 = new int;

// allocation d’un tableau de 10 entiers


ptr2 = new int [10];

// allocation d’un entier avec initialisation


ptr3 = new int(10);

L’opérateur dedete:
L’opérateur delete libère l’espace mémoire alloué par new à un seul objet, tandis que
l’opérateur delete[] libère l’espace mémoire alloué à un tableau d’objets.
// libération d’un entier

8
delete ptr1;
// libération d’un tableau d’entier
delete[] ptr2;
 A chaque instruction new doit correspondre une instruction delete.
 Il est important de libérer l’espace mémoire dès que celui ci n’est plus nécessaire.
 La mémoire allouée en cours de programme sera libérée automatiquement à la fin du
programme.

Deux opérateurs : new et delete


float *PointeurSurReel = new float; // Équivalent en C :
//PointeurSurReel = (float *) malloc(sizeof(float));
int *PointeurSurEntier = new int[20]; // Équivalent en C :
//PointeurSurEntier = (int *) malloc(20 * sizeof(int));
delete PointeurSurReel; // Équivalent en C : free(pf);
delete [] PointeurSurEntier; // Équivalent en C : free(pi);

11.Références

Variables références
En plus des variables normales et des pointeurs, le C++ offre les variables références. Une
variable référence permet de créer une variable qui est un "synonyme" d'une autre. Dès
lors, une modification de l'une affectera le contenu de l'autre.

int x;
int & y = x; // y est une référence à x
int *ptr;
x=1;
cout << "x= " << x << " y= " << y << endl;
// affichage de : x= 1 y= 1
y=2;
cout << "x= " << x << " y= " << y << endl;
// affichage de : x= 2 y= 2
ptr = &y;
*ptr = 3;
cout << "x= " << x << " y= " << y << endl;
// affichage de : x= 3 y= 3

Une variable référence doit obligatoirement être initialisée et le type de l'objet initial doit être
le même que l'objet référence.

9
Chapitre 2:
Classes et objets

10
La P.O.O basée entièrement sur le concept de classe. Une classe est la généralisation de la
notion de type défini par l’utilisateur, dans lequel se trouvent associées à la fois des
données (membres données) et des méthodes (fonctions membres). En P.O.O. « pure »,
les données sont encapsulées et leur accès ne peut se faire que par le biais des méthodes.
En C++, en revanche, vous pourrez n’encapsuler qu’une partie des données d’une classe
(même si cette démarche reste généralement déconseillée). Vous pourrez même ajouter
des méthodes au type structure (mot clé struct); dans ce cas, il n’existera aucune
possibilité d’encapsulation. Ce type sera rarement employé sous cette forme généralisée
mais comme, sur un plan conceptuel, il correspond à un cas particulier de la classe.

1. Notion de classe
En C++, la structure est un cas particulier de la classe. Une classe est une structure dans
laquelle vous pourrez encapsuler certains membres et/ou fonctions membres. La
déclaration d’une classe est voisine de celle d’une structure. En effet, il suffit: de
remplacer le mot clé struct par le mot clé class ;
de préciser quels sont les membres publics (fonctions ou données) et les membres privés
en utilisant les mots clés public et private.

Figure 1 : Exemple 1 d’un programme en C++.

11
Figure 2 : Exemple 2 d’un programme en C++.

Dans le jargon de la P.O.O., on dit que a et b sont des instances de la classe point, ou
encore que ce sont des objets de type point ; Dans notre exemple, tous les membres
données de point sont privés, ce qui correspond à une encapsulation complète des
données. Si aucun de ces deux mots « private ou public » n’apparaît au début de la
définition, tout se passe comme si private y avait été placé. Il existe un troisième mot,
protected (protégé), lequel n’intervient que dans le cas de classes dérivées. Il est possible
de rendre certaines fonctions de la classe privées. Dans ce cas, ces fonctions ne seront plus
accessibles de l’extérieur de la classe. Elles ne pourront être appelées que par d’autres
fonctions membres.

2. Affectation d’objets
class point
{
int x ;
public :
int y ;
....
};
point a, b ;
l’instruction :

12
b=a;
provoquera la recopie des valeurs des membres x et y de a dans les membres correspondants
de b.
Contrairement aux ces des structures, il n’est plus possible ici de remplacer cette instruction
par :
b.x = a.x ;
b.y = a.y ;

3. Notions de constructeur et de destructeur


A priori, les objets suivent les règles habituelles concernant leur initialisation par défaut :
seuls les objets statiques voient leurs données initialisées à zéro. En général, il est donc
nécessaire de faire appel à une fonction membre pour attribuer des valeurs aux données
d’un objet. C++ offre un mécanisme très performant pour initialiser les données de l’objet
lors de sa création: le constructeur. Il s’agit d’une fonction membre (définie comme les
autres fonctions membres) qui sera appelée automatiquement à chaque création d’un
objet. Un objet pourra aussi posséder un destructeur, c’est-à-dire une fonction membre
appelée automatiquement au moment de la destruction de l’objet. Par convention, le
constructeur se reconnaît à ce qu’il porte le même nom que la classe. Quant au
destructeur, il porte le même nom que la classe, précédé d’un tilde (~).

Figure 3 : Exemple 3 d’un programme en C++.

13
Figure 4 : Exemple 4 d’un programme en C++.

À partir du moment où une classe possède un constructeur, il n’est plus possible de créer
un objet sans fournir les arguments requis par son constructeur (sauf si ce dernier ne
possède aucun argument !). Pour une classe point disposant d’un constructeur sans
argument, la déclaration d’objets de type point peut s’écrire de la même manière que si la
classe ne disposait pas de constructeur :

point a ; //déclaration utilisable avec un constructeur sans argument


Pour une classe point disposant d’un constructeur avec arguments:
point a() ; // incorrect

Lorsqu’une classe ne définit aucun constructeur, tout se passe en fait comme si elle
disposait d’un « constructeur par défaut » ne faisant rien.

4. Construction et destruction des objets


Nous vous proposons ci-dessous un petit programme mettant en évidence les moments où
sont appelés respectivement le constructeur et le destructeur d’une classe. Nous y
définissons une classe nommée test ne comportant que ces deux fonctions membres ;
celles-ci affichent un message nous fournissant ainsi une trace de leur appel. En outre, le
membre donnée num initialisé par le constructeur nous permet d’identifier l’objet
concerné. Nous créons des objets de type test à deux endroits différents : dans la fonction
main d’une part, dans une fonction fct appelée par main d’autre part.

14
Figure 5 : Exemple 5 d’un programme en C++.

Figure 6 : Exemple 6 d’un programme en C++.

15
Figure 7: Exemple 7 d’un programme en C++.

Un constructeur peut comporter un nombre quelconque d’arguments, éventuellement


aucun. Par définition, un constructeur ne renvoie pas de valeur; aucun type ne peut figurer
devant son nom (dans ce cas précis, la présence de void est une erreur). Par définition, un
destructeur ne peut pas disposer d’arguments et ne renvoie pas de valeur. Là encore,
aucun type ne peut figurer devant son nom (et la présence de void est une erreur).

16
Chapitre 3 :
Les propriétés des fonctions membres.

17
Nous allons étudier un peu plus en détail l’application aux fonctions membres des
possibilités offertes par C++: surdéfinition, arguments par défaut, fonction en ligne,
transmission par référence...

1. Surdéfinition des fonctions membres


Nous avons déjà vu comment C++ nous autorise à surdéfinir les fonctions ordinaires.
Cette possibilité s’applique également aux fonctions membres d’une classe, y compris au
constructeur (mais pas au destructeur puisqu’il ne possède pas d’argument).

Le statut privé ou public d’une fonction membre n’intervient pas dans la surdéfinition des
fonctions membres.

Condisérez cet exemple :

class A { public : void f(int n) { ..... }


private : void f(char c) { ..... }
};
main()
{ int n ; char c ; A a ;
a.f(c) ;
}

18
L’algorithme recherche de la meilleure fonction, et ceci, indépendamment de leur statut
(public ou privé), conclut alors que f(char) est la meilleure fonction et qu’elle est unique.

si f(char) est définie publique, elle serait bien appelée par a.f(c) ;

si f(char) n’est pas définie du tout, a.f(c) appellerait f(int).

2. Arguments par défaut


Comme les fonctions ordinaires, les fonctions membres peuvent disposer d’arguments par
défaut.

3. Les fonctions membres en ligne


C++ permet de définir aussi des fonctions membre en ligne. En effet, 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é ; 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. on placera les définitions des fonctions en ligne à la suite
de la déclaration de la classe, dans le même fichier en-tête.

4. Cas des objets transmis en argument d’une fonction membre

Dans l’exemple précédent, l’objet pt était transmis classiquement à coincide, à savoir par
valeur. Précisément, cela signifie donc que, lors de l’appel : les valeurs des données de b
sont recopiées dans un emplacement (de type point) local à coincide (nommé pt). En fait,
en C++, n’importe quelle fonction membre d’une classe peut accéder à n’importe quel
membre (public ou privé) de n’importe quel objet de cette classe. On parle dans ce cas de
la transmission par les valeurs.
Transmission par adresse d’un objet
19
Pour ne pas modifier les valeurs des objets, des arguments des fonctions, utilisés en
passage par adresse, il faut utiliser le qualificatif const.

int point::coincide (const point * adpt)


en modifiant parallèlement son prototype :
int coincide (const point *) ;

5. Transmission par référence

20
Pour ne pas modifier les valeurs des objets, des arguments des fonctions, utilisés en
passage par adresse en référence, il faut utiliser le qualificatif const.

int point::coincide (const point & pt)

6. Autoréférence : le mot clé this


C++ a créé le mot clé : this. Celui-ci, utilisable uniquement au sein d’une fonction
membre, Il désigne un pointeur sur l’objet l’ayant appelée.

21
Chapitre 4:
Construction, destruction et initialisation
des objets

22
En C++, une variable peut être créée de deux façons : par une déclaration : elle est alors de
classe automatique ou statique ; sa durée de vie est parfaitement définie par la nature et
l’emplacement de sa déclaration ; en utilisant les opérateurs new et delete ; elle est alors
dite dynamique ; sa durée de vie est contrôlée par le programme. Ces trois « classes
d’allocation » (statique, automatique, dynamique) vont naturellement s’appliquer aux
objets.

1. Durée de vie
Les objets automatiques sont ceux créés par une déclaration :

 Dans une fonction, L’objet est créé lors de la rencontre de sa déclaration et Il est
détruit à la fin de l’exécution de la fonction.
 Dans un bloc, l’objet est aussi créé lors de la rencontre de sa déclaration et il est
détruit lors de la sortie du bloc.

Les objets statiques sont ceux créés par une déclaration située : En dehors de toute
fonction, ou bien dans une fonction, mais assortie du qualificatif static. 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.

2. Appel des constructeurs et des destructeurs

Si un objet possède un constructeur, sa déclaration doit obligatoirement comporter les


arguments correspondants. Par exemple, si une classe point comporte le constructeur de
prototype :

point (int, int)

les déclarations suivantes seront incorrectes :

point a ; // incorrect : le constructeur attend deux arguments

point b (3) ; // incorrect (même raison)

Celle-ci, en revanche, conviendra :

point a(1, 7) ; // correct car le constructeur possède deux arguments

En ce qui concerne la chronologie, on peut dire que : le constructeur est appelé après la
création de l’objet et le destructeur est appelé avant la destruction de l’objet.

3. Les objets dynamiques


Nous pourrons créer dynamiquement un emplacement de type point et affecter son adresse
à une variable de type pointeur adr par :

point * adr = new point;


23
L’accès aux membres de l’objet pointé par adr se fera par:
adr -> initialise (1, 3) ; adr -> affiche ( ) ;
ou, éventuellement, sans utiliser l’opérateur ->, par :
(* adr).initialise (1, 3) ; (* adr).affiche ( ) ;
La suppression de l’objet se fera par: delete adr ;

C++ fait du constructeur (dès lors qu’il existe) un passage obligé lors de la création d’un
objet. Après l’allocation dynamique de l’emplacement mémoire requis, l’opérateur new
appellera un constructeur de l’objet. Pour que new puisse appeler un constructeur
disposant d’arguments, il est nécessaire qu’il dispose des informations correspondantes.
Avant la libération de l’emplacement mémoire correspondant, l’opérateur delete appellera
le destructeur.

24
Chapitre 5:
Le constructeur de recopie

25
Nous avons vu comment C++ garantissait l’appel d’un constructeur pour un objet créé par
une déclaration ou par new. Mais il existe des situations dans lesquelles il est nécessaire
de construire un objet, même si le programmeur n’a pas prévu de constructeur pour cela.
Le cas de la valeur d’un objet transmise en argument à une fonction. le cas d’un objet
renvoyé par valeur comme résultat d’une fonction le cas où un objet est initialisé, lors de
sa déclaration, avec un autre objet de même type.

1. Objet transmis par valeur en argument à une fonction.


Dans ce cas, il est nécessaire de créer, dans un emplacement local à la fonction, un objet
qui soit une copie de l’argument effectif.

2. Objet renvoyé par valeur comme résultat d’une fonction.


Dans ce cas, il faut alors créer, dans un emplacement local à la fonction appelante, un
objet qui soit une copie du résultat.

26
3. Objet est initialisé, lors de sa déclaration, avec un autre objet de
même type.

point a;

point b=a ; équivalent à point b(a) ;

Ces deux déclarations (point b=a et point b(a)) entraînent effectivement la création d’un
objet de type point, suivie de l’appel du constructeur par recopie
D’une manière générale, on regroupe ces trois situations sous le nom d’initialisation par
recopie. Une initialisation par recopie d’un objet est donc la création d’un objet par
recopie d’un objet existant, de même type. Pour réaliser une telle initialisation, C++ a
prévu d’utiliser un constructeur particulier dit constructeur de recopie. Si un tel
constructeur n’existe pas, un traitement par défaut est prévu; on peut dire, de façon
équivalente, qu’on utilise un constructeur de recopie par défaut. Dans toute situation
d’initialisation par recopie il y toujours appel d’un constructeur de recopie.

Si la classe ne contient pas du constructeur de recopie alors l’appel du constructeur de


recopie par défaut est généré automatiquement par le compilateur. Ce constructeur se
contente d’effectuer une copie de chacun des membres. Ce constructeur posera donc les
problèmes pour les objets contenant des pointeurs sur des emplacements dynamiques.
Seules les valeurs des pointeurs seront recopiées, les emplacements pointés ne le seront
pas. Ils risquent alors, par exemple, d’être détruits deux fois.
L’appel de fct (a) ; a créé un nouvel objet, dans lequel on a recopié les valeurs des
membres nelem et adr de a. La situation peut être schématisée ainsi (b est le nouvel objet
ainsi créé) :

À la fin de l’exécution de la fonction fct, le destructeur ~point est appelé pour b, ce qui
libère l’emplacement pointé par adr. A la fin de l’exécution de la fonction main, le
destructeur est appelé pour a, 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.

4. Définition d’un Constructeur de recopie.


Pour éviter le précédent problème il faut que l’appel de fct (a) ; conduise à : créer un
nouvel objet de type vect, avec ses membres données nelem et adr, mais aussi son propre
emplacement de stockage des valeurs du tableau.
Pour ce faire, nous définissons, un constructeur par recopie de la forme :
vect (const vect &) ; // ou, a la rigueur vect (vect &)

dont nous savons qu’il sera appelé dans toute situation d’initialisation donc, en particulier,
lors de l’appel de fct.

27
Vous pouvez fournir explicitement dans votre classe un constructeur de recopie. Il doit
alors s’agir d’un constructeur public disposant d’un seul argument du type de la classe et
transmis obligatoirement par référence.
Exemple:

point (point &) point (const point &)

On notera que si ce constructeur de recopie est privé, il n’est appelable que par des
fonctions membres de la classe.
Notez bien que C++ impose au constructeur par recopie que son unique argument soit
transmis par référence (sinon l’appel du constructeur de recopie impliquerait une
initialisation par recopie de l’argument).
Il doit alors s’agir d’un constructeur disposant d’un seul argument du type de la classe et
transmis obligatoirement par référence.

28
Chapitre 6:
Les fonctions amies

29
En C++, le principe d’encapsulation interdit à une fonction membre d’une classe
d’accéder à des données privées d’une autre classe. Supposons que vous avez deux
classes, une nommée vecteur et l’autre nommée matrice. Il est possible que vous
souhaiterez alors définir une fonction permettant de calculer le produit d’une matrice par
un vecteur. Bien entendu, vous pourriez toujours rendre publiques les données de vos
deux classes, mais vous perdriez alors le bénéfice de leur protection. En C++, la notion de
fonction amie propose une solution intéressante, sous la forme d’un compromis entre
encapsulation formelle des données privées et des données publiques.

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 ». Une telle déclaration d’amitié les autorise
alors à accéder aux données privées, au même titre que n’importe quelle fonction membre.
Il existe plusieurs situations d’amitiés : fonction indépendante, amie d’une classe ;
fonction membre d’une classe, amie d’une autre classe ; fonction amie de plusieurs classes
; toutes les fonctions membres d’une classe, amies d’une autre classe.

L’emplacement de la déclaration d’amitié au sein de la classe. Il n’est pas nécessaire de


déclarer la fonction amie dans la fonction ou dans le fichier source où on l’utilise, car elle
est déjà obligatoirement déclarée dans la classe concernée. Pas d’arguments implicite
(this). Une fonction amie d’une classe possédera un ou plusieurs arguments ou une valeur
de retour du type de cette classe. La déclaration d’amitié peut figurer n’importe où dans la
classe(dans la partie privée ou public).

 Fonction membre d’une classe, amie d’une autre classe


Il s’agit ici d’un cas particulier de la situation précédente. Il suffit simplement de préciser,
dans la déclaration d’amitié, la classe à laquelle appartient la fonction concernée, à l’aide
de l’opérateur de résolution de portée (::).

 Toutes les fonctions d’une classe amies d’une autre classe


pour dire que toutes les fonctions membres de la classe B sont amies de la classe A, on
placera, dans la classe A, la déclaration : friend class B ;

30
Chapitre 6:
L’Héritage

31
Le concept d’héritage constitue l’un des fondements de la P.O.O. L’héritage vous autorise
à définir une nouvelle classe, dite « dérivée », à partir d’une classe existante dite « de
base ». La classe dérivée « héritera » des « potentialités » de la classe de base, tout en lui
en ajoutant de nouvelles, et cela sans qu’il soit nécessaire de remettre en question la classe
de base. Une classe dérivée peut devenir à son tour classe de base pour une autre classe.
C++ autorise l’héritage multiple, grâce auquel une classe peut être dérivée de plusieurs
classes de base.

1. Syntaxe de l’héritage
 Héritage simple :

class classe_dérivée: public classe_de_base {/* etc. */}

 Héritage multiple :

class classe_dérivée: public classe_de_base_1, public classe_de_base_2


{/* etc. */}

2. Redéfinition des membres d’une classe dérivée


En C++ on peut écrire dans la classe dérivée une méthode de même nom d’une fonction
de la classe mère. (redéfinition). Pour appeler la fonction de la classe mère dans la
fonction de la classe dérivée il faut utiliser l’opérateur de résolution de portée (::)

3. Appel des constructeurs et des destructeurs


Pour créer un objet de type B, il faut: d’abord créer un objet de type A( donc faire appel
au constructeur de A) puis le compléter par ce qui est spécifique à B (faire appel au
constructeur de B).

lors de la destruction d’un objet de type B, il y aura: appel du destructeur de B, puis appel
de celui de A (les destructeurs sont appelés dans l’ordre inverse de l’appel des
constructeurs).

Un problème se pose lorsque le constructeur de A nécessite des arguments!!!! En fait,


C++ a prévu la possibilité de spécifier, dans la définition d’un constructeur d’une classe
dérivée, les informations que l’on souhaite transmettre à un constructeur de la classe de
base.

class point { .....


public :
point (int, int) ;
..... .....
};

class pointcol : public point


{ .....

32
public: pointcol (int, int, char) ;

};

et que l’on souhaite que pointcol retransmette à point les deux premières informations
reçues, on écrira son en-tête de cette manière :
pointcol (int abs, int ord, char cl) : point (abs, ord)
Le compilateur mettra en place la transmission au constructeur de point des informations
abs et ord correspondant (ici) aux deux premiers arguments de pointcol.
Ainsi, la déclaration : pointcol a (10, 15, 3) ;

entraînera :

 l’appel de point qui recevra les arguments 10 et 15 ;


 l’appel de pointcol qui recevra les arguments 10, 15 et 3.

4. Le constructeur de recopie et l’héritage


Qu’il s’agisse d’un constructeur par défaut ou de celui fourni explicitement, nous savons
que le constructeur de recopie est appelé en cas : d’initialisation d’un objet par un objet de
même type ; de transmission de la valeur d’un objet en argument ou en retour d’une
fonction. Toutefois, il faut aussi tenir compte de l’existence d’un constructeur de recopie
par défaut.

 La classe dérivée ne définit pas de constructeur de recopie

Il y a donc appel du constructeur de recopie par défaut de B. La recopie se fait membre


par membre. Ici, cela signifie que la « partie » de b1 appartenant à la classe A sera traitée
comme un membre de type A. On cherchera donc à appeler le constructeur de recopie de
A pour les membres données correspondants. Rappelons que :
 si A a défini un tel constructeur, sous forme publique, il sera appelé ;
 s’il n’existe aucune déclaration et aucune définition d’un tel constructeur, on fera
appel à la construction par défaut.
 Notamment, si A déclare un constructeur privé, sans le définir, en vue d’interdire la
recopie d’objets de type A ; dans ce cas, la recopie d’objets de type B s’en trouvera
également interdite.

 La classe dérivée définit un constructeur de recopie

Le constructeur de recopie de B est alors naturellement appelé. Mais la question qui se


pose est de savoir s’il y a appel d’un constructeur de A. En fait, C++ a décidé de ne
prévoir aucun appel automatique de constructeur de la classe de base dans ce cas (même
s’il existe un constructeur de recopie dans A !). Cela signifie que : Le constructeur de
recopie de la classe dérivée doit prendre en charge l’intégralité de la recopie de l’objet, et
non seulement de sa partie héritée.

33
Il est possible d’utiliser le mécanisme de transmission d’informations entre constructeurs.

B (B & x) : A (x) {// recopie de la partie de x spécifique à B (non héritée de A) }

Dans ces conditions, on voit que ce constructeur doit recevoir en argument non pas l’objet
x tout entier, mais seulement ce qui, dans x, est de type A.

C’est là qu’intervient la possibilité de conversion implicite d’une classe dérivée dans une
classe de base

5. Contrôle des accès

34
35

Vous aimerez peut-être aussi