Vous êtes sur la page 1sur 44

Cours d’Informatique II

L3Maths : 2019-2020
B. ONDAMI
Email : bondami@gmail.com

Faculté des Sciences et Techniques, Université Marien NGOUABI


Table des matières
Chapitre 1- Quelques rappels ou compléments sur les bases C++ ......................................................... 3
1-1 Quelques principes de C++ .......................................................................................................... 4
1-2 Rappel d’un exemple de programme C++ ................................................................................... 4
1-3 Déclaration de variables locales ................................................................................................... 5
1-4 Type bool .................................................................................................................................. 5
1-5 En-têtes de C++............................................................................................................................ 5
1-6 Espaces de nommage ................................................................................................................... 6
1-7 Surcharge des fonctions .............................................................................................................. 6
1-8 Surcharge d’opérateurs............................................................................................................. 7
Chapitre 2- Approche orientée objet ................................................................................................. 8
2-1 Principes généraux des principes objets .................................................................................. 8
2-1-1 Encapsulation ........................................................................................................................ 8
2-1-2 Polymorphisme .................................................................................................................... 8
2-1-3 Héritage................................................................................................................................. 9
2-2 C++ et la programmation objet ................................................................................................... 9
2-2-1 Notions d'objets et de classe .................................................................................................. 9
2-2-2 Déclaration d’une classe ....................................................................................................... 9
2-2-3 Définition d’un objet........................................................................................................... 10
2-2-4 Accès aux membres (publiques) d’une classe .................................................................... 10
2-2-5 Affectation à des objets....................................................................................................... 10
2-2-6 Visibilité des attributs et des méthodes ............................................................................... 10
2-2-7 Mise en œuvre d’une méthode de classe.............................................................................. 11
2-2-8 Constructeurs et destructeurs .............................................................................................. 12
Chapitre 3- Classes et objets ................................................................................................................ 14
3-1 Création d’une classe ............................................................................................................... 14
3-2 Fonctions amies...................................................................................................................... 16
3-3 Fonctions en ligne ................................................................................................................... 17
3-4 Constructeurs paramétrés ......................................................................................................... 18
3-5 Membres statiques.................................................................................................................. 19
3-5-1 Données membres statiques .............................................................................................. 19
3-5-2 Fonctions membres statiques .......................................................................................... 20
3-6 Membres constants .................................................................................................................. 21
3-6-1 Donnée membre constante ................................................................................................. 21
3-6-2 Fonction membre constante .............................................................................................. 21
3-7 Moment d’exécution des constructeur et des destructeurs ...................................................... 21

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 2


3-8 Transmission d’objet à des fonctions ...................................................................................... 23
3-9 Retour d’objets ......................................................................................................................... 23
3-10 Affectation d’objets................................................................................................................. 24
Chapitre 4 – Tableaux, pointeurs et opérateurs d’allocation dynamique ............................................. 24
4-1 Tableau d’objets ...................................................................................................................... 24
4-2 Pointeurs sur objet .................................................................................................................... 26
4-3 Le pointeur this..................................................................................................................... 27
4-4 Références ............................................................................................................................... 28
4-4-1 Passage de paramètres par référence................................................................................. 28
4-4-2 Référence comme type de retour d’une fonction .............................................................. 29
4-4-3 Référence autonome ......................................................................................................... 30
4-5 Opérateurs d’allocation dynamique ....................................................................................... 31
4-5-1 Initialisation de la mémoire allouée .............................................................................. 32
4-5-2 Allocation de tableaux ................................................................................................... 32
4-5-3 Allocation dynamique d’objets...................................................................................... 33
Chapitre 5 – Constructeurs par copie et arguments par défaut d’une fonction .................................... 34
5-1 Constructeurs par copie ......................................................................................................... 34
5-2 Arguments par défaut d’une fonction .................................................................................... 36
Chapitre 6 - Héritage ........................................................................................................................... 37
6-1 Mode de dérivation de la classe de base ................................................................................ 37
6-2 Héritage et membres protected ....................................................................................... 39
6-3 Héritage en mode protected ............................................................................................ 40
Chapitre 7- Fonctions virtuelles et polymorphisme ............................................................................. 41
7-1 Fonctions virtuelles ............................................................................................................... 41
7-2 Appel d’une fonction virtuelle par une référence sur une classe mère .................................. 42
7-3 Fonction virtuelle pure .......................................................................................................... 42
7-4 Classe abstraite ...................................................................................................................... 44

Chapitre 1- Quelques rappels ou compléments sur les bases C++

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 3


1-1 Quelques principes de C++
En L2 Maths, le programme s’était limité sur les bases du langage C++. A partir de maintenant
nous abordons la notion de POO (Programmation Orientée Objet), que l’on trouve dans les
langages moderne comme Java, C# et ben d’autres.
C’est essentiellement la POO est qui le point de rupture du langage C++ avec son langage
« mère » C.

1-2 Rappel d’un exemple de programme C++


Commençons par le petit exemple ci-dessous :
#include <iostream>
using namespace std ;
int main()
{
int i ;
cout << "Ceci est un exemple:"<<end ; // commentaire d’une ligne
/* Vous pouvez continuer à vous servir des commentaires de style C */
cout << "Taper un nombre : " ;
//Entrer un nombre via >>
cin >> i ;
//Maintenant, afficher un nombre à l’aide de <<
cout << "Le carré de " << i << "est " << i*i << endl ;
return 0 ;
}
Analysons maintenant le programme précédent :
 L’entête <iostream> prend en charge les opérations d’E/S de style C++ (en fait
<iostream> est en C++, ce qu’est <stdio.h> en C). Selon la norme C++, Il n y a pas
besoin de l’extension .h
 La deuxième ligne du programme est: using namespace std;.
 L’instruction using indique au compilateur d’utiliser std comme espace de
nommage. L’objectif des espaces de nommage en C++ est créer une zone de
déclaration, dans laquelle figure divers éléments de programmes et faciliter ainsi
l’organisation d’un long code. Ici on simplifie l’accès à la bibliothèque standard
déclarée entièrement dans std.
 Le mot cout correspond à un identificateur lié à l’écran.
 Tout ce qui vient après // sont ignorés par le compilateur. C’est une façon
supplémentaire de mettre des commentaires en C++ (les commentaires de style C /*
*/ restent encore valable en C++).
 En C++, >> sert toujours au décalage à droite. Ainsi l’instruction cin >> i ;
correspond à l’opérateur de redirection d’entrée. Grâce à cette instruction, i reçoit une
valeur lue après saisie. Quant à l’identificateur cin, il se rapporte au dispositif d’entrée
standard, soit généralement au clavier.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 4


 Enfin l’instruction return 0; entraine le retour de zéro au processus appelant (soit
généralement au système d’exploitation). Elle fonctionne de façon analogue en C et
C++. Le retour de zéro marque la fin normale du programme et celui d’une valeur
différente signale une anomalie. On peut, comme en C, également recourir aux valeurs
EXIT_SUCCESS et EXIT_FAILURE.
Il convient de remarquer que les opérateurs << et >> peuvent gérer tout type de données
de base (float, double, char …) C++, lorsqu’ils sont destinés aux E/S.

1-3 Déclaration de variables locales


En C++, on peut déclarer une variable locale après une instruction « Action ».
A titre d’exemple, le fragment de code suivant est correct.
int f()
{
int i ;
i=0 ;
int j ;//cette variable est déclarée après une instruction exécutale
j=i*2 ;

return j ;
}
En C++, on a donc le choix de déclarer toutes les variables locales au début du bloc, ou un
peu avant leur première utilisation. Cependant, c’est dans le cas des fonctions longues que
cette seconde solution trouve son intérêt.

1-4 Type bool


Défini en C++, le type booléen s’appelle bool. Il n’existe pas actuellement dans la norme C.
Avec ce type de données de base, les objets peuvent seulement stocker les valeurs true et
false, soit deux mots clé de C++. Des conversions automatiques ont lieu de bool en int et
vice versa. En particulier zéro est converti en false et une valeur différente en true.
Inversement, true est transformé en 1 et false en zéro. En conséquence, le concept
fondamentale selon lequel zéro correspond à « faux » et une valeur différente à « vrai » reste
complément établi dans le langage C++.

1-5 En-têtes de C++


C++ inclut l’intégralité de la bibliothèque des fonctions de C. Il prend ainsi en charge, les
fichiers d’en-têtes associés à C. En conséquence, stdio.h et les autres fichiers d’en-têtes
restent disponibles en C++. Cependant la norme C++, offre également de nouveaux en-têtes,
qui peuvent remplacer le style C.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 5


Ces nouveaux en-tête ne correspondent pas aux noms de fichiers, ils ne portent pas
d’extension .h. Ils se composent uniquement de leur nom entre chevrons. Voici quelques
exemples pris en charge par la norme de C++.
<iostream>, <sftream>, <vector>, <string>.

1-6 Espaces de nommage


Lorsqu’un nouvel en-tête est inclus dans le programme, son contenu figure dans std. Ce
dernier correspond à un espace de nommage, soit à une zone déclarative. Son objectif
consiste à repérer le nom des identificateurs et à éviter ainsi des conflits. Les éléments
déclarés se distinguent d’un espace de nommage à un autre. Les espaces de nommages ne
seront pas traités dans le cadre de ce cours. On se contentera d’utiliser l’espace std (using
namespace std;) ; suffisant pour la plupart des applications C++. using namespace std;
rend visible std (en d’autres termes, elle introduit std dans l’espace de nommage global).
Noter que lorsqu’un programme en C++ inclut un en-tête de C comme stdio.h, son
contenu est placé dans l’espace de noms globaux par souci de compatibilité, ce qui permet à
un compilateur de traiter des programmes liés au sous-ensemble C.

1-7 Surcharge des fonctions


Il est interdit en C de définir plusieurs fonctions qui portent le même nom. En C++, cette
interdiction est levée, moyennant quelques précautions. Le compilateur peut différencier deux
fonctions en regardant le type des paramètres qu'elle reçoit. La liste de ces types s'appelle la
signature de la fonction. En revanche, le type du résultat de la fonction ne permet pas de
l'identifier, car le résultat peut ne pas être utilisé ou peut être converti en une valeur d'un
autre type avant d'être utilisé après l'appel de cette fonction. Il est donc possible de faire des
fonctions de même nom (on les appelle alors des surcharges) si et seulement si toutes les
fonctions portant ce nom peuvent être distinguées par leurs signatures. La surcharge qui sera
appelée sera celle dont la signature est la plus proche des valeurs passées en paramètre lors
de l'appel.
Exemple :
#include <iostream>
using namespace std ;
float produit(int i, int j)
{
int res = i*j;
cout << "Appel du produit des entiers, resultat:"<<res<< endl;
return res;
}

float produit(float i, float j)


{

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 6


float res = i*j ;
cout << "Appel du produit des float, resultat: "<< res << endl;
return res;
}

int main()
{
int i =2;
int j =3;
produit(i,j);

float a=2.;
float b=3.;
produit(a,b);

return 0 ;

Ces deux fonctions portent le même nom, et le compilateur les acceptera toutes les deux. Lors
de l'appel de produit (2,3), ce sera la première qui sera appelée, car 2 et 3 sont des entiers.
Lors de l'appel de produit(2.,3.), ce sera la deuxième, parce que 2. et 3. sont réels.

Attention ! Dans un appel tel que produit(2.5,3), le flottant 2.5 sera converti en entier et la
première fonction sera appelée. Il convient donc de faire très attention aux mécanismes de
surcharge du langage, et de vérifier les règles de priorité utilisées par le compilateur.

1-8 Surcharge d’opérateurs


Les opérateurs ne se différencient des fonctions que syntaxiquement, pas logiquement. Le
compilateur traite un appel à un opérateur comme un appel à une fonction. En générale vous
pouvez surcharger la plupart des opérateurs de C++ en définissant leur signification rapport
à une classe spécifique.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 7


Chapitre 2- Approche orientée objet

2-1 Principes généraux des principes objets


Contrairement à d’autres techniques de programmation (programmation structurée par
exemple), un programme orienté objet (POO) est organisé autour « des données qui gèrent
l’accès au code ». Vous y définissez les données et les routines autorisées à agir sur ces
dernières. En conséquence, un type de données définit précisément la catégorie d’opérations
applicables à celles-ci.
Pour prendre en charge la POO, tous les langages concernés connaissent : l’encapsulation, le
polymorphisme et l’héritage en commun.

2-1-1 Encapsulation
L’encapsulation permet de lier le code et les données que celui-ci traite. Elle préserve ces
éléments d’une intervention extérieure ou d’un mauvais usage. Dans un langage de POO on
peut allier le code et les données de manière à créer une « boite noire » autonome. Lorsqu’on
établit un tel lien, on crée un objet. Ce dernier est donc un moyen de prendre en charge
l’encapsulation.
Dans un objet, les membres peuvent être privés ou publics, qu’il s’agisse du code, d’une
donnée ou des deux. Dans le premier cas, ils ne sont connus et accessibles qu’a l’intérieur de
l’instance. En conséquence, un programme extérieur ne peut pas atteindre ces éléments
privés. En revanche, il peut accéder aux membres publics définis dans un objet. Celui-ci sert
habituellement à fournir une interface contrôlée aux éléments privés.
Un objet est à toute fins pratiques une variable, dont le type est défini par l’utilisateur, ce qui
peut paraître étrange pour une instance liant du code et des données. En POO, le cas se
présente pourtant. Lorsque vous définissez une nouvelle catégorie d’objets, vous créer un
type de données. Chaque instance associée à ce dernier constitue une variable composée.

2-1-2 Polymorphisme
Les langages de programmation orientée objet prennent en charge le polymorphisme. Cela se
caractérise par « une interface, plusieurs méthodes ». En termes simples, le polymorphisme est
l’attribut qui permet à une interface de gérer l’accès à une catégorie générale d’actions. Pour
une opération spécifique, le choix est déterminé par la nature exacte de la situation.
Les premiers langages de POO étaient des langages interprétés. En conséquence, le
polymorphisme était pris en charge lors de l’exécution.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 8


En C++, le polymorphisme de compilation et d’exécution sont supportés, car c’est un langage
compilé.

2-1-3 Héritage
L’héritage est le processus par le quel un objet peut acquérir les propriétés d’un autre.

2-2 C++ et la programmation objet


Dans cette section, la caractéristique la plus importante de C++ est introduite : la classe. Pour
créer un objet, on doit d’abord définir sa forme générale, grâce au mot clé class.

2-2-1 Notions d'objets et de classe


Un objet est une entité qui regroupe des données membre (les attributs) et des fonctions
membre (les méthodes). Les méthodes sont des fonctions qui s'appliquent à l'objet et à ses
attributs, elles représentent les fonctionnalités de l'objet (son comportement).
Une classe est un moule d'objets, elle décrit les attributs et les méthodes de toute une famille
d'objets. Une classe définit en particulier un type d'objet. Les objets d'une même classe auront
des attributs de même type, mais avec des valeurs différentes. Une classe est la description
générale d'une famille d'objet et un objet est une instance de classe.
Par exemple, si une classe Voiture décrit une voiture avec comme attributs un nombre de
chevaux, une instance (un objet) de cette classe, désigne donc une voiture particulière, "la
voiture de Monsieur Jean", qui a 90 chevaux, par exemple.

2-2-2 Déclaration d’une classe


La déclaration d'une classe s'inspire de la déclaration des structures en C.
Pour déclarer une classe, vous devez taper le mot clé class, suivi d’une accolade ouvrante et la
liste des données membres et des méthodes, dont doit préciser le mode d’accès (public ou
privé). Pour clore la déclaration, tapez une accolade fermante et un point-virgule.
Voici un exemple de déclaration d'une classe voiture avec une donnée membre (attribut) et
une méthode qui affiche :

class Voiture {
public:
int nombreDeChevaux ;
void demarrer() ;
};

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 9


2-2-3 Définition d’un objet
Il s’agit d’une opération aussi aisée que de définir une variable entière :
unsigned int poidsBrut ; // déclaration d’un entier non signé
Voiture Rav4 ; // définition d’une Voiture Rav4

Dans cet exemple, on définit la variable poidsBrut de type entier non signé ainsi que l’objet
Rav4 de la classe (ou de type) Voiture.
Ainsi Rav4 est une instance de la classe Voiture.

2-2-4 Accès aux membres (publiques) d’une classe


Après avoir défini un objet (Voiture, par exemple), vous pouvez lire ses membres à l’aide de
l’opérateur point (.). Pour affecter la valeur 90, à la variable membre nombreDeChevaux de
l’objet Rav4, on écrit l’expression suivante :
Rav4.nombreDeChevaux=90 ;
Pour appeler la méthode demarrer(), il suffit d’écrire : Rav4.Demarrer();

2-2-5 Affectation à des objets


En C++, il est interdit d’affecter des valeurs à des types. L’attribution des valeurs concerne les
variables. Par exemple, vous n’avez pas le droit de tapez l’expression suivante :
int =2 ; // erreur !!!

Le compilateur détecte l’erreur car il est impossible d’affecter 2 au type entier. En revanche,
vous pouvez définir une variable de type entier et lui affecter la valeur 2. Exemple,
int x ; // x est un entier
x=2 ; // x est égal à 2

L’expression suivante génère aussi une erreur:


Voiture.nombreDeChevaux=70; // Erreur !!!!

Le compilateur génère une erreur car il est impossible d’affecter la valeur 70 au membre
nombreDeChevaux de la classe Voiture. Pour cela vous devez créer un objet Voiture, puis lui
attribuer cette valeur. Exemple :
Voiture Toyota_corrola; //instanciation : on définit une instance (i.e un
objet) de la classe Voiture
Toyota_corrola.nombreDeChevaux = 70; //affectation

2-2-6 Visibilité des attributs et des méthodes


Pour se conformer au principe d'encapsulation, il est possible de masquer des attributs et des
méthodes au sein d'un objet. On utilise pour cela les mots clés private et public: les
membres (attributs ou méthodes) qui sont private ne sont accessibles que par les méthodes

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 10


de l'objet lui-même et les méthodes des objets de la même classe, tandis que les membres
public sont visibles par tous.
Exemple :

class A{
public :
int attribut_public ;
void methode_public(){} ;
private :
int attribut_private ;
void methode_private(){} ;
} ;

int main()
{
A a ; // déclaration d’un ojet (une instance) a de la classe A
a.attribut_public =0 ; // ok
a.methode_public () ; // ok
a. attribut_private =0; // erreur
a.methode_private() ; // erreur

return 0;
}
Ainsi un attribut public pourra être modifié par tous et l'objet ne pourra finalement pas
contrôler les modifications effectuées sur son propre attribut. Pour éviter ce manque de
contrôle, il est préférable de toujours protéger les attributs (avec le mode d’accès private) et de
mettre en place une méthode qui permette de récupérer la valeur d'un attribut et une
méthode permettant de modifier la valeur d'un attribut (si l'on veut pouvoir modifier cet
attribut, bien sûr). C’est ce que nous allons voir dans l’exemple dans le paragraphe suivant.

2-2-7 Mise en œuvre d’une méthode de classe


Pour être implémentée, une méthode de classe doit d’abord être définie dans le programme.
Cette définition commence par la valeur de retour, le nom de la classe, suivi de l’opérateur
double deux-points (::). Dans l’exemple suivant, vous déclarez une classe B et implémentez ces
méthodes qui permettent de récupérer et modifier ces attributs
//Déclaration de la classe B
class B {
private:
int attribut1;
double attribut2;
public:
int getAttribut1(); //Méthode qui renvoie la valeur de l'attribut1
void setAttribut1(int v); //Méthode qui affecte la valeur passée en
paramètre à l'attribut1

double getAttribut2(); //Méthode qui renvoie la valeur de l'attribut2


void setAttribut2(double v); //Méthode qui affecte la valeur passée en
paramètre à l'attribut2
};

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 11


//Corps des méthodes(définition des méthodes)
int B::getAttribut1() {
return attribut1;
}

void B::setAttribut1(int v) {
attribut1 = v;
}

double B::getAttribut2() {
return attribut2;
}

void B::setAttribut2(double v) {
attribut2 = v;
}

2-2-8 Constructeurs et destructeurs


Pour définir une variable entière, vous pouvez procéder de deux façons distinctes. D’une part,
la variable est définie, puis une valeur lui est affectée ultérieurement dans le programme.
Exemple :
int poids ; // déclaration de la variable poids
………………….. // autres instructions du programme
…………………… // autre instruction du programme
poids = 7 ; //affectation de l valeur 7 à la variable poids

Il est également possible de définir une variable, que vous initialisez aussitôt.
Exemple: int poids =7;//déclaration et initialisation de la variable poids

L’initialisation consiste à définir une variable et à lui affecter une valeur. Vous pouvez modifier
cette valeur ultérieurement, car l’initialisation n’est pas une opération irréversible et définitive.
Comment initialiser les données membres d’une classe ?
Toute classe comprend une fonction membre spéciale appelée constructeur. Ce dernier peut
prendre des paramètres, mais ne renvoie pas de valeurs (même void). En fait le constructeur
est une méthode qui porte le même nom que la classe dans laquelle il réside.

Lorsque vous créer un constructeur, il est souhaitable de déclarer aussi le destructeur associé.
Alors que les constructeurs créent et initialisent des objets dans la classe, les destructeurs
libèrent la mémoire allouée aux objets, puis les suppriment. Un destructeur porte le nom de la
classe précédé d’un tilde (~). Il ne prend pas d’arguments en charge et ne récupère pas de
valeur de renvoi.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 12


2-2-8-1 Construction par défaut
Pour passer la valeur 90 au constructeur de la classe Voiture, vous écrivez,
Voiture prado(90);
En revanche la ligne ci-après :
Voiture prado;
utilise le constructeur par défaut de la classe Voiture.
Un constructeur sans paramètre est appelé constructeur par défaut.

2-2-8-2 Constructeurs fournis par le compilateur


Si vous ne déclarez pas de constructeur, le compilateur crée un constructeur par défaut.
Le constructeur par défaut ne réalise aucune opération, car il est vide de toute instruction.
Notez les deux points importants suivants :
 Le constructeur par défaut ne contient pas de paramètres. Vous pouvez
indifféremment utiliser le constructeur par défaut du compilateur ou créer un
constructeur par défaut.
 Si vous opter pour un constructeur (avec ou sans paramètres), le compilateur ne
génère pas de constructeur par défaut.

Lorsque vous déclarer un constructeur, n’oublier pas de définir le destructeur correspondant,


même si ce dernier ne réalise aucune action.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 13


Chapitre 3- Classes et objets

3-1 Création d’une classe


Comme nous l’avions vu au chapitre précédent, pour créer une classe on doit employer le
mot clé class. Il s’agit donc d’un nouveau type. Ce dernier qui lie le code avec des données,
sert ensuite à déclarer des objets. En conséquence, une classe est une abstraction logique.
Son instance, soit son objet, a en revanche une existence physique.
Quant à la syntaxe de la déclaration d’une classe non dérivée (on verra plus, la définition
d’une classe dérivée), elle est similaire à celle d’une structure (vu dans le module 1). Voici ci-
dessous la forme intégrale de la définition d’une classe.

Class nom_de_la_classe{
données et fonction privées
spécificateur_accès :
données et fonctions
// …………………………
spécificateur_accès :
données et fonctions
} ensemble_objets

Dans ce cas, ensemble_objets est facultatif. Il permet de déclarer des objets de la classe. Par
ailleurs, spécificateur_accès est l’un des trois clé suivant de C++ :
public (pour des données et fonctions publics),
private (pour des données et fonctions privées),
protected (pour des données et fonctions protégées).

Par defaut, les fonctions et les données d’une classe sont privées: Seuls les autres membres de
la classe peuvent donc les atteindre. Pour rendre les fonctions ou les données accessibles à
d’autres parties du programme, définissez-les avec le spécificateur public. Quant à
protected, il se révèle nécessaire, seulement en cas d’héritage (on le verra au prochain
chapitre).
A noter que vous êtes autorisez à changer les caractéristiques d’accès aussi souvent que vous
le souhaitez. Ainsi, vous pouvez parfois passer à public, puis à private, de nouveau.

Voici un exemple de création d’une classe et son d’utilisation :


#include <iostream>

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 14


using namespace std ;

class douanier {
char name[80] ; // private par défaut
public :
void setname(char *pnom) ; // public
void getname(char *pn) ; // public
private :
double age ; // private maintenant
public :
void setage(double dage) ; // public de nouveau
double getage() ; // public
} ;

void douanier::setname(char * pnom)


{
strcpy(name, pnom) ;
}

void douanier::getname(char *pn)


{
strcpy(pn, name) ;
}

void douanier::setage(double _age)


{
age = _age;
}

double douanier::getage()
{
return age ;
}

int main()
{
douanier d;
char nom[80] ;

d.setname("Paul TATA") ;
d.setage(40) ;
d.getname(nom) ;
cout << nom <<" a " << d.getage() <<" ans."<< endl;

return 0;

}
Notez que du point de vue des programmeurs, il est plus aisé de disposer en réalité d’une
seule section par catégorie de spécificateur d’accès dans chaque classe.
Lorsqu’une fonction est déclarée dans une classe, elle est qualifiée de « membre ». Dans ce
cas elle peut accéder aux autres éléments de sa classe. Les variables d’une classe sont
appelées par données membres. Les données et fonctions membres peuvent tous être
désignés par « membre ».
Notez aussi qu’une structure (définie dans le module 1) offre un autre moyen de spécifier une
classe. Par défaut, tous ses membres sont publics et non privés.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 15


De même qu’une union (définie dans le module 1) peut également servir à définir une classe.
En C++ elle peut contenir non seulement des fonctions membres et des variables, mais
également des constructeurs et des destructeurs. Dans ce langage une union garde toutes les
caractéristiques de type C. L’aspect le plus important concerne le partage du même
emplacement mémoire par toutes les données élémentaires. A l’instar d’une structure, une
union possède des membres publics par défaut et parfaitement compatible avec C.

3-2 Fonctions amies


Une fonction extérieure peut-être déclarée amie d’une classe. En tant qu’amie d’une classe,
elle peut accéder à tous les membres privés et protégés de la classe. Pour déclarer une
fonction, comme amie d’une classe, incluez le prototype de la fonction dans la classe
concernée, et faites précéder celui-ci du mot clé friend.
Tester par exemple le programme ci-dessous, qui illustre cette notion :

#include <iostream>
using namespace std ;

class myclass{
int a, b ;
public :
friend int sum(myclass x); //sum est une fonction extérieure,
amie de la classe
void set_ab(int i, int j) ;
} ;

void myclass :: set_ab(int i, int j)


{
cout << "on renseigne a ="<<i << " et b="<< j <<endl;
a = i ;
b = j ;
}

//sum n’est pas une fonction membre de la classe myclass


int sum(myclass x)
{
//comme sum est amie de myclass, elle accéder directement aux membres
privés a et b.
return x.a + x.b ;
}

int main()
{
myclass y ;
y.set_ab(3,4) ;
cout << "a+b ="<<sum(y) << endl ;

return 0;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 16


3-3 Fonctions en ligne
Souvent présent dans une classe, la fonction en ligne constitue une caractéristique importante
de C++. En C++, vous pouvez développer en ligne le code d’une petite fonction, plutôt que
de lancer un appel à celui-ci. Un tel processus est similaire à l’utilisation d’une macro. Pour
développer le code au lieu d’effectuer un appel, faites précéder la définition de la fonction du
mot clé inline. En guise d’illustration, vous pouvez tester la fonction max () suivante :

#include <iostream>
using namespace std ;

inline int max(int a, int b)


{
return a > b ? a : b ;
}

int main()
{
cout << max(10,20)<<endl;
cout << "" << max(99,88)<<endl ;

return 0;
}
La très grande efficacité du code crée par les fonctions en ligne justifie l’importance de leur
incorporation dans C++. L’intérêt est essentiel, car les classes nécessitent habituellement
l’exécution fréquente de plusieurs fonctions d’interfaces (les quelles donnent accès aux
données privées). Comme vous le savez probablement, le mécanisme d’appel et de retour
mobilise chaque fois une quantité considérable de temps système. Lorsque vous développer
en revanche le code d’une fonction en ligne, aucun délai ne se produit.
Notez qu’à l’instar du spécificateur register, inline permet en réalité de faire une
suggestion et non donner un ordre au compilateur. Ce dernier peut donc choisir de l’ignorer.
inline peut s’appliquer à une fonction membre d’une classe comme le montre l’exemple
suivant :

class myclass{
int a, b ;
public :
void init(int i, int j) ;
void show() ;
} ;

inline void myclass :: init(int i, int j) // init en coder en ligne


{
a = i ;
b = j ;
}

inline void myclass :: show()


{

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 17


cout << "a ="<<a <<" b="<< b << endl ;
}

int main()
{
myclass x ;
x.init(10,20) ;
x.show() ;

return 0 ;
}
Enfin notez qu’il est possible de définir entièrement une petite fonction au moment de la
déclaration d’une classe. Automatiquement, elle devient alors, une fonction en ligne (sauf cas
d’impossibilité). Il n’est ni nécessaire, ni erroné de faire précéder sa déclaration du mot clé
inline, comme le montre l’exemple suivant :
class myclass{
int a, b ;
public :
// Fonctions automatiquement en ligne
void init(int i, int j) { a = i ; b = j ;}
void show() { cout <<"a="<<a <<" b="<<b<< endl ;}
} ;

int main()
{
myclass x ;
x.init(10,20) ;
x.show() ;

return 0 ;
}

3-4 Constructeurs paramétrés


De tels constructeurs reçoivent des arguments qui facilitent habituellement l’initialisation d’un
objet existant. Pour en créer un, ajouter-lui simplement les paramètres comme s’il s’agissait
d’une autre fonction. Lorsque vous définissez son corps, utilisez les arguments pour initialiser
l’objet. Voici une classe simple contenant un constructeur paramétré :
class myclass{
int a, b ;
public :
myclass(int i, int j){ a =i ; b = j ;} // constructeur avec
paramètres
void show(){ cout <<"a="<<a <<" b="<<b<< endl ;}
} ;

int main()
{
myclass ob(3,5); //initialisation de a à 3 et de b à 5.
ob.show() ;

return 0 ;

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 18


}
Notez que dans le cas d’un constructeur avec un seul paramètre, la syntaxe de la construction
d’un objet peut se faire de manière différente comme le montre l’exemple suivant :
class X {
int a ;
public :
X(int j) { a = j ;} // constructeur avec un seul paramètre.
int geta() { return a ;}
} ;

int main()
{
X ob = 98 ; // Transmet 98 à j
cout << ob.geta()<<endl; // affiche 98

return 0 ;
}

3-5 Membres statiques


Vous pouvez rendre statiques les membres d’une classe, qu’il s’agisse des fonctions ou des
données. Pour cela on utilise le mot clé static. Pour chaque cas, les conséquences d’une
telle opération sont expliquées dans cette section.

3-5-1 Données membres statiques


Une classe peut contenir des données membres statiques. Ces données sont soit des données
membres propres à la classe, soit des données locales statiques des fonctions membres de la
classe. Dans tous les cas, elles appartiennent à la classe, et non pas aux objets de cette classe.
Elles sont donc communes à tous ces objets (il n’y a donc pas de copie particulière qui est
faite pour chaque, comme c’est le cas des données non statiques).
Il est impossible d'initialiser les données statiques d'une classe dans le constructeur de la
classe, car le constructeur n'initialise que les données des nouveaux objets. En fait, leur
initialisation doit se faire lors de leur définition, en dehors de la déclaration de la classe. Pour
préciser la classe à laquelle les données ainsi définies appartiennent, on devra utiliser
l'opérateur de résolution de portée (::). Exemple,

class myclass
{
static int i; //Déclaration dans la classe.
...
};

int myclass::i=3; //Initialisation en dehors de la classe.

La variable ::i sera partagée par tous les objets de classe test, et sa valeur initiale est 3.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 19


Notez que la définition des données membres statiques suit les mêmes règles que la
définition des variables globales. Autrement dit, elles se comportent comme des variables
déclarées externes. Elles sont donc accessibles dans tous les fichiers du programme (pourvu,
bien entendu, qu'elles soient déclarées en zone publique dans la classe). De même, elles ne
doivent être définies qu'une seule fois dans tout le programme. Il ne faut donc pas les définir
dans un fichier d'en-tête qui peut être inclus plusieurs fois dans des fichiers sources, même si
l'on protège ce fichier d'en-tête contre les inclusions multiples.
Les variables statiques des fonctions membres doivent être initialisées à l'intérieur des
fonctions membres. Elles appartiennent également à la classe, et non pas aux objets. De plus,
leur portée est réduite à celle du bloc dans lequel elles ont été déclarées. Ainsi, le code
suivant :
#include <iostream>
using namespace std ;
class myclass
{
public:
int getcompte (void);
};

int myclass :: getcompte(void)


{
static int compte=0; //variable statique de la fonction membre
return compte++;
}

int main()
{
myclass objet1, objet2;
cout << objet1.getcompte () << endl ; // affiche 0
cout << objet2.getcompte () << endl ; // affiche 1
return 0 ;
}
affiche 0 et 1 parce que la variable statique compte est la même pour les deux objets.

3-5-2 Fonctions membres statiques


Vous pouvez également de déclarer les fonctions membres avec static. Toutefois, plusieurs
restrictions les concernent. Premièrement, elles peuvent se rapporter directement à d’autres
membre statiques de la classe (bien entendu, elles accèdent éventuellement aux données et
aux fonctions globale).
Deuxièmement, une fonction membre statique ne comporte pas le pointeur this (on verra sa
définition au chapitre suivant).
Par ailleurs, la même fonction ne peut pas avoir deux versions, avec et sans static
respectivement. Exemple de fonction statique :

#include <iostream>

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 20


using namespace std ;

class Entier
{
static int i;
public:
static int get_value(void);
};

int Entier::i=3;
int Entier::get_value(void)
{
return i;
}
int main(void)
{
// Appelle la fonction statique get_value :
int resultat = Entier::get_value();
cout << "resultat="<<resultat<< endl ;

return 0;
}

3-6 Membres constants


Les membres d’une classe peuvent être constants. Pour cela on sert du mot clé const.

3-6-1 Donnée membre constante


Une donnée membre d'une classe peut être déclarée constante en utilisant le mot clé const.
Il est alors obligatoire de l'initialiser lors de la construction d'un objet, et sa valeur ne pourra
par la suite plus être modifiée.

3-6-2 Fonction membre constante


Appliquée à une méthode, le mot clé const, empêche toute modification des membres de la
classe. Il s’intercale entre la parenthèse fermante du nom de la fonction et le point-virgule.
Dans l’exemple suivant, la fonction distance ne peut pas modifier l’objet.

class Point {
void placer(int a, int b); // peut modifier l’objet
float distance(Point p) const; // ne peut pas modifier l’objet.
};

3-7 Moment d’exécution des constructeur et des destructeurs


En règle générale le constructeur d’un objet intervient à sa construction et son destructeur à
sa suppression. On va voir sur la base d’un exemple, le moment exact de tels événements.
Considérons d’abord un objet local. L’exécution de son constructeur se produit une fois son
instruction de déclaration rencontrée, et celle de son destructeur dans l’ordre inverse.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 21


Examinons ensuite un objet global. Son constructeur commence avant main(). Par rapport à
d’autres fonctions de ce type, l’ordre d’exécution dépend du lieu de déclaration dans le même
fichier.
Lorsque les constructeurs globaux sont répartis entre plusieurs fichiers, il est indéterminé.
Quant aux destructeur, ils interviennent après main(). Dans l’ordre inverse.
Pour illustrer de tels événements, tester le programme suivant :

#include <iostream>
using namespace std ;

class Entier
{
static int i;
public:
static int get_value(void);
};

int Entier::i=3;
int Entier::get_value(void)
{
return i;
}

class Point {
void placer(int a, int b); // peut modifier l’objet
float distance(Point p) const; // ne peut pas modifier l’objet.
};

class myclass{
public :
int who ;
myclass(int id) ; //constructeur avec un seul paramètre ;
~myclass() ; //destructeur

} ob1(1), ob2(2) ; //deux objet ob1 et ob2

myclass ::myclass(int id)


{
cout << "Initialisation" << id << endl ;
who = id ;
}

myclass ::~myclass()
{
cout << "Destructeur" << who <<endl ;
}

int main()
{
myclass local_ob1(3) ;
cout << "On est apres declaration de local_ob1(3)" << endl ;
myclass local_ob2(4);

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 22


3-8 Transmission d’objet à des fonctions
Vous pouvez passer des objets à des fonctions comme s’il s’agissait de variable d’un autre
type. Pour cela, le mécanisme standard de l’appel par valeur s’impose. En conséquence, une
copie de l’objet est réalisée au moment de la transmission de l’instance à une fonction.
Lorsque la copie d’un objet est due à la transmission de cette instance à une fonction, l’appel
au constructeur n’a pas lieu.
En revanche, l’appel du destructeur se produit au moment de la suppression de la copie de
l’objet de la fonction.

3-9 Retour d’objets


Une fonction peut retourner un objet à un programme d’appel. Lorsqu’un objet est retourné
par une fonction, il est créé automatiquement et provisoirement. Une fois la valeur renvoyée,
cette instance temporaire est détruite. Une telle suppression peut entrainer des effets
secondaires inattendus, dans certaines situations. En cas de mémoire allouée
dynamiquement, cet espace est libéré même si l’instance recevant la valeur de retour
continue à l’utiliser. Pour surmonter ce problème, des moyens existent. Ils impliquent la
surcharge de l’opérateur d’affectation et la définition d’un constructeur de copie (on le verra
dans le module 3). Ci-dessous un exemple de code ayant comme valeur de retour un objet :
#include <iostream>
using namespace std ;

class myclass {
int i ;
public :
void set_i(int n) {i=n ;}
int get_i (){return i ;}
} ;
myclass f() // retourne un objet
{
myclass x ;
x.set_i(1) ;

return x ;
}
int main()
{
myclass o ;
o = f() ;
cout<< "i="<<o.get_i()<<endl ;

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 23


3-10 Affectation d’objets
Supposez que vous ayez deux objets de même type. Vous pouvez alors affecter un objet à un
autre. Cette opération entraine la copie des données de l’objet de droite dans celui de l’objet
de gauche. Par défaut, toutes les données d’un objet sont affectées à un autre via une copie
bit à bit. Toutefois, vous pouvez surcharger l’opérateur d’affectation et définir une procédure
d’affectation.
Ci-dessous un exemple de qui illustre une affectation d’objets :
#include <iostream>
using namespace std ;

class myclass {
int i ;
public :
void set_i(int n) {i=n ;}
int get_i (){return i ;}
} ;

int main()
{
myclass ob1, ob2 ;
ob1.set_i(78) ;
ob2 = ob1 ; // affecte les données d’ob1 à ob2
cout << "ob2.i=" << ob2.get_i() <<endl ;

return 0 ;
}

Chapitre 4 – Tableaux, pointeurs et opérateurs d’allocation


dynamique
Nous allons maintenant aborder les tableaux et pointeurs, avec la notion d’objet.

4-1 Tableau d’objets


Le C++ permet de manipuler des tableaux d’objets en utilisant une syntaxe identique à celle
employée pour un tableau contenant des éléments d’un type quelconque. Le programme
suivant exploite un tableau de trois objets.
#include <iostream>
using namespace std ;

class cl {
int a ;
public :
void set_a(int i) { a = i ;}
int get_a() {return a ;}
} ;

int main()
{
cl ob[3] ;

for( int i=0 ; i< 3 ; i++) ob[i].set_a(i+1) ;

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 24


for( int j=0 ; j<3 ; j++)
cout <<"ob["<<j<<"].a="<< ob[j].get_a() << endl ;

return 0 ;
}
Si une classe possède un constructeur paramétré, vous pouvez définir les divers objets du
tableau en fournissant une liste de paramètres d’initialisation, de la même façon que pour tout
autre type de tableau. Dans ce cas particulier, le format exact de la liste d’initialisation
dépendra du nombre de paramètres attendus par le constructeur des objets contenus dans le
tableau.
Pour ceux dont le constructeur ne requiert qu’un seul paramètre, vous pouvez utiliser le
format normal, en spécifiant successivement les valeurs d’initialisation. Lors de la création de
chacun des éléments du tableau, une valeur est extraite de la liste, et passé en paramètre au
constructeur de l’objet.

Le programme suivant est une version légèrement différente de l’exemple précédent, utilisant
une liste de paramètres d’initialisation :

#include <iostream>
using namespace std ;

class cl {
int a ;
public :
cl(int i) { a = i ;} // constructeur
int get_a(){ return a ;}
} ;

int main()
{
cl ob[3] ={1,2,3} ;

for(int i=0 ; i<3 ; i++)


cout << "ob[" <<i<<"]="<<ob[i].get_a()<< endl ;

return 0 ;
}
En réalité la syntaxe employée ci-dessus est un abrégé de la forme suivante, plus longue :
cl ob[3] ={ cl(1), cl(2), cl(3) };

Pour les objets dont le constructeur compte deux paramètres ou plus, le format plus long,
sera requis.
Par ailleurs il convient de noter que, si le constructeur contient un paramètre, tout tableau
contenant ce type d’éléments doit être initialisé par le biais du constructeur. Par exemple
l’instruction suivante est fausse :
cl a[9] ; // erreur car le constructeur requiert un paramètre.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 25


4-2 Pointeurs sur objet
Exactement comme vous pouvez avoir un pointeur sur un type de donnée quelconque, il est
possible d’avoir un pointeur sur un objet.
Étant donné un pointeur sur un objet, pour atteindre ses attributs ou ses fonctions membres,
utilisez l’opérateur flèche (->) au lieu de l’opérateur point (.). Le programme suivant illustre
l’accès à un objet par le biais d’un pointeur.
#include <iostream>
using namespace std ;

class cl {
int a ;
public :
cl(int i) { a = i ;}
int get_a() {return a ;}
} ;

int main()
{
cl ob(88), *p ;
p= &ob ; // récupération de l’adresse de ob
cout <<"p->a="<< p->get_a() <<endl ; // utilisation de l’opérateur -
>, pour l’appel de get_a().
return 0 ;
}
Comme nous l’avions vu dans le module1, une fois incrémenté, un pointeur désigne le
prochain élément du même type. De manière générale, les opérations sur les pointeurs sont
relatives au type de base du pointeur, c’est –à-dire au type de données que le pointeur
désigne. Ces règles sont par ailleurs valables pour les pointeurs sur objet, comme le montre
l’exemple suivant :
#include <iostream>
using namespace std ;

class cl {
int a ;
public :
cl(int i){a = i ;}
int get_a() {return a ;}
} ;

int main()
{
cl ob[3] = {1, 2, 3} ; // cl est la classe précédente
cl *p ;
p = ob ; // on initialise à l’adresse du premier élément du tableau
ob.
for(int i=0 ; i<3 ; i++){
cout<<"b["<<i<<"]="<<p->get_a()<<endl ;
p++ ; // pointeur sur le prochain objet
}
return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 26


Un pointeur peut également être initialisé pour désigner un attribut public d’un objet pour
permettre d’y accéder par la suite, comme le montre le code suivant :
#include <iostream>
using namespace std ;

class cl {
public :
int a ;
cl(int i){a = i ;}
int get_a() {return a ;}
} ;

int main()
{
cl ob(1) ;
int *p ;
p = &ob.a ; // on affecte à p l’adresse de ob.a
cout << "*p="<<*p <<endl; // on accède à ob.a via le pointeur p

return 0 ;

Note : Il est essentiel de retenir qu’en C++, vous pouvez assigner la valeur d’un pointeur à un
autre pointeur, uniquement si les deux sont compatibles, c’est-à-dire si les deux pointeurs
sont définis pour désigner le même type de donnée.
Considérons par exemple :
int *pi ;
float *pf ;
L’affectation suivante est impossible en C++ :
pi = pf ; // erreur : incompatibilité des types

Il est bien entendu possible de transtyper (cast) les variables pour échapper à ces problèmes
d’incompatibilité.

4-3 Le pointeur this


De manière implicite, un argument est toujours passé lors de l’appel d’une fonction membre
d’une classe. Il s’agit du pointeur que l’on nomme this et qui permet de désigner l’objet
effectuant l’appel de la fonction. Ainsi les deux déclarations de la classe A suivantes sont
valables en C++ :

class A {
int a ;
public :
A(int i){ a = i ;} // pas d’utilisation explicite de this
} ;

class A {
int a ;
public :

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 27


A(int i){ this -> a = i ;}
/* avec utilisation explicite de this: c’est bien l’objet effectuant
l’appel du constructeur qui est initialisé */
} ;

Note : Les fonctions amies ne sont membre d’une classe, et ne reçoivent par conséquent pas
le pointeur this en paramètre. Les méthodes static (méthode de classe) ne peuvent pas
utiliser le pointeur this.

4-4 Références
Le langage C++ à la particularité de proposer un type proche des pointeurs, appelé
référence.
Il existe trois façons d’utiliser une référence :
- En tant paramètre d’une fonction
- En tant que type de retour d’une fonction
- En tant référence autonome.

4-4-1 Passage de paramètres par référence


Lorsque vous passer des paramètres par valeurs, une copie de l’argument est créer et passée
à la fonction.
En revanche, lors d’un passage par référence, seule l’adresse de l’argument est fournie à la
fonction.
Pour créer une référence et la passer en paramètre, il suffit de précéder d’un & le nom de ce
dernier.
Il est recommandé, pour des raisons de performances, de passer par référence tous les
paramètres dont la copie peut prendre beaucoup de temps (en pratique, seuls les types de
base du langage pourront être passés par valeur).
Exemple :
#include <iostream>
using namespace std ;

void neg(int &i) ; // i est maintenant une référence

int main()
{
int x ;
x=10 ;
cout <<"avant appel de neg(x), x="<< x << endl ;
neg(x) ; // plus besoin de l’opérateur &
cout <<"apres appel de neg(x), x="<< x << endl ;
return 0 ;
}

void neg(int &i)

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 28


{
i=-i ;
}
Un objet d’une classe peut également être passé en paramètre par référence à une fonction.
Dans ce cas, aucune copie de l’objet n’est effectuée. Cela signifie qu’aucun appel au
destructeur n’est lancé, et qu’aucun objet reçu en paramètre n’est par conséquent détruit à la
fin de l’exécution. Ci-dessous un exemple qu’illustre le passage par référence sur un objet :
#include <iostream>
using namespace std ;

class A {
int id ;
public :
int i ;
A(int num) ; // constructeur avec un paramètre
~A() ; // destructeur
void neg( A &o) { o.i = -o.i ; } // aucune copie générée
} ;

A ::A(int num)
{
cout << "On passe dans le constructeur."<< endl ;
id = num ;
}

A ::~A()
{
cout << "On passe dans le destructeur."<< endl ;
}

int main()
{
A o(1) ;
o.i = 10 ;
o.neg(o) ;

cout <<"o.i="<< o.i << endl ;

return 0 ;
}

4-4-2 Référence comme type de retour d’une fonction


Il est tout à fait possible qu’une fonction retourne une référence, cela lui donne même
l’avantage important de permettre son appel dans la partie gauche d’un opérateur
d’affectation, comme le montre l’exemple suivant :
#include <iostream>
using namespace std ;

char &replace(int i) ; //retourne une référence


char s[80] = "Hello There" ;

int main()
{
replace(5) = 'X' ; //remplace par X l’espace suivant Hello
cout <<"la chaine de caracteres devient: "<< s <<endl;;
return 0 ;

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 29


}

char &replace(int i)
{
return s[i] ;
}

4-4-3 Référence autonome


Le passage de paramètres par référence et le renvoi de références par un programme
correspond de loin à la plus grande utilisation faite des références. Il est cependant possible
de déclarer une référence, et de l’utiliser comme une simple variable. Ce type de référence est
appelé références autonomes.
Lorsque vous créez une référence autonome, tout ce que vous déclarez est en réalité un autre
nom, pour désigner un objet existant. Une référence est en fait un alias (pseudonyme). En
d’autres termes, une référence autonome est initialisée avec le nom d’un autre objet appelé
objet cible. Dès lors, elle se comporte comme l’objet cible et toute opération effectuée sur la
référence se répercute sur celui-ci.
Certains développeurs assimilent les références aux pointeurs. S’il est vrai que les références
sont le plus souvent implémentées à l’aide de pointeurs, vous devez distinguer ces deux
concepts :
Un pointeur est une variable contenant l’adresse d’un objet, alors qu’une référence désigne
un objet du programme.
Toutes les références autonomes doivent être initialisées lors de leur déclaration.
Par ailleurs il faut noter les restrictions suivantes concernant les références :
- Une référence ne peut pas désigner une autre référence, autrement dit, vous ne
pouvez obtenir l’adresse d’une référence.
- Il n’est pas possible de créer un tableau de référence, ni de créer un pointeur sur une
référence ou une référence sur un champ de bit
- Les références nulles sont formellement interdites.
Le code ci-dessous illustre l’utilisation d’une référence autonome:
#include <iostream>
using namespace std ;
int main()
{
int a ;
int &ref = a ; // référence autonome
a=10 ;
cout << "a="<<a << " " <<"ref="<<ref << endl ;
ref =100 ;
cout <<"a="<<a << " " <<"ref=" <<ref << endl ;

int b = 20 ;
ref = b ; // a prend donc la valeur de b

cout << "a="<<a << " " <<"ref="<<ref << endl ;


ref-- ; // décrémentation de a

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 30


// n’affecte en aucun cas ce que ref pointe
cout << "a="<<a << " " <<"ref="<<ref << endl ;

return 0 ;
}

4-5 Opérateurs d’allocation dynamique


C++ propose deux opérateurs d’allocation dynamique, new et delete. Ces opérateurs sont
utilisés pour allouer ou libérer de la mémoire lors de l’exécution. L’allocation dynamique est
une fonctionnalité largement employée. C++ reconnait également les fonctions d’allocation
dynamique que sont malloc() et free(), pour assurer la compatibilité avec C. En revanche
en C++, vous devez utiliser les opérateurs new et delete qui présentent de nombreux
avantages.
L’opérateur new alloue de la mémoire et retourne un pointeur sur le début de la zone
mémoire correspondante. L’opérateur delete libère la mémoire préalablement allouée à
l’aide de l’opérateur new. La syntaxe qui prévaut avec new et delete est :
p_var = new type ;
delete p_var ;
Ici p_var est une variable qui pointe sur une zone de mémoire assez grande pour recevoir un
élément de type type.
En cas d’échec de new, une exception de type bad_alloc (selon le standard C++) est
générée. Cette exception est définie dans l’inclusion <new>.
L’exemple suivant illustre l’utilisation des opérateurs new et delete.
#include <iostream>
#include <new>
using namespace std ;

int main()
{
int *p ;
try {
p = new int ; // allocation de la zone mémoire utile pour un
entier
} catch (bad_alloc xa) {
cout << "Echec de l’allocation mémoire" << endl ;
return 1 ;
}
*p = 100 ;
cout << " a l'adresse "<< p << endl;
cout << "il y a la valeur " << *p << endl ;

delete p ;

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 31


4-5-1 Initialisation de la mémoire allouée
Vous pouvez initialisez la mémoire en précisant un argument d’initialisation après le nom du
type, dans la syntaxe de l’opérateur new. Voici la syntaxe générale de l’opérateur new pour
une initialisation.
p_var = new var_type (argument_d’initialisation) ; .

Il faut bien entendu que l’argument d’initialisation de type soit compatible avec le type de
donnée pour lequel la zone mémoire a été allouée. Vous pouvez remplacer dans le
programme précédent, la ligne d’allocation dynamique par la ligne suivante :
p = new int(87); // initialisation à 87

4-5-2 Allocation de tableaux


Il est également possible d’allouer des tableaux en utilisant l’opérateur new. La syntaxe est la
suivante :
p_var = new type_tableau [taille] ; // taille spécifie le nombre d’éléments du tableau
De la même façon, il est possible d’appliquer delete pour libérer la mémoire d’un tableau :
delete [ ] p_var ;
Les crochets [ ] précisent à l’opérateur delete qu’il s’agit d’un tableau. L’exemple suivant
utilise cette syntaxe :
#include <iostream>
#include <new>
using namespace std ;

int main()
{
int *p;
try {
p = new int [10]; // allocation d’un tableau de 10 entiers
}catch (bad_alloc xa) {
cout << "Echec de l’allocation mémoire" << endl ;
return 1 ;
}
for(int i =0 ; i<10 ; i++)
p[i] = i ;

for(int j=0 ; j <10 ; j++)


cout <<"p["<< j <<"]="<< p[j] <<endl;

delete [ ] p ; //libère la mémoire


return 0 ;
}

Notez qu’il ne peut pas y avoir d’argument d’initialisation lors de l’allocation dynamique de
tableaux.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 32


4-5-3 Allocation dynamique d’objets
L’allocation dynamique d’objets est également possible avec l’opérateur new. De cette façon
un objet est créé et un pointeur sur celui-ci est retourné. L’objet créé dynamiquement ne
diffère en rien d’un objet crée autrement. Cette allocation fait appel au constructeur (s’il en
possède un) et le destructeur sera appelé lors de la libération. Voici un exemple :
#include <iostream>
#include <new>
using namespace std ;

class balance{
double cur_bal ;
char name[80] ;
public :
void set(double n, char *s){
cur_bal = n ;
strcpy(name, s) ; }
void get_bal(double &n, char *s){
n = cur_bal ;
strcpy(s,name) ; }
} ;

int main()
{
balance *p ;
char s[80] ;
double n ;

try {
p = new balance ;
}catch (bad_alloc xa) {
cout << "Echec d’allocation " << endl ;
return 1 ;
}
p->set(12387.87, "Papa") ;
p->get_bal(n,s) ;

cout << s << endl ;

delete p ;

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 33


Chapitre 5 – Constructeurs par copie et arguments par défaut
d’une fonction
Les constructeurs sont les fonctions les plus couramment surchargées en C++, et plus
particulièrement les constructeurs par copie. La notion d’argument par défaut est très liée à la
surcharge de fonctions et peut parfois lui servir d’alternative.

5-1 Constructeurs par copie


Comme pour les autres fonctions, les constructeurs peuvent être surchargés en C++. L’une
des formes les plus importantes de surcharge de constructeur est le constructeur par copie.
Définir un constructeur par copie vous permet de pallier les éventuels problèmes qui naissent
quand un objet est créé à partir d’un autre.
Par défaut, quand un objet est utilisé pour en créer un autre, le C++ effectue une copie
membre à membre. Ainsi, une copie identique à l’objet d’initialisation est créé dans l’objet
cible. Bien que cela convienne dans beaucoup de cas, il existe des situations où une copie
membre à membre soit inadaptée.
Pour pallier à certains types de problèmes liés à la copie membre à membre, C++ autorise la
création de constructeurs de copie, que le compilateur applique quand un objet en initialise
un autre. Quand un constructeur par défaut existe, on évite la copie membre à membre par
défaut. La forme la plus courante de constructeur par copie est :
nomclasse (const nomclasse &o) {
// corps du constructeur
}
Ici, o est une référence sur l’objet dans la partie droite de l’initialisation. Il peut y avoir
plusieurs paramètres. Mais dans tous les cas, le premier paramètre doit être une référence sur
l’objet d’initialisation.
Il est important de comprendre que C++ définit deux situations distinctes, où la valeur d’un
objet est attribuée à un autre. La première est l’affectation. La seconde est l’initialisation, qui
peut apparaître sous trois formes :
 Quand un objet en initialise explicitement un autre, comme lors d’une déclaration ;
 Quand une copie d’objet s’impose pour passer en argument d’une fonction ;
 Quand un objet temporaire est généré (le plus souvent, en tant que valeur de retour
d’une fonction).
Le constructeur par copie ne s’applique qu’aux initialisations. Par exemple, supposons une
classe maclasse et un objet y de type maclasse ; chacune des instructions suivantes invoque
une initialisation.
maclasse x = y ; //y initialisation explicitement x
fonc(y) ; //y passé comme paramètre

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 34


y = fonc() ; //y reçoit en retour un objet temporaire

Le programme suivant crée une classe de tableau. Dans la mesure où l’espace pour le tableau
est alloué avec new, un constructeur par copie est prévu pour allouer la mémoire quand un
objet tableau sert à en initialiser un autre.

#include <iostream>
#include <new>
#include <cstdlib>
using namespace std ;

class tableau {
int *p ;
int taille ;
public :
tableau(int t) {
try {
p = new int[t] ;
}catch (bad_alloc xa){
cout << "Echec allocation" << endl ;
exit(EXIT_FAILURE) ;
}
taille = t ;
}

~tableau(){ delete [ ] p ;}

// constructeur par copie


tableau (const tableau &a) ;

void set(int i, int j){


if( i>=0 && i<taille) p[i] =j ;
}
int get(int i){
return p[i] ;

}
} ;
// constructeur par copie
tableau::tableau(const tableau &a){
try {
p = new int[a.taille] ;
}catch(bad_alloc xa){
cout << "Echec allocation" << endl ;
exit(EXIT_FAILURE) ;
}
for(int i=0 ; i<a.taille ; i++) p[i] = a.p[i] ;
}

int main()
{
tableau num(10) ;
int i ;
for(i=0 ; i<10 ; i++) num.set(i,i) ;
for(i=9 ;i >=0 ; i--) cout << num.get(i)<<endl ;
cout << endl ;

//creation d’un autre tableau initialisé avec num

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 35


tableau x(num) ; // invoque le constructeur par copie
for(i=0 ; i<10 ; i++) cout << x.get(i)<<endl ;

return 0 ;
}

5-2 Arguments par défaut d’une fonction


Le C++ permet à une fonction d’affecter une valeur par défaut à ses paramètres quand aucun
argument correspondant n’est spécifié lors de son appel. La valeur par défaut est alors
spécifiée par la même syntaxe qu’une initialisation de variable. Par exemple, voici la
déclaration de mafonc() qui prend en argument double dont la valeur par défaut est 0.0 :
void mafonc (double d = 0.0)
{
…..

}
Cette fonction peur alors être invoquée de deux manières différentes, comme le montre les
exemples suivants :
mafonc (198.25) ; // passe une valeur explicitement
mafonc () ; //laisse la fonction utiliser la valeur par défaut
L’exemple ci-dessous utilise une fonction ayant des arguments par défaut :

#include <iostream>
using namespace std ;

class Cube {
int x, y, z ;
public :
Cube(int i=0, int j =0, int k=0) {
x=i ;
y=j ;
z=k ;
}
int volume(){
return x*y*z ;
}
} ;

int main()
{
Cube a(2,3,4), b ;
cout << "volume pour a="<<a.volume() << endl ;
cout << "volume pour b="<<b.volume() << endl ;

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 36


Chapitre 6 - Héritage
L’héritage est un des principes fondamentaux de la Programmation Orientée Objet : il permet
de créer une structure de programmation hiérarchisée basée sur le concept de classe. Le
mécanisme d’héritage permet aussi de définir une classe générale, spécifiant les traits
communs dont seront dotés tous les éléments qui en dérivent. Ces derniers vont hériter ces
traits de cette classe mère (encore appelée classe de base) et ajouter leurs propres spécificités
qui vont les distinguer. Conformément à la terminologie C++, la classe qui hérite de la classe

de base est appelée classe dérivée.

6-1 Mode de dérivation de la classe de base


Lorsqu’une classe dérive d’une autre, les membres de la classe de base deviennent également
les membres de la classe dérivée. La syntaxe permettant de dériver une classe d’une autre est
la suivante :

Class nom_classe_dérivée : mode_acces nom_classe_de_base {


// corps de la classe
};
mode_acces définit le mode de dérivation, c’est-à-dire qu’il contrôle les accès à la classe de
base depuis la classe dérivée. mode_acces peut être l’un des mots suivants : public, private
ou protected.
Si aucun de ces trois mots n’est présent, le mode de dérivation par défaut est private, si la
classe dérivée est une Class. S’il s’agit d’une classe struct, alors public devient le mode
par défaut.
Lorsque le mode d’accès est public, tous les membres publics de la classe de base
deviennent des membres public de la classe dérivée. De la même façon, les membres
protected deviennent ses membres protected.
En revanche, dans tous les cas, les membres private de la classe de base restent
inaccessibles par les membres de la classe dérivée.
Lorsque le mode de dérivation entre la classe dérivée et la classe de base est private, tous
les membres public et protected de la classe de base deviennent des membres private
de la classe dérivée.
Les deux exemples suivants illustrent ces problèmes d’héritage.
Exemple1 :
#include <iostream>
using namespace std ;

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 37


class Base {
int i, j ;
public :
void set(int a, int b) { i = a, j=b ;}
void show(){ cout <<"i="<< i << " "<<"j="<<j << endl; }
} ;
// définition d’une classe dérivée en mode public
class Derived : public Base {
int k ;
public :
Derived(int x){ k = x ;}
void showk(){ cout << "k="<<k << endl ;}
} ;

int main()
{
Derived ob(3) ;
ob.set(1, 2) ; // accès possible aux membres de la classe de base
ob.show() ; // accès possible aux membres de la classe de base

ob.showk() ; // utilisation des membres de la classe dérivée


return 0 ;
}

Exemple 2 : Dérivation en mode private (les membres public de la classe de base


deviennent maintenant des membres private de la classe dérivée)
#include <iostream>
using namespace std ;

class Base {
int i, j ;
public :
void set(int a, int b) { i = a, j=b ;}
void show(){ cout <<"i="<< i << " "<<"j="<<j << endl; }
} ;

class Derived : private Base {


int k ;
public :
Derived(int x){ k = x ;}
void showk(){ cout << k << endl ;}
} ;

int main()
{
Derived ob(3) ;
ob.set(1, 2) ; // erreur accès refusé
ob.show() ; // erreur

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 38


6-2 Héritage et membres protected
Lorsqu’un membre d’une classe est déclaré comme protected, il est exclusivement accessible
par les autres membres de la classe. Une seule caractéristique distingue en fait un membre
protected d’un membre private: il s’agit de son comportement lorsqu’il est hérité, qui
diffère de celui d’un membre privé.
Comme expliqué dans la section précédente, un membre privé d’une classe de base n’est pas
accessible par le reste du programme et cela même depuis ses classes dérivées.
Les membres protected, restent en revanche accessibles pour les classes dérivées. Dans le
cas d’une « dérivation » en mode public, les membres protected de la classe de base
deviennent également protégés dans la classe dérivée et restent ainsi accessible de l’intérieur
de la classe.
En utilisant protected, vous créer des membres de classe qui sont privés pour leur classe,
mais qui peuvent quand même être hérités et accessibles pour les classes dérivées.
Voici un exemple :

#include <iostream>
using namespace std ;

class Base {
protected :
int i, j ; // privés pour la classe mère, mais accessible par
les classes dérivées.
public :
void set(int a, int b) { i = a, j=b ;}
void show(){ cout <<"i="<<i << " " <<"j="<<j<< endl ;}
} ;

class Derived : public Base {


int k ;
public :
// possibilité d’accéder à i et j
void setk(){ k= i*j ;}
void showk(){ cout <<"k="<<k << endl ;}

} ;

int main()
{
Derived ob ;
ob.set(2, 3) ; // Ok, connu par les classe derivée
ob.show() ; // Ok

ob.setk() ;
ob.showk() ;
return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 39


6-3 Héritage en mode protected
L’héritage d’une classe mère peut être spécifié en mode protected. Dans ce cas, tous les
membres publics et protégés de la classe mère deviennent des membres protected pour la
classe dérivée. Voici un exemple :

#include <iostream>
using namespace std ;
class Base {
protected :
int i, j ; // restent accessible pour les classes dérivées.
public :
void setij(int a, int b) { i = a, j=b ;}
void showij(){ cout << i << " " << j << endl ;}
} ;
// Dérivation de la classe en mode protected
class Derived : protected Base {
int k ;
public :
// la classe dérivée peut accéder aux membres hérités
void setk(){ setij(10,12) ; k = i*j ;}
void showall(){ cout << k << " " << endl ; showij() ;}

} ;
int main()
{
Derived ob ;
ob.setij(2, 3) ; // Impossible setij est un membre protégé

ob.setk() ; // ok
ob.showall() ; // ok

ob.showij() ; // impossible

return 0 ;
}

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 40


Chapitre 7- Fonctions virtuelles et polymorphisme
Le polymorphisme est pris en charge par C++, tant au moment de la compilation que lors de
l’exécution. Le polymorphisme de compilation est obtenu par la surcharge des fonctions et
des opérateurs. Le polymorphisme d’exécution est quant à lui réalisé en exploitant l’héritage
et les fonctions virtuelles qu’on va voir dans ce chapitre.

7-1 Fonctions virtuelles


Une fonction virtuelle est une fonction membre, déclarée dans une classe de base et qui est
destinée à être redéfinie dans les classes dérivées. Pour créer une fonction virtuelle, il suffit de
faire précéder sa déclaration dans la classe mère par le mot clé virtual.
Quand une classe contenant une fonction virtuelle est héritée, la classe dérivée peut redéfinir
celle-ci pour qu’elle convienne à ses besoins.
Dans le principe, les fonctions virtuelles implémentent la philosophie « une interface, plusieurs
méthodes » qui est sous-jacent au polymorphisme.
Voici un exemple qui utilise une fonction virtuelle :
#include <iostream>
using namespace std ;

class Base {
public :
virtual void vfunc() { cout << "on est dans vfunc de Base." <<
endl;}
} ;

class Derived1 : public Base {


public :
void vfunc() { cout << "on est dans vfunc de Derived1." << endl;}

} ;

class Derived2 : public Base {


public :
void vfunc() { cout << "on est dans vfunc de Derived2." << endl;}

} ;

int main()
{
Base *p, b;
Derived1 d1 ;
Derived2 d2 ;
p = &b ;
p-> vfunc() ; // accès à vfunc de Base

p = &d1 ;
p-> vfunc() ; // accès à vfunc de Derived1

p= &d2 ;
p-> vfunc() ; // accès à vfunc de Derived2

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 41


return 0 ;
}

Note : Tout pointeur sur une classe mère peut être utilisé pour pointeur sur un objet d’une
classe dérivée de celle-ci. Quand un pointeur de base pointe sur une classe dérivée qui
contient une fonction virtuelle, C++ détermine quelle version de la fonction doit être
invoquée en se fondant sur le type de l’objet pointé. Ce choix intervient lors de l’exécution.
Ainsi, quand des objets différents sont pointés, des versions distinctes de la fonction sont
exécutées. C’est le polymorphisme d’exécution.
A première vue, la redéfinition d’une fonction virtuelle dans une classe dérivée peut paraître
similaire à la surcharge de fonction. Cependant, ce n’est pas le cas et le terme surcharge ne
s’applique pas aux fonctions virtuelles, car il existe quelques différences. La plus significative
est probablement que le prototype d’une fonction virtuelle redéfinie doit correspondre
exactement au prototype spécifié dans la classe mère.

7-2 Appel d’une fonction virtuelle par une référence sur une classe mère

Dans l’exemple précédent, une fonction virtuelle était invoquée par l’intermédiaire d’un
pointeur sur classe mère ; mais la nature polymorphe d’une fonction virtuelle est également
accessible par un appel par le biais d’une référence sur classe mère comme le montre
l’exemple suivant (on utilise les classes du paragraphe précédent):

void f(Base &r){


r.vfunc() ;
}
int main()
{
Base b;
Derived1 d1 ;
Derived2 d2 ;
f(b) ; // passe un objet de Base à f
f(d1) ; // passe un objet de Derived1
f(d2) ; // passe un objet de Derived2

return 0 ;
}

7-3 Fonction virtuelle pure


Lorsqu’une fonction virtuelle n’est pas redéfinie dans une classe dérivée, c’est la version de la
classe mère qui est exécutée. Cependant, dans certaines situations, il se peut qu’il n’y ait pas
de définition significative pour la fonction dans la classe mère. Cela peut être le cas si une
classe mère ne définit pas suffisamment un objet pour qu’une fonction virtuelle s’y applique.

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 42


Pour gérer ce genre de situation, C++ autorise les fonctions virtuelles pures.
Une fonction virtuelle pure est une fonction virtuelle qui n’a pas de définition au sein de la
classe mère. Pour déclarer une fonction virtuelle pure, il faut utiliser la forme générale :
virtual type nom_fonction(liste_paramètre)=0 ;
Quand une classe possède une fonction virtuelle pure, toutes ses dérivées doivent fournir leur propre
définition. Si ce n’est pas le cas une erreur de compilation apparaît.

Voici un exemple d’utilisation d’une fonction virtuelle pure :


#include <iostream>
using namespace std ;

class Nombre{
protected:
int val;
public:
void setval(int i){val =i;}
// affiche() est une fonction virtuelle pure
virtual void affiche() = 0;
};

class Nombre1:public Nombre{


public:
void affiche(){
cout << "Nombre1 : "<< val << endl;}

};

class Nombre2:public Nombre{


public:
void affiche(){
cout << "Nombre2 : "<<val << endl;}

};

class Nombre3:public Nombre{


public:
void affiche(){
cout << "Nombre3 : " << val << endl;}
};

int main()
{
Nombre1 N1;;
Nombre2 N2;
Nombre3 N3;

N1.setval(20);
N1.affiche();

N2.setval(22);
N2.affiche();

N3.setval(23);
N3.affiche();

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 43


return 0 ;
}

7-4 Classe abstraite


Une classe qui contient au moins une fonction virtuelle pure est dite abstraite. Dès qu’une
classe est abstraite, elle devient non instanciable (c’est-à-dire, qu’il n’est plus possible de créer
un objet de cette classe). En revanche une classe abstraite constitue un type incomplet sur
lequel on va se fonder pour dériver d’autres classes.
Bien qu’il soit impossible de créer des objets de classes abstraites, il est possible de créer des
pointeurs et des références sur ces classes. Cela permet aux classes abstraites de prendre en
charge le polymorphisme d’exécution, qui s’appuie sur des pointeurs ou des références sur
classe mère pour sélectionner la fonction virtuelle appropriée.

---------------------------------------------- FIN -------------------------------------------------

Cours de C++ proposé par B. ONDAMI, email : bondami@gmail.com Page 44

Vous aimerez peut-être aussi