1

LE LANGAGE C++
Rév B
auteur: Denis MADEC
dma@magic.fr
2
2
PLAN DU COURS
INTRODUCTION
L'APPROCHE OBJET
LE LANGAGE C++
– PRESENTATION DE C++
– CLASSES ET OBJETS
– OBJETS, TABLEAUX ET POINTEURS
– ARGUMENTS PAR DEFAUT
– MOT-CLE THIS
– FONCTIONS ET METHODES INLINE
– VARIABLES ET METHODES DE CLASSE
– LA NOTION DE REFERENCE
– LE PASSAGE D'ARGUMENTS
– LE RETOUR D'OBJETS
– LES CONSTANTES
– ALLOCATION DYNAMIQUE DE MEMOIRE
– LE CONSTRUCTEUR DE COPIE
Toute représentation ou reproduction intégrale ou partielle faite sans le consentement de l'auteur
Denis MADEC ou de ses ayants droits ou ayant cause est illicite selon le code de la propriété
intellectuelle du 1er juillet 1992 et constitue une contefaçon réprimée par le code pénal.
3
3
PLAN DU COURS
LE LANGAGE C++ (suite)
– LA SURCHARGE DE L'OPERATEUR=
– FONCTIONS ET CLASSES AMIES
– SURCHARGE D'OPERATEURS
– LES CLASSES INTERNES
– LA COMPOSITION
– L'HERITAGE
– LE POLYMORPHISME
– IDENTIFICATION DE TYPE A L'EXECUTION (RTTI)
– LES EXCEPTIONS
– LA GENERICITE
LA BIBIOTHEQUE STL
BIBLIOGRAPHIE
4
4
INTRODUCTION
le langage C++ a une vingtaine d'années et son avenir, à long terme
sans doute compromis, reste dans l'immédiat plutôt radieux
c'est encore aujourd'hui le langage objet le plus utilisé dans le monde
et ses domaines d'utilisation sont extrêmement diversifiés
son apprentissage, réservé aux connaisseurs du langage C qu'il
englobe, reste long et difficile
ce support n'a pas pour ambition de couvrir toutes les subtilités du
langage C++, mais d'aller à l'essentiel, de façon à permettre
rapidement la conception et la réalisation de programmes objets en
C++
ce document n'est qu'un support de cours, dont le rôle est d'appuyer
une formation avec exercices dispensée par un formateur
5
L'APPROCHE OBJET
LA PROGRAMMATION CLASSIQUE
LA PROGRAMMATION OBJET
6
6
LA PROGRAMMATION
CLASSIQUE
La programmation classique s'attache à découper le problème à
résoudre en entités fonctionnelles
les entités fonctionnelles se préocupent peu des données sur
lesquelles elles agissent
dans le meilleur des cas, les données sont protégées au sein d'une
entité fonctionnelle, car connues d'elle seule
dans d'autres cas, les données sont partagées par plusieurs entités
fonctionnelles
7
7
LA PROGRAMMATION
OBJET
les concepteurs des langages objet se sont attachés à trouver des
solutions pour résoudre les problèmes rencontrés en programmation
classique:
– faible protection des données d'ou un grand nombre de bogues potentiels
– faible réutilisabilité des développements effectués
– difficulté à modifier des logiciels existants sans compromettre leur fiablité
– maintenance rendue difficile par la dépendance des entités fonctionnelles
entre elles
8
LA PROGRAMMATION
OBJET
LES PRINCIPES
LES FACTEURS DE QUALITE D'UN LOGICIEL
LA TERMINOLOGIE
LES PROPRIETES DE L'APPROCHE OBJET
LES PRINCIPAUX LANGAGES ORIENTES OBJET
9
9
LES PRINCIPES
Masquage de l'information
– l'information n'est disponible que si l'on en a un réel besoin
Réutilisabilité
– évite la ré-écriture de code
Extensibilité
– permet d'étendre les fonctionalités sans impact majeur sur les développements
déjà effectués
10
10
LES FACTEURS DE
QUALITE D'UN LOGICIEL
Exactitude
– pour des données valides en entrée, les sorties sont exactes
Robustesse
– pour des données erronées en entrée, les sorties sont "raisonnables"
Stabilité
– le logiciel peut être modifié sans remise en cause majeure des développements
Fiabilité
– le logiciel est robuste et fiable
Efficacité
– cela est lié à la performance et dépend des caractéristiques propres au logiciel
11
11
LA TERMINOLOGIE
OBJET
METHODE
MESSAGE
CLASSE
HIERARCHIE DE CLASSE
12
12
OBJET
un objet comporte deux types de membres:
– des données ou attributs
– des fonctions ou méthodes
l'objet est un groupement d'attributs et de méthodes avec une
interface parfaitement définie avec l'extérieur
les objets communiquent entre eux par des messages, complétés ou
non par des paramètres
un sélecteur permet d'identifier le message
13
13
OBJET
l'objet récepteur du message déclenche une méthode lui appartenant
correspondant au sélecteur
il peut retourner à l'objet émetteur du message une donnée (qui peut
être un objet)
en Smalltalk, les messages sont traités par la bibliothèque
d'exécution
– les messages et les méthodes sont distincts
en C++ ou Java, les messages sont traités par le compilateur
– un message est l'appel direct à une méthode
14
14
OBJET
La manipulation de plusieurs objets nécessite que chaque objet
puisse être identifié
– en Smalltalk, l'identificateur est un entier
– en C++ ou Java, l'identificateur est l'adresse de l'objet
un objet possède:
– un état (valeur de ses attributs)
– un comportement (dicté par ses méthodes)
– une identité (son nom)
la structure et le comportement d'objets similaires sont définis dans
leur classe commune
15
15
CLASSE
une classe est un modèle pour la création d'objets et représente la
nature des objets qu'elle permet de créer
une classe définit:
– les attributs
– les méthodes
– l'interface avec l'extérieur
les objets d'une même classe sont des instances de cette classe:
– ils ont les mêmes méthodes
– ils ont le même type d'attributs
– ils peuvent avoir et ont en général des valeurs différentes pour leurs attributs
16
LES PROPRIETES DE
L'APPROCHE OBJET
L'ENCAPSULATION
L'HERITAGE
LE POLYMORPHISME
17
17
L'ENCAPSULATION
l'encapsulation est le fait d'empaqueter ensemble données et
fonctions avec une visibilité réduite depuis l'extérieur
la mise en oeuvre de l'encapsulation est effectuée par les classes
les attributs et méthodes sont masqués pour les objets qui n'ont pas
besoin d'y accéder
le masquage est défini dans une classe par une interface qui précise
quels sont les membres (attributs ou méthodes) accessibles depuis
l'extérieur
18
18
L'ENCAPSULATION
le masquage des membres d'une classe permet d'utiliser les objets de
cette classe en ayant un accès limité à leurs membres
les méthodes d'une classe peuvent être:
– privées, donc inaccessibles par un autre objet
– publiques, c'est-à-dire qu'un message provenant d'un autre objet peut les
déclencher
les attributs devraient toujours rester privés
– C++ et Java autorisent les attributs à être publics, cependant il est
recommandé de laisser les attributs privés pour conserver une bonne
encapsulation
19
19
L'HERITAGE
l'héritage traduit la relation EST-UN de notre langage
– un employé est une personne
– un employé hérite de personne
la relation EST-UN ne doit pas être confondue avec la relation
A-UN qui est traduite par la composition
– une personne a une tête, des jambes, des bras
– il n'y a pas dans ce cas de relation d'héritage entre tête, jambes ou bras
20
20
L'HERITAGE
l'héritage met en relation au moins deux classes:
– la classe du dessus est appelée classe parente ou classe de base ou super-
classe
– la classe du dessous est appelée classe fille ou classe dérivée ou sous-classe
si une classe Employe hérite d'une classe Personne (car un Employe
EST-UNE Personne):
– Personne est la classe de base ou super-classe
– Employe est la classe dérivée ou sous-classe
21
21
L'HERITAGE
plusieurs classes peuvent hériter d'une même classe
– la classe Employe hérite de la classe Personne
– la classe Actionnaire hérite de la classe Personne
une même classe peut hériter de plusieurs classes de base
– la classe Directeur hérite de la classe Employe et de la classe Actionnaire car
un Directeur EST-UN Employe ET un Actionnaire
22
22
L'HERITAGE
une classe dérivée hérite, c'est-à-dire bénéficie des biens de sa classe
de base:
– de ses attributs
– de ses méthodes
l'héritage est transitif, par conséquent une classe dérivée hérite de la
classe de base de sa classe de base
une hiérarchie de classe décrit les relations d'héritage entre plusieurs
classes
23
23
L'HERITAGE
l'héritage apporte une technique de réutilisation:
– ce qui a été développé pour une classe de base peut être réutilisé par ses
classes dérivées
l'héritage traduit la spécialisation si l'on considère les classes
dérivées
– un Employe est une Personne avec un salaire
il traduit la généralisation si l'on considère les classes de base
– une Personne représente des caractéristiques et le comportement commun des
Employes et des Actionnaires
24
24
LE POLYMORPHISME
le polymorphisme est la propriété qui permet de manipuler des
objets sans tenir compte de leur classe
cette propriété découle de l'héritage et ne peut être mise en oeuvre
qu'à travers ce type de relation
le polymorphisme permet de manipuler un objet d'une classe dérivée
en le considérant comme un objet de sa classe de base, tout en
conservant sa spécificité (comportement spécialisé)
la mise en oeuvre du polymorphisme permet de réutiliser du code en
apportant des modifications
25
LES PRINCIPAUX
LANGAGES OBJETS
SMALLTALK
EIFFEL
OBJECTIVE C
ADA
C++
JAVA
26
26
SMALLTALK
Smalltalk est l'un des langages orienté objet les plus populaires
il a été développé au PARC (Palo Alto Research Center) de Xerox
dans les années 1970
il s'agit d'un environnement de développement
il comporte la modification dynamique des instances de classe
il est assez utilisé outre-Atlantique, mais les liens dynamiques
pénalisent les performances
27
27
EIFFEL
on doit ce langage à un français, Bertrand Meyer
il s'agit d'un environnement orienté objet propriétaire pour stations
UNIX
il comporte une petite bibliothèque de classes prédéfinies
ce langage supporte la gestion des exceptions, la généricité,
l'héritage multiple
la gestion mémoire est automatique mais peut être contrôlée
le compilateur traduit les programmes source en C
28
28
OBJECTIVE C
ce langage est compatible avec C et complète celui-ci par une
approche objet simplifiée
son intégration avec C n'est pas aussi poussée que C++
il adopte la syntaxe de Smalltalk et supporte l'héritage multiple et les
bibliothèques génériques
le système d'ecploitation NextStep de la société Next a été écrit en
Objective C
29
29
ADA
le langage ADA fut développé sur appel d'offres du Department of
Defense (DoD) des Etats-Unis vers la fin des années 1970
il fallait offrir un langage robuste et fiable à l'armée américaine
la proposition d'un français, René Ichbiack, fut retenue
sa normalisation ANSI intervient en 1983
la dernière version est ADA 95
il s'agit d'un langage procédural, non purement objet, qui comporte
des notions comme les packages, les types privés, les exceptions, la
généricité, les tâches, l' héritage simple, les constructeurs et
destructeurs
son utilisation principale se situe dans le domaine militaire et aéro-
spatial, pour le développement de grand projets
le nom ADA provient du nom de la contesse de Lovelace, Augusta Ada Byron
Ada fut assistant de Charles Babbage qui inventa la première machine à calculer
30
30
C++
le C++ a été développé aux laboratoires AT&T de Bell au début des
années 1980 par Bjarne Stroustrup
il a été conçu pour être compatible avec le C, qu'il améliore sur de
nombreux points
il s'agit d'un langage hybride, qui n'oblige pas à la programmation
objet
il ne comporte pas de bibliothèque de classes standard
chaque éditeur fournit sa propre bibliothèque de classes
31
31
C++
il s'agit d'un langage puissant, comportant des classes, les
exceptions, la généricité, l'héritage multiple, les constructeurs et
destructeurs, la surcharge d'opérateurs
la gestion de l'allocation dynamique de mémoire est entièrement à la
charge du programmeur
C++ est un langage complexe, mais efficace
32
32
JAVA
JAVA a été développé chez Sun Microsystems en 1991 pour le
développement de logiciels enfouis concernant les appareils de
grande consommation (télévision, magnétoscopes etc...)
il est devenu rapidement un langage de prédilection pour la
distribution de programmes exécutables via le World Wide Web
il a pris son essor après le développement en 1994 du navigateur
HotJava de Sun permettant l'exécution d'applets Java
33
33
JAVA
Java est fortement inspiré de C++ mais certaines fonctionnalités
considérées commes dangereuses (pointeurs, héritage multiple) ou
superflues (surcharge d'opérateurs) ont été abandonnées.
Java apporte d'autres fonctionnalités comme le multithreading
inspiré du langage ADA
Java comporte une bibliothèque de classes standard qui peut être
complétée par d'autres classes
34
LE LANGAGE C++
PRESENTATION DE C++
CLASSES ET OBJETS
OBJETS, TABLEAUX ET POINTEURS
ARGUMENTS PAR DEFAUT
MOT-CLE THIS
FONCTIONS ET METHODES INLINE
VARIABLES ET METHODES DE CLASSE
LA NOTION DE REFERENCE
LE PASSAGE D'ARGUMENTS
LE RETOUR D'OBJETS
LES CONSTANTES
ALLOCATION DYNAMIQUE DE MEMOIRE
LE CONSTRUCTEUR DE COPIE
35
LE LANGAGE C++
(Suite)
LA SURCHARGE DE L'OPERATEUR=
FONCTIONS ET CLASSES AMIES
SURCHARGE D'OPERATEURS
LES CLASSES INTERNES
LA COMPOSITION
L'HERITAGE
LE POLYMORPHISME
IDENTIFICATION DE TYPE A L'EXECUTION (RTTI)
LES EXCEPTIONS
LA GENERICITE
36
PRESENTATION DE C++
HISTORIQUE
CARACTERISTIQUES PRINCIPALES DU C++
COMPILATION
UN C AMELIORE
COMMENTAIRES
CONSTANTES
LE TYPE BOOL
CONVERSIONS
DEFINITION DE VARIABLES
PROTYPAGE DE FONCTIONS
EDITION DE LIENS
LES ESPACES DE NOMS
MOTS-CLES DU LANGAGE
PRIORITE DES OPERATEURS
37
37
HISTORIQUE
Bjarne Stroustrup, l'auteur du C++, s'est inspiré du langage C et de
Simula67 ainsi que d'Algol68
la première version de son langage s'appelait "C with classes" en
1980
cette première version a donné naissance en 1983 au langage C++
la version 1.2 du C++ est sortie en 1987 et a rencontré un vif succès
auprès des développeurs
les versions 2.0 puis 2.1 sortirent ensuite au début des années 1990
avec l'objectif d'une normalisation du langage
la version 3.0, sortie en 1998, se veut être la version ISO du langage
38
38
CARACTERISTIQUES
PRINCIPALES DU C++
le C++ est un langage hybride compatible avec le langage C,
permettant la réalisation de programmes objets, mais autorisant aussi
la réalisation de programmes non objets
C++ est sans aucun doute le langage le plus complexe, le plus
puissant aussi, permettant la réalisation de programmes très rapides à
l'exécution
il s'agit d'un langage réservé à des spécialistes qui ne peuvent pas
utiliser un autre langage pour les raisons suivantes:
– nécessité d'une grande rapidité à l'exécution
– nécessité ou souhait d'employer un langage objet
– compatibité nécessaire avec des logiciels existants en C ou C++
39
39
CARACTERISTIQUES
PRINCIPALES DU C++
des langages objets plus récents sont en effet disponibles, comme
Java ou C#, offrant globalement les mêmes possibilités pour une
plus grande simplicité
les domaines d'utilisation du C++ sont très nombreux, citons:
– l'informatique industrielle (temps réel)
– l'informatique scientifique (calculs)
– l'informatique des salles de marché et de la finance en général (IHM+calculs)
40
40
COMPILATION
la compilation d'un programme C++ s'effectue comme celle d'un
programme C, à l'aide d'un compilateur C++
sous LINUX, le compilateur GNU gcc intègre un compilateur C++
que l'on peut invoquer par la commande: g++
avec le compilateur gcc, les fichiers sources C++ doivent avoir une
extension .C, .cc, .cx ou .cpp
sous l'environnement Windows, des compilateurs commerciaux
comme Visual C++ de Microsoft, ou Borland C++ de Borland,
nécessitent des fichiers sources avec extension .cpp
41
41
UN C AMELIORE
le langage C, développé initialement par Brian Kernighan et Dennis
Ritchie en 1972, a été normalisé en 1989 et est alors devenu le C
ANSI
le langage C++ a, quant à lui, été mis au point au début des années
1980 par Bjarne Stroustrup, lequel a voulu englober le langage C en
lui apportant toutefois quelques amélioration substantielles
Bjarne Stroustrup a "saupoudré" le langage C de nombreuses
améliorations en l'introduisant dans le C++, conduisant ainsi à un C
amélioré, avec bien sûr une couche objet en plus
une liste non exhaustive des améliorations est décrite ci-après, la
plupart étant approfondies dans la suite du cours
Toute représentation ou reproduction intégrale ou partielle faite sans le consentement de l'auteur
Denis MADEC ou de ses ayants droits ou ayant cause est illicite selon le code de la propriété
intellectuelle du 1er juillet 1992 et constitue une contefaçon réprimée par le code pénal.
42
42
UN C AMELIORE
– Commentaires
– Constantes
– Le type bool
– Conversions
– Définition de variables
– Prototypage des fonctions
– Édition de liens
– Espaces de noms
– Surcharge de fonctions
– Arguments par défaut
– Fonctions inline
– Références
– Allocation dynamique de mémoire
– Fonctions génériques
– Entrées/sorties sous forme d'objets
43
43
COMMENTAIRES
en plus des symboles /*…*/ utilisés en C, un commentaire peut être
introduit en C++ par //
il prend fin automatiquement en fin de ligne
/* programme de …
. . .
*/
int main() { // fonction principale
// renvoie 0 si pas d'erreur
. . .
}
en aucun cas les commentaires ne doivent être imbriqués
44
44
CONSTANTES
en C, les constantes litérales peuvent être définies de deux manières
– par la directive #define du pré-processeur
– par le mot-clé const devant une définition de variable
const double pi=3.14159;
l'emploi de #define est déconseillé en C++ car il s'agit d'une simple
substitution effectuée par le pré-processeur que le compilateur ne
peut contrôler
C++ encourage la définition de constantes utilisant le mot-clé const
car il améliore leur mise-en-œuvre:
– une constante ne réserve pas nécessairement de la mémoire
– une constante exige une valeur d'initialisation
– elle a comme portée le fichier dans laquelle elle est définie
45
45
LE TYPE BOOL
un nouveau type a fait son apparition en C++: le type bool
il permet de représenter une variable ne prenant que deux valeurs
possibles: true, false
les opérateurs relationnels renvoient un bool
un bool peut être converti en int et un int en bool: true correspond à
1, false à 0 et réciproquement
46
46
CONVERSIONS
le C est très tolérant quant aux opérations effectuées sur les données,
notamment en ce qui concerne les conversions
C++ contrôle le type des données d'une façon plus stricte, nécessitant
parfois une conversion explicite là ou le C ne l'impose pas
double x;
int a;
//a=x; // accepté en C, refusé en C++
a=(int)x; // accepté en C, accepté en C++
de façon à rendre encore plus sûres les conversions explicites, une
nouvelle syntaxe a été mise en place, laissant toutefois le
compilateur accepter l'ancienne notation
47
47
CONVERSIONS
il existe dorénavant quatre sortes de conversion explicite:
static_cast, dynamic_cast, reinterpret_cast et const_cast
– la syntaxe est la suivante: static_cast<type>(donnée)
static_cast permet de convertir un type vers un type plus faible, par
exemple un long en int ou un double en int, ou un pointeur sur char
en pointeur sur int
– par exemple:
double x;
int a;
a=static_cast<int>(x); // ou a=(int)x;
48
48
CONVERSIONS
dynamic_cast est utilisé lorsque l'on manipule des objets dérivés via
des pointeurs ou des références (Cf chapitre "Identification de type à
l'exécution)
reinterpret_cast est utilisé pour des conversions extrêmes, par
exemple un int vers un pointeur, nécessitant une double
interprétation:
int a;
int *p=reinterpret_cast<int*>(a);
const_cast est utilisé uniquement pour ajouter mais surtout enlever
le spécificateur const à un type
const char* p="DMA";
char *d=const_cast<char*>(p);
49
49
DEFINITION DE
VARIABLES
en C, les variables locales doivent obligatoirement être définies en
début de bloc
en C++, une variable locale peut être définie n'importe où dans un
bloc, et utilisable à partir de l'endroit où elle a été définie
ceci s'applique particulièrement aux fonctions
void f() {
int a=3;
a++;
int b=5;
b*=a;
. . .
}
50
50
DEFINITION DE
VARIABLES
ceci permet également de définir une variable au sein d'une boucle,
cette variable n'existant plus en dehors de la boucle
for(int i=0;i<10;i++)
cout<<i<<endl;
}
// i n'existe plus ici!
while(int a=getValeur()) {
. . .
}
// a n'existe plus ici!
51
51
PROTOTYPAGE DES
FONCTIONS
en l'absence du prototype d'une fonction f avant son appel, le C
suppose qu'elle a été déclarée comme: int f(void)
le C++ renforce le contrôle lors de l'appel d'une fonction en exigeant
sa déclaration préalable (appelée prototype) de manière explicite
double calcul(int, double); // déclaration obligatoire en C++
int main() {
. . .
double x=calcul(5, 78.65);
. . .
}
52
52
EDITIONS DE LIENS
l'édition de liens C++ met en œuvre le "type safe linkage"
les fichiers objets générés par le compilateur C++ comportent les
noms des fonctions sous forme encodée de façon à permettre la
distinction entre fonctions surchargées
pour lier des fonctions C avec du code C++, et compte tenu des
différences de format entre les fichiers objets C et C++, il faut
indiquer quelles sont les fonctions C utilisées dans le code source
C++
ceci s'effectue au moyen d'une directive extern "C" qu'il faut placer
en dehors de toute fonction, de préférence dans un fichier d'entête
53
53
EDITIONS DE LIENS
entre double quotes figure le langage dans lequel les fonctions
appelées ont été compilées (il s'agit en fait de la forme de l'édition de
liens, "C" convenant également pour le Fortran ou l'assembleur par
exemple)
extern "C" void calcul(double,double); // déclaration d'une fonction C
extern "C" { // déclaration de plusieurs fonctions C
int f(int);
float g(double);
}
extern "C" { // déclaration de plusieurs fonctions C
#include <string.h> // d'une bibliothèque
}
54
54
EDITIONS DE LIENS
extern "C" { // déclaration de plusieurs fonctions C
int f(int);
float g(double);
}
int main() {
int b;
double x;
. . .
int a=f(b);
double z=g(x);
}
55
55
LES ESPACES DE NOMS
les espaces de noms (namespaces) ont pour objectif principal de
résoudre les conflits portant sur le nom donné aux identificateurs
dans un programme C++
on peut dès lors envisager de donner à une classe le même nom
qu'une classe de la bibliothèque par exemple
la syntaxe d'un espace de noms est la suivante:
namespace identificateur {
. . .
}
tout ce qui se trouve entre les accolades fait partie de l'espace de
noms appelé identificateur
56
56
LES ESPACES DE NOMS
toute classe, fonction, variable de cet espace de nom doit être
préfixée par le nom de l'espace suivi de :: pour être trouvée par le
compilateur
namespace alpha {
class A {
};
. . .
}
int main() {
alpha::A aa;
}
57
57
LES ESPACES DE NOMS
en pratique, cette écriture est principalement utilisée pour lever des
ambiguités, la directive using namespace étant plus facile d'emploi
namespace alpha {
class A {
};
. . .
}
using namespace alpha;
int main() {
A aa;
}
58
58
LES ESPACES DE NOMS
namespace alpha {
class A {
};
}
namespace beta {
class A {
};
}
using namespace alpha;
int main() {
A aa; // il s'agit de alpha::A
beta::A b;
}
59
59
LES ESPACES DE NOMS
la bibliothèque standard du C++ fournie avec tout compilateur est
définie dans l'espace de nom std, qu'il faut donc indiquer pour
utiliser un élément de cette bibliothèque
#include <iostream>
#include <string>
using namespace std;
int main() {
ifstream ficin("alpha.txt");
. . .
}
60
60
MOTS-CLES DU
LANGAGE
asm auto bool break case catch char class const
const_cast continue default delete do double dynamic_cast
else enum explicit extern false float for friend int long
goto if inline int long mutable namespace new operator
private protected public register reinterpret_cast return
short signed sizeof static static_cast struct switch template
this throw true try typedef typeid typename union
unsigned using virtual void volatile wchar_t while
bien que non utilisés, les mot-clés suivants sont également réservés:
and and_eq bit_and bit_or compl export not not_eq or
or_eq xor xor_eq
61
61
PRIORITE DES
OPERATEURS
évalués en premier
:: de gauche à droite
. -> [] () lvalue++ lvalue -- typeid() xxx_cast de gauche à droite
! ~ + - (cast) ++lvalue --lvalue *expr &lvalue sizeof new delete de droite à gauche
.* -> de gauche à droite
* / % de gauche à droite
+ - de gauche à droite
<< >> de gauche à droite
< > >= <= de gauche à droite
== != de gauche à droite
& de gauche à droite
^ de gauche à droite
| de gauche à droite
&& de gauche à droite
|| de gauche à droite
= += -= *= /= %= ^= &= |= <<= >>= de droite à gauche
?: de gauche à droite
throw de gauche à droite
, de gauche à droite
62
INTRODUCTION AUX
ENTREES/SORTIES
63
63
INTRODUCTION AUX
ENTREES/SORTIES
bien que les entrées/sorties du langage C continuent d'être
supportées, il est préférable d'utiliser les objets prédéfinis du C++,
ou d'utiliser les classes d'E/S disponibles pour créer ses propres obets
il existe 3 objets prédéfinis cin, cout, cerr correspondant
respectivement aux fichier standard d'entrée, fichier standard de
sortie, fichier standard d'erreur
par défaut, ces fichiers standards sont associés au clavier pour cin, à
l'écran pour cout et cerr
la mise en œuvre de ces objets s'effectue en utilisant l'opérateur <<
pour cout et cerr, >> pour cin
dans tous les cas, il faut inclure le fichier iostream.h qui décrit ces
objets
64
64
INTRODUCTION AUX
ENTREES/SORTIES
#include <iostream.h>
int main() {
int x;
double z;
cout<<"saisir une valeur entière:";
cin>>x;
cout<<"saisir une valeur décimale:";
cin>>z;
cout<<"valeurs saisies: "<<x<<", "<<z<<endl; // endl: end of line
return 0;
}
65
65
EXERCICES
Votre environnement de travail vous est présenté par votre
formateur. Il vous indique comment réaliser les exercices, comment
compiler et exécuter vos programmes
Exo1: premier programme C++ simple
Exo2: affichage de variables de types primitifs
Exo3: saisie d'un nombre décimal, calcul, affichage du résultat
66
CLASSES ET OBJETS
LA DEFINITION DE CLASSE
L'INSTANCIATION D'UNE CLASSE
L'APPEL DES METHODES
L'ACCES AUX MEMBRES
LA SURCHARGE DE METHODES
LES CONSTRUCTEURS
LE DESTRUCTEUR
67
67
LA DEFINITION DE
CLASSE
une classe est un modèle pour la création d'objets
elle comporte des données membres ou attributs ou données
d'instance
elle comporte des fonctions membres ou méthodes
elle définit l'interface d'accès avec l'extérieur pour ses membres
les attributs d'une classe peuvent être des types de base, des objets
il peut ne pas y avoir d'attributs dans une classe (seulement des
méthodes)
il peut ne pas y avoir de méthodes (seulement des variables)
par convention, le nom d'une classe commence par une majuscule
68
68
LA DEFINITION DE
CLASSE
class Counter { // nom de la classe
private:
int count; // attribut
public:
int increment() { // méthode
return ++ count;
}
void affiche() { // méthode
cout<< "valeur : " << count<<endl;
}
}; // point-virgule obligatoire
69
69
L' INSTANCIATION D'UNE
CLASSE
"instancier" une classe signifie créer un objet sur le modèle de cette
classe
– un objet est une instance de classe
tous les objets de cette même classe auront un comportement
identique
tous les objets de cette classe auront les mêmes types d'attributs
– chaque objet comportera ses propres attributs
les objets d'une même classe pourront avoir des états différents
il faut tout simplement créer une variable du type de la classe:
Counter c1; // c1 est une instance de la classe Counter
70
70
L' APPEL DES
METHODES
une méthode doit être appelée (invoquée) pour un objet donné:
Counter c1;
c1.affiche();
int x = c1.lirecount();
cout<<"compteur c1 : "<< c1.increment()<<endl;
71
71
L' ACCES AUX MEMBRES
les méthodes d'une classe ont toujours directement accès aux autres
membres de cette même classe (variables ou méthodes)
l'interface de la classe définit l'accès à ses membres depuis l'extérieur
de la classe, c'est-à-dire depuis une autre classe
l'interface d'accès aux membres d'une classe est défini par les mots-
clés:
public, protected, private
ces mots-clés définissent des sections dans lesquelles sont placés les
membres de la classe
par défaut, en l'absence de toute indication, c'est le spécificateur
private qui est utilisé
72
72
L' ACCES AUX MEMBRES
public
– un membre public est accessible de tout autre objet d'une autre classe
protected
– un membre protected est inaccessible de tout objet d'une autre classe sauf
d'une classe dérivée
private
– un membre private interdit son accès depuis tout objet d'une autre classe
73
73
L' ACCES AUX MEMBRES
un bon programme orienté objets doit conserver l'encapsulation des
données
les variables doivent donc rester privées dans la mesure du possible
les méthodes permettant d'y accéder seront publiques
bien que non recommandé, il est possible d'accéder aux attributs, si
l'interface défini dans la classe le permet:
c1.count = 0; // ok si count accessible
74
74
EXERCICE
Exo4: définition d'une classe, création d'objets de cette classe, ajout
d'une méthode
75
75
LA SURCHARGE DE
METHODES
le prototype d'une méthode (d'instance ou de classe) est composé de:
– son nom
– le type de chacun des arguments qu'elle reçoit
– son type de retour
la signature d'une méthode est composée de:
– son nom
– le type de chacun des arguments qu'elle reçoit
76
76
LA SURCHARGE DE
METHODES
la surcharge de méthodes (d'instances ou de classe) consiste à donner
le même nom à des méthodes différentes dans une même classe
le compilateur les différencie par leur signature (nombre et type des
arguments)
le type de retour n'intervient pas dans la surcharge
il s'agit d'une facilité d'écriture
il n'y a pas de limites aux nombres de surcharges d'une même
méthode
77
77
LA SURCHARGE DE
METHODES
class Date {
private:
int jour;
char mois[20];
int annee;
public:
void setDate (int j, char* m, int a) {
. .
}
void setDate (int numero_jour) { // méthode surchargée
. .
}
};
78
78
LA SURCHARGE DE
METHODES
int main()
{
int jour;
Date d;
. . .
d.setDate(30, « mars »,1957); // il s’agit de la méthode setDate(int,char*,int)
. . .
d.setDate(128); // il s’agit de la méthode setDate(int)
return 0;
}
79
79
EXERCICE
Exo5: surcharge de méthode dans la classe Counter
80
80
LES CONSTRUCTEURS
un constructeur est une méthode automatiquement invoquée lors de
la création d'un objet
– un constructeur n'est invoqué qu'une seule fois dans la vie d'un objet
– il sert essentiellement à initialiser les attributs
bien que non indispensable, un constructeur permet la création d'un
objet en précisant des valeurs initiales pour ses attributs
les constructeurs peuvent être surchargés
– le langage fournit systématiquement un constructeur dit "constructeur par
défaut"
– un constructeur explicite sans arguments remplace le constructeur par défaut
– s'il existe un constructeur avec argument(s), alors la construction d'un objet
par défaut nécessitera la présence d'un constructeur explicite sans argument
81
81
LES CONSTRUCTEURS
un constructeur doit respecter les règles suivantes:
– son nom est obligatoirement celui de sa classe
– il ne doit pas comporter de type de retour (même void)
– il ne peut être appelé explicitement
– il doit être accessible (donc public en général)
un constructeur ne peut être explicitement invoqué pour un objet, il
s'agit d'une méthode réservée pour le compilateur
un constructeur se doit d'être accessible depuis la méthode dans
laquelle on crée l'objet . Il est en général public
82
82
LES CONSTRUCTEURS
class Counter {
int count;
public:
Counter() { count=0; } // constructeur sans argument
Counter(int val) { count=val; } // constructeur avec argument
. . .
};
void main() {
Counter c1; // appel du constructeur sans argument
Counter c2(10); // appel du constructeur avec argument
c1.affiche(); // affiche 0
c2.affiche(); // affiche 10
}
83
83
LES CONSTRUCTEURS
autre notation possible en utilisant une liste d'initialisateurs de
membres:
class Counter {
int count;
public:
Counter():count(0) { } // constructeur sans argument
Counter(int val):count(val) { } // constructeur avec argument
. . .
};
cette notation est recommandée, et devient obligatoire lorsque des
attributs constants ou de type référence (Cf chapitre "Référence")
doivent être initialisés
84
84
EXERCICE
Exo6: mise en place de trois constructeurs dans la classe Counter
85
85
LE DESTRUCTEUR
un destructeur est une méthode automatiquement invoquée juste
avant la disparition d'un objet
sa présence est nécessaire lorsque certaines ressources telles que la
mémoire, des fichiers, des sockets qu'il faut libérer sont utilisés dans
l'objet
une classe ne peut comporter qu'un seul destructeur
son nom est constitué du nom de sa classe précédé du caractère ~
~Date(){
. .
}
un destructeur peut être appelé explicitement
86
86
EXERCICE
Exo7: mise en place d'un destructeur dans la classe Counter
87
87
LA DEFINITION DE
CLASSE
en pratique, la définition monolithique d'une classe dans un même
fichier source pose des problèmes lors de la compilation ou de
l'édition de liens de programmes comportant de nombreux fichiers
il est préférable de séparer la définition de la classe de la définition
des méthodes
la classe est alors définie dans un fichier d'entête .h et les méthodes
dans un fichier source
le fichier d'entête pourra être inclus (directive #include du pré-
processeur) dans tout fichier source ayant besoin d'utiliser le type
correspondant
Toute représentation ou reproduction intégrale ou partielle faite sans le consentement de l'auteur
Denis MADEC ou de ses ayants droits ou ayant cause est illicite selon le code de la propriété
intellectuelle du 1er juillet 1992 et constitue une contefaçon réprimée par le code pénal.
88
88
LA DEFINITION DE
CLASSE
fichier counter.h
class Counter {
private:
int count;
public:
int increment();
void affiche();
};
seuls les prototypes des méthodes figurent dans cette définition de la
classe
89
89
LA DEFINITION DE
CLASSE
fichier counter.cpp
#include "counter.h"
int Counter::increment() {
return ++ count;
}
void Counter::affiche() {
cout<< "valeur : " << count<<endl;
}
90
90
LA DEFINITION DE
CLASSE
les méthodes sont définies en indiquant leur nom précédé de leur
classe et suivi de l'opérateur ::
int Counter::increment() {
return ++ count;
}
il doit y avoir cohérence totale entre les éléments définis dans la
classe et les méthodes définies dans le fichier source
dans la suite de ce cours et par commodité, les classes seront le plus
souvent définies de façon monolithique
91
91
EXERCICE
Exo8: définir la classe Counter dans un fichier d'entête et ses
méthodes dans un fichier source
92
OBJETS, TABLEAUX ET
POINTEURS
93
93
OBJETS, TABLEAUX ET
POINTEURS
les objets peuvent avantageusement être manipulés via des pointeurs:
Counter c1(4), c2(8);
Counter *p=&c;
p->increment();
p->affiche();
p=&c2;
p->affiche();
94
94
OBJETS, TABLEAUX ET
POINTEURS
les tableaux d'objets peuvent être simplements créés en utilisant la
notation usuelle:
Counter tab[10]; // tableau tab de 10 objets Counter
le constructeur par défaut ou le constructeur sans argument est
automatiquement appelé pour l'initilisation des objets du tableau
for (int i=0 ; i<10 ; i++)
tab[i].affiche();
OU Counter *p;
for (p=tab,int i=0 ; i<10 ; i++,p++)
p->affiche();
95
ARGUMENTS PAR DEFAUT
96
96
ARGUMENTS PAR
DEFAUT
les arguments par défaut d'une fonction ou d'une méthode
permettent, lors de l'appel, de ne préciser qu'une partie des
arguments, les autres prenant une valeur par défaut indiquée dans la
déclaration de la fonction ou méthode
la valeur des arguments par défaut doit être précisée dans la
déclaration de la fonction ou méthode et non dans sa définition
seuls les derniers arguments consécutifs d'une fonction ou méthode
peuvent être des arguments par défaut
tous les arguments d'une fonction ou méthode peuvent être des
arguments par défaut
une fonction ou méthode comportant des arguments par défaut peut
être surchargée, mais certaines ambiguités peuvent surgir
97
97
ARGUMENTS PAR
DEFAUT
char* convert(int, int =10); // déclaration: la base est 10 par défaut
char* convert(int x, int base) { // conversion en chaine de l'entier x dans
. . . // la base indiquée (2, 8, 10 ou 16)
}
int main() {
int a=10;
cout<<"base 10: "<<convert(a)<<endl;
cout<<"base 8: "<<convert(a,8)<<endl;
. . .
}
98
98
ARGUMENTS PAR
DEFAUT
la présence d'arguments par défaut permet souvent de réduire le
nombre de méthodes surchargées
class Counter {
int count;
public:
Counter(int =0); // remplace Counter() et Counter(int)
. . .
};
99
99
EXERCICE
Exo9: mettre en place une méthode comportant un argument par
défaut dans la classe Counter
100
LE MOT-CLE THIS
101
101
LE MOT-CLE THIS
les méthodes d'instance, ou méthode, d'une classe définissent le
comportement des objets de cette classe
ellles permettent d'accéder aux attributs, aux variables de classe,
d'effectuer des traitements à partir d'arguments etc...
elles doivent nécessairement être invoquées pour un objet de la
classe
102
102
LE MOT-CLE THIS
this est un mot-clé qui représente l'objet courant, c'est-à-dire l'objet
pour lequel la méthode a été invoquée, au sein d'une méthode
d'instance
– this est un pointeur constant sur l'objet courant
dans une méthode d'instance, il peut être utilisé pour:
– retourner l'objet courant
– le passer en argument à une autre méthode
– différencier les attributs de variables locales de mêmes noms
– éviter des traitements inutiles ou impossibles
103
103
LE MOT-CLE THIS
utilisation de this pour retourner l'objet courant:
class Counter {
int count;
public:
Counter increment() {
count++;
return *this; // l'objet courant est retourné
}
void affiche() {
cout<<"valeur : "<<count<<endl;
}
};
104
104
LE MOT-CLE THIS
utilisation de this pour retourner l'objet courant (suite):
int main() {
Counter c1;
c1.increment().affiche(); // valeur: 1
. . .
}
105
105
LE MOT-CLE THIS
utilisation de this pour transmettre l'objet courant en argument:
class Alpha{
Beta b;
. . .
public:
void start() {
b.calcul(*this);
. . .
};
106
106
LE MOT-CLE THIS
utilisation de this pour différencier les attributs de variables locales
de mêmes noms:
class Counter {
int count;
public:
void init(int count) {
this->count=count; // count désigne l'argument
// this->count désigne l'attribut
}
void affiche() {
cout<<"valeur : "<<count<<endl;
}
};
107
107
LE MOT-CLE THIS
utilisation de this pour éviter des traitements inutiles ou impossibles:
class Personne {
. . .
public:
void mariage(Personne* p) {
if(this != p){
// traitement
}else{
// erreur: on ne peut se marier avec soi-même
}
};
108
108
EXERCICE
Exo10: retour de l'objet courant dans quelques méthodes de la classe
Counter
109
FONCTIONS ET METHODES
INLINE
110
110
FONCTIONS ET
METHODES INLINE
une fonction ou une méthode inline correspond en fait à une macro-
instruction
une telle fonction ou méthode doit comporter le mot-clé inline
précédant sa définition
inline int max (int a, int b) {
return a>b?a:b;
}
int main()
{
int x=3,y=5,z;
z=max(x,y);
}
111
111
FONCTIONS ET
METHODES INLINE
l'intérêt d'utiliser une macro-instruction en lieu et place d'une
fonction/méthode est d'éviter les phases d'appel et de retour qui
nécessitent une sauvegarde/restauration du contexte de la
fonction/méthode appelante en pile
si la fonction/méthode appelée réalise peu de traitements, la
sauvegarde/restauration du contexte de la fonction/méthode
appelante peut parfois représenter l'essentiel du travail réalisé,
pénalisant ainsi l'opération globale
en utilisant une méthode inline, le compilateur substitue l'appel à la
fonction/méthode par les instructions correspondant à la
fonction/méthode inline, évitant ainsi les phases d'appel et de retour
112
112
FONCTIONS ET
METHODES INLINE
en pratique, il s'agit d'une technique d'optimisation qui présente
quelques inconvénients:
– le compilateur ne peut traiter une fonction/méthode inline que si elle est
utilisée dans le même fichier que celui dans lequel elle est définie, la vraie
solution consistant à la placer dans un fichier d'entête
– la présence du mot-clé inline devant une fonction/méthode ne garantit pas son
traitement comme macro-instruction
– une méthode inline ne peut être virtuelle (Cf chapitre sur le polymorphisme)
dans le cas d'une définition de classe monolithique, les méthodes
sont par défaut inline
dans le cas d'une définition qui sépare les méthodes de leur classe, le
mot-clé inline est requis devant la définition des méthodes dont on
souhaite qu'elles soient traitées comme des macro-instructions
113
113
FONCTIONS ET
METHODES INLINE
class Counter {
int count;
public:
int increment() { // méthode implicitement inline
return ++ count;
}
void affiche() { // méthode implicitement inline
cout<< "valeur : " << count<<endl;
}
int lirecount() { // méthode implicitement inline
return count;
}
};
114
114
FONCTIONS ET
METHODES INLINE
class Counter {
private:
int count;
public:
int increment();
void affiche();
int lirecount();
};
115
115
FONCTIONS ET
METHODES INLINE
inline int Counter::increment() {
return ++ count;
}
inline void Counter::affiche() {
cout<< "valeur : " << count<<endl;
}
inline int Counter::lirecount() {
return count;
}
116
116
EXERCICE
Exo11: définir inline quelques méthodes de la classe Counter
117
LES VARIABLES ET
METHODES DE CLASSE
LES VARIABLES DE CLASSE
LES METHODES DE CLASSE
118
118
LES VARIABLES DE
CLASSE
les variables de classe sont "globales" à une même classe
leurs valeurs sont les mêmes pour tous les objets de cette classe
elles ne se retrouvent pas dans chacun des objets de cette classe
il s'agit donc de variables partagées par tous les objets d'une classe
comme les variables d'instance, elles bénéficient des accès private,
protected, public
119
119
LES VARIABLES DE
CLASSE
le mot-clé static déclare une variable comme étant une variable de
classe
class Article {
public:
static char monnaie[32]; // variable de classe
private:
static int nombreArticles; // variable de classe
double prix; // attribut
int quantite; // atrribut
char designation[32]; // attribut
. . .
};
120
une variabl e de classe respecte les règles d'accès comme les variables d'instance
et les méthodes.
une variable de classe peut être invoquée soit via la classe, soit via un objet de
cette classe. Cette dernière solution peut néanmoins prêter à confusion car une
variable de classe n'est pas liée à un objet donné:
class Stock {
public static void main (String args[]) {
Article a1 = new Article ();
. . .
Article.nombreArticle = 100;
. . .
a1.nombreArticle = 0;
}
}
120
LES VARIABLES DE
CLASSE
une variable de classe doit par ailleurs être définie, éventuellement
initialisée:
int Article::nombreArticles = 0;
lorsqu'une variable de classe est en accès public, elle peut être
invoquée depuis l'extérieur de la classe de deux façons:
– via un objet de cette classe comme pour un attribut public:
Article a1;
strcpy(a1.monnaie,"F");
– via le nom de la classe (solution préférable):
strcpy(Article::monnaie,"F");
l'accès à une variable de classe depuis une méthode de cette classe
s'effectue comme pour un attribut
121
121
LES METHODES DE
CLASSE
les méthodes de classe ne concernent pas un objet en particulier mais
plutôt la classe elle-même, ou bien tous les objets de cette classe
le mot-clé static définit une méthode comme étant une méthode de
classe
une méthode de classe ne peut pas utiliser this, puisqu'elle n'est pas
liée à un objet donné
elle ne peut pas accéder directement (sans préciser un objet) aux
attributs ni aux méthodes d'instance, pour la même raison
elle peut accéder aux variables de classe (c'est son rôle principal) ou
à d'autres méthodes de classe
122
une méthode de classe respecte les règles d'accès comme les variables d'instance
et les autres méthodes.
une méthode de classe peut être invoquée soit via la classe, soit via un objet de
cette classe. Cette dernière solution peut néanmoins prêter à confusion car une
méthode de classe n'est pas liée à un objet donné:
class Stock {
public static void main (String args[]) {
Article a1 = new Article ();
. . .
int x = Article.lire_nombreArticle();
. . .
int z = a1.lire_nombreArticle();
}
}
122
LES METHODES DE
CLASSE
class Article {
private:
static int nombreArticles; // variable de classe
int code; // attribut
char designation[32]; // attribut
public:
static int lire_nombreArticles () { // méthode de classe
return nombreArticles;
}
. . .
};
123
123
LES METHODES DE
CLASSE
les méthodes de classe peuvent être invoquées de deux façons
différentes:
– via un objet de cette classe comme pour une méthode d'instance publique:
Article a;
cout<<"Nombre d'articles : "<<a.lire_nombreArticles()<<endl;
– via le nom de la classe (solution préférable):
cout<<"Nombre d'articles : " <<Article::lire_nombreArticles()<<endl;
l'accès à une méthode de classe depuis une méthode d'instance de
cette classe peut s'effectuer directement
124
124
EXERCICE
Exo12: définir une variable de classe et une méthode de classe dans
la classe Counter
125
LA NOTION DE REFERENCE
126
126
LA NOTION DE
REFERENCE
le langage C++ introduit une possibilité supplémentaire de
manipuler les variables: la référence
une référence sur une variable est une sorte de pointeur implicite sur
cette variable
à la différence d'un pointeur cependant, une référence est
définitivement liée à une variable, et ce, dès sa création
int x=6;
int &rx=x; // rx est une référence sur x
rx++; // x est modifié
le moyen le plus commode de considérer une référence est de la voir
comme un alias
127
127
LA NOTION DE
REFERENCE
les références sont beaucoup utilisées en C++ car elles facilitent la
lisibilité tout en apportant une grande efficacité
elles sont notamment employées lors de la transmission d'objets en
arguments de fonctions ou méthodes
elles permettent également de manipuler des objets avec une grande
efficacité
elles peuvent être utilisées également en attribut
en règle générale et pour des raisons d'efficacité, les objets devraient
toujours être manipulés par référence (ou par pointeurs) sauf
bonne(s) raison(s)
128
LE PASSAGE
D'ARGUMENTS
129
129
LE PASSAGE
D'ARGUMENTS
par défaut, les arguments des types primitifs et les objets sont
transmis aux méthodes ou aux fonctions par valeur (ou par copie)
– une méthode ou fonction appelée ne peut modifier la donnée de la méthode
appelante
les arguments de type tableau sont transmis aux méthodes par
adresse:
– toute modification de ces arguments dans la méthode appelée se répercute sur
les objets initiaux de la méthode appelante
le passage d'arguments par référence permet d'obtenir les mêmes
effets que ceux obtenus avec les pointeurs, tout en simplifiant
l'écriture
130
130
LE PASSAGE
D'ARGUMENTS
argument de type objet
void incr(Counter& c) {
c.increment(); // c représente c1
}
int main () {
Counter c1(5);
incr(c1); // l'objet c1 est transmis par référence
c1.affiche(); // apres l'appel, c1 est modifié
}
131
131
LE PASSAGE
D'ARGUMENTS
argument de type primitif
void carre(int& c) {
c*=c; // c représente i
}
int main () {
int i = 5;
carre(i); // la variable i est transmise par référence
// apres l'appel, i vaut 25
}
132
132
EXERCICE
Exo13: transmission d'objets Counter par référence à une méthode
de la classe Counter
133
LE RETOUR D'OBJETS
134
134
LE RETOUR D'OBJETS
par défaut, les données des types primitifs et les objets sont retournés
par valeur
– il faut donc réaliser une affectation pour récupérer la valeur retournée
les tableaux retournés par les méthodes ou fonctions le sont par
adresse
on peut vouloir retourner certaines données par référence,
notamment des objets pour des raisons d'efficacité
135
135
LE RETOUR D'OBJETS
class Counter {
int count;
public:
Counter& add(int c) {
count+=c;
return *this; // retour de l'objet courant
}
void affiche() { cout<<"valeur : " <<count<<endl; }
};
int main () {
Counter c1(5);
c1.add(4).add(6); // le retour d'objets par référence permet l'associativité
c1.affiche(); // affiche 15
}
136
136
EXERCICE
Exo14: retour d'objets par référence dans quelques méthodes de la
classe Counter
137
LES CONSTANTES
138
138
LES CONSTANTES
en C++, le mot-clé const définit une constante
une valeur initiale doit nécessairement être attribuée à une constante
lors de sa définition
class Personne {
static const char* m = "Masculin";
static const char* f = "Feminin";
. . .
};
l'utilisation des variables const est plus large qu'en C:
const int TAILLE=10;
int tab[TAILLE]; // légal en C++, illégal en C
139
139
LES CONSTANTES
le mot-clé const est beaucoup utilisé lors du passage d'arguments par
adresse ou par référence
il permet de se protéger d'une modification intempestive de
l'argument par la fonction ou la méthode
class Counter {
int count;
public:
void add(const Counter& c) { // c ne peut être modifié car déclaré const
count+=c;
}
void affiche() {
cout<<"valeur : " <<count<<endl;
}
};
140
140
LES CONSTANTES
un objet constant est un objet dont l'état ne peut être modifié
const Counter c(5);
seules les méthodes déclarées const peuvent être invoquées sur des
objets constants
une méthode peut être déclarée const si elle ne modifie pas l'état des
attributs
il faut donc déclarer le maximum de méthodes const car l'apparition
d'objets constants est quasiment inévitable
141
141
LES CONSTANTES
class Counter {
int count;
public:
void add(const Counter& c) {
// c ne peut être modifié car déclaré const
count+=c;
c.affiche(); // ok car affiche() méthode const
}
void affiche() const {
cout<<"valeur : " <<count<<endl;
}
};
142
142
LES CONSTANTES
lorsqu'un attribut est constant, son initialisation doit avoir lieu dans
la liste d'initalisateurs de membres
class Counter {
int count;
const int MAX;
public:
Counter():count(0), MAX(10000){} // OK
// Counter():count(0){ MAX=10000;} illégal
. . .
};
143
143
EXERCICE
Exo15: ajout du mot-clé const sur quelques méthodes et sur certains
arguments de méthodes
144
ALLOCATION DYNAMIQUE
DE MEMOIRE
Toute représentation ou reproduction intégrale ou partielle faite sans le consentement de l'auteur
Denis MADEC ou de ses ayants droits ou ayant cause est illicite selon le code de la propriété
intellectuelle du 1er juillet 1992 et constitue une contefaçon réprimée par le code pénal.
145
145
ALLOCATION DYNAMIQUE
DE MEMOIRE
l'allocation dynamique de mémoire permet de construire des
applications qui n'utilisent que la quantité de mémoire dont elles ont
besoin à un instant donné
l'objectif est d'optimiser la gestion de la ressource mémoire, lorsque
les besoins risquent d'être importants compte-tenu de la quantité de
mémoire disponible
en C, les fonctions standards malloc et calloc permettent d'allouer
une zone mémoire, realloc permet de réallouer et free permet de
libérer une zone mémoire préalablement allouée
146
146
ALLOCATION DYNAMIQUE
DE MEMOIRE
en C++, l'allocation s'effectue via les opérateurs new et new[], la
libération par les opérateurs delete et delete[]
il y a incompatibilité entre les fonctions d'allocation/libération du C
et les opérateurs new, new[] et delete, delete[] du C++. Il est donc
préférable de ne pas les utiliser conjointement au sein d'une même
application
toute allocation effectuée à l'aide de l'opérateur new doit être libérée
à l'aide de l'opérateur delete
toute allocation effectuée à l'aide de l'opérateur new[] doit être
libérée à l'aide de l'opérateur delete[]
la mémoire allouée par new ou new[] n'est pas initialisée
147
147
ALLOCATION DYNAMIQUE
DE MEMOIRE
allocation d'une donnée simple:
int *p=new int; // allocation dynamique de mémoire pour un entier
*p=5;
. . .
delete p; // libération de la mémoire associée à cet entier
allocation d'un tableau:
int *psav;
int *pt=new int[10]; // alloc. dyn. de mém. pour un tableau d'entiers
for(int i=0, psav=pt ; i<10 ; i++, pt++)
*pt=0 ;
. . .
delete[] psav; // libération de la mémoire associée à ce tableau
148
148
ALLOCATION DYNAMIQUE
DE MEMOIRE
allocation d'un objet:
Counter *p1=new Counter; // alloc. dyn. de mém. pour un objet
Counter *p2=new Counter(5); // alloc. dyn. de mém. pour un objet
p1->increment();
p2->increment();
. . .
delete p1; // libération de la mémoire associée à l'objet *p1
delete p2; // libération de la mémoire associée à l'objet *p2
à la différence de la fonction malloc ou calloc, l'opérateur new fait
appel au constructeur pour initialiser l'objet alloué
l'opérateur delete fait quant à lui appel au destructeur
149
149
ALLOCATION DYNAMIQUE
DE MEMOIRE
allocation d'un tableau d'objets:
Counter *psav;
Counter *pc=new Counter[10]; // alloc. dyn. de mém. pour un tableau
// d'objets
for(int i=0, psav=pc ; i<10 ; i++, pt++)
pt->affiche() ;
. . .
delete[] psav; // libération de la mémoire associée à ce tableau
l'opérateur new[] fait appel au constructeur par défaut ou à celui sans
argument s'il existe pour initialiser chacun des objets du tableau
l'opérateur delete[] fait appel au destructeur autant de fois que le
tableau comporte d'objets à détruire
150
150
ALLOCATION DYNAMIQUE
DE MEMOIRE
en cas d'échec de l'allocation, les opérateurs new et new[] peuvent
selon le cas:
– lancer une exception
– retourner une adresse nulle
– déclencher l'exécution d'une fonction utilisateur
le code généré par les compilateurs récents qui respectent la version
3.0 du C++ entraine par défaut le lancement d'une exception de type
bad_alloc (Cf chapitre "Exceptions")
il est néanmoins possible d'intervenir dans ce comportement de sorte
que les opérateurs new et new[] retournent une adresse nulle ou
déclenchent l'exécution d'une fonction spécifique
151
151
ALLOCATION DYNAMIQUE
DE MEMOIRE
il suffit de faire appel à une fonction set_new_handler(void*), en lui
passant comme argument l'adresse d'une fonction de gestion d'erreur
d'allocation
void erreur_alloc() {
cerr<<"allocation impossible!"<<endl;
. . .
}
int main() {
set_new_handler(erreur_alloc);
for(;;) new char[10000];
. . .
}
152
152
ALLOCATION DYNAMIQUE
DE MEMOIRE
si l'allocation échoue, la fonction erreur_alloc est exécutée, puis une
nouvelle tentative d'allocation est effectuée, et ainsi de suite.
la fonction erreur_alloc doit donc tenter de libérer de la mémoire
avant son retour, ou alors lancer une exception ou encore quitter
l'application
si la fonction set_new_handler reçoit une adresse nulle, les
opérateurs new et new[] renvoient une adresse nulle lorsque
l'allocation échoue
int main() {
set_new_handler(0);
if((int* p=new int[10000])==0) {
. . .}
}
153
153
ALLOCATION DYNAMIQUE
DE MEMOIRE
on peut vouloir effectuer de l'allocation dynamique de mémoire au
sein d'objets, ce qui revient alors à créer des objets de taille variable
le destructeur doit alors libérer toute la mémoire allouée pour l'objet
sous peine de consommer de la mémoire
l'un ou plusieurs des attributs est de type pointeur et désigne une
zone mémoire allouée dynamiquement
l'exemple suivant représente une classe chaine de caractères
permettant de créer des objets de taille variable, en stockant les
caractères dans une zone mémoire allouée dynamiquement
154
154
ALLOCATION DYNAMIQUE
DE MEMOIRE
class Chaine {
char *pch;
int longueur;
public:
Chaine();
Chaine(const char*);
~Chaine();
Chaine& copie(const char*);
Chaine& concat(const char*);
int compare(const char*) const;
int length() const;
void print() const;
};
155
155
ALLOCATION DYNAMIQUE
DE MEMOIRE
Chaine::Chaine() {
longueur=0;
pch=new char[1];
*pch='\0';
}
Chaine::Chaine(const char* s) {
longueur=strlen(s);
pch=new char[longueur+1];
strcpy(pch,s);
}
Chaine::~Chaine() {
delete[] pch;
}
156
156
ALLOCATION DYNAMIQUE
DE MEMOIRE
int Chaine::compare(const char* s) const {
return strcmp(pch,s);
}
int Chaine::length() const {
return longueur;
}
void Chaine::print() const {
cout<<pch<<endl;
}
157
157
EXERCICE
Exo16: définir quelques méthodes de la classe Chaine
158
CONSTRUCTEUR DE COPIE
159
159
CONSTRUCTEUR DE
COPIE
lorsque l'un des attributs d'un objet est une référence ou un pointeur,
la création d'un objet à partir d'un autre de la même classe peut
présenter quelque problème:
Chaine c1(c2);
en effet, en l'absence d'un constructeur adéquat, dit constructeur de
copie, la création d'un objet à partir d'un autre de la même classe est
malgré tout effectuée
cependant, le code généré par défaut effectue une copie membre à
membre des attributs: les objets c1 et c2 se partagent donc la même
zone mémoire allouée dynamiquement pour stocker les caractères
que ces chaines représentent
160
160
CONSTRUCTEUR DE
COPIE
les objets c1 et c2 ne sont donc pas complètement distints et une
modification de l'un peut entraîner de graves dommages sur l'autre
ce problème peut être corrigé en introduisant un constructeur dit
constructeur de copie: Chaine(const Chaine&);
Chaine::Chaine(const Chaine& c) {
longueur=c.longueur;
pch=new char[longueur+1];
strcpy(pch,c.pch);
}
161
161
CONSTRUCTEUR DE
COPIE
la création d'un objet à partir d'un autre du même type se produit
dans les cas suivants:
– création explicite:
X x1(x2);
X x3=x4;
– transmision par valeur d'un objet à une fonction ou méthode
X x();
f(x); // avec …f(X)
– retour par valeur d'objets
X x=g(…); // avec X g(…)
162
162
EXERCICE
Exo17: rajouter un constructeur de copie dans la classe Chaine
163
SURCHARGE DE
L'OPERATEUR =
164
164
SURCHARGE DE
L'OPERATEUR =
un problème similaire se rencontre lors de l'affectation de deux
objets de mêmes types:
Chaine c1("alpha");
Chaine c2("beta");
c1=c2;
en effet, en l'absence d'une méthode adéquate, dite opérateur
d'affectation surchargé, la copie d'un objet à partir d'un autre de la
même classe est malgré tout effectuée
cependant, le code généré par défaut effectue une copie membre à
membre des attributs: les objets c1 et c2 se partagent donc la même
zone mémoire allouée dynamiquement pour stocker les caractères
que ces chaines représentent
165
165
SURCHARGE DE
L'OPERATEUR =
les objets c1 et c2 ne sont donc pas complètement distints et une
modification de l'un peut entraîner de graves dommages sur l'autre
ce problème peut être corrigé en introduisant une surcharge de
l'opérateur d'affectation: Chaine& operator=(const Chaine&);
Chaine& Chaine::operator=(const Chaine& c) {
if(this!=&c) {
delete[] pch;
longueur=c.longueur;
pch=new char[longueur+1];
strcpy(pch,c.pch);
}
return *this;
}
166
166
SURCHARGE DE
L'OPERATEUR =
la présence d'un constructeur de copie et d'un opérateur d'affectation
surchargé dans toute classe est souhaitable, ne serait-ce que pour
rassurer l'utilisateur
la présence nécessaire de l'un entraîne généralement la présence
nécessaire de l'autre
toute classe qui comporte ces deux méthodes ainsi qu'un
constructeur sans argument est qualifiée de "forme canonique
orthodoxe" par Bjarne Stroustrup
167
167
EXERCICE
Exo18: rajouter l'opérateur d'affectation dans la classe Chaine
168
FONCTIONS ET CLASSES
AMIES
169
169
FONCTIONS ET CLASSES
AMIES
une fonction amie d'une classe est une fonction qui a accès à tous les
membres, mêmes privés de tous les objets de cette classe
une fonction est déclarée amie d'une classe par le mot-clé friend
class A {
friend void f(A&);
int a;
. . .
};
void f(A& x) {
x.a=0; // accès à l'attribut a privé dans A
}
n'étant pas un membre de la classe, l'emplacement de sa déclaration
dans la classe importe peu
170
170
FONCTIONS ET CLASSES
AMIES
une fonction ne peut être déclarée amie d'une classe que si cette
classe a été préalablement définie, à défaut préalablement déclarée,
par exemple: class A;
si une fonction doit avoir accès aux membres privés des objets de
deux classes, elle peut être rendue amie de chacune d'elles
class A { class B {
friend int g(A&,B&); friend int g(A&,B&);
int a; int b;
. . . . . .
}; };
int g(A& x,B& y) {
return x.a+y.b=0; // accès aux attributs a et b privés
}
171
171
FONCTIONS ET CLASSES
AMIES
une méthode d'une classe peut également être amie d'une autre classe
class A { class B {
int a; friend void A::f(B&);
public: int b;
void f(B&); . . .
}; };
void A::f(B& y) {
y.b=0; // accès à l'attribut b privé dans B
}
172
172
FONCTIONS ET CLASSES
AMIES
une classe peut être rendue amie d'une autre classe, donnant ainsi à
la première un accès illimité aux membres de la deuxième
class B;
class A {
friend class B;
int a;
. . .
};
173
SURCHARGE
D'OPERATEURS
174
174
SURCHARGE
D'OPERATEURS
la surcharge d'opérateurs consiste à redéfinir certains opérateurs du
langage de sorte qu'ils puissent s'appliquer aux objets:
Counter c1(5),c2(8);
Counter c3=c1+c2;
– l'opérateur + n'étant pas défini pour le type Counter, le compilateur signale
une erreur
– il suffit de définir l'opérateur + dans une méthode adéquate pour rendre légale
cette opération
tous les opérateurs du langage C++ peuvent être surchargés sauf:
:: .* . ?: sizeof
il n'est pas possible de créer de nouveaux opérateurs, de modifier la
priorité d'un opérateur, ni de modifier son nombre d'opérandes
175
175
SURCHARGE
D'OPERATEURS
l'opérateur d'affectation déjà été examiné précédemment:
Chaine& operator=(const Chaine&);
pour obtenir de nom de la méthode correspondant à un opérateur, il
suffit de faire suivre le mot operator du symbole utilisé pour
l'opérateur, ce symbole étant constitué de un ou deux caractères
en ce qui concerne le type de retour, il dépend essentiellement de
l'opérateur utilisé, de même pour les arguments
il est possible de trouver dans une même classe le même opérateur
surchargé plus d'une fois
176
176
SURCHARGE
D'OPERATEURS
un moyen simple et sûr de trouver le type de retour ainsi que les
arguments est de ré-écrire l'expression:
c1=c2; // est équivalent à: c1.operator=(c2);
Counter c3=c1+c2; // est équivalent à: Counter c3=c1.operator+(c2);
++c3; // est équivalent à: c3.operator++();
tout opérateur unaire (s'appliquant à un seul opérande) peut être
défini comme méthode n'ayant pas d'argument
tout opérateur binaire (s'appliquant à deux opérandes) peut être
défini comme méthode ayant un seul argument
l'exemple suivant montre quelques opérateurs surchargés pour des
objets de la classe Counter
177
177
SURCHARGE
D'OPERATEURS
class Counter {
int count;
public:
Counter();
Counter(int);
. . .
bool operator==(const Counter&) const;
bool operator!=(const Counter&) const;
Counter& operator+=(const Counter&);
Counter operator+(const Counter&);
Counter& operator++(); // pré-incrémentation
Counter operator++(int); // post-incrémentation
};
178
178
SURCHARGE
D'OPERATEURS
bool Counter::operator==(const Counter& c) const {
return count==c.count;
}
bool Counter::operator!=(const Counter& c) const {
return count!=c.count;
}
Counter& Counter::operator+=(const Counter& c) {
count+=c.count;
return *this;
}
Counter Counter::operator+(const Counter& c) {
Counter temp(count+c.count);
return temp;
}
179
179
SURCHARGE
D'OPERATEURS
Counter& Counter::operator++() { // pré-incrémentation
++count;
return *this;
}
Counter Counter::operator++(int x) { // post-incrémentation
Counter temp=*this;
++count;
return temp;
}
180
180
SURCHARGE
D'OPERATEURS
int main() {
Counter c1(5),c2(14),c3;
c1++;
c3=c1+c2;
Counter c4(20);
if(c1==c4) cout<<"c1 et c4 ont le même état"<<endl;
else cout<<"c1 et c4 ont un état différent"<<endl;
c4+=5;
cout<<"c4: "<<c4.lirecount()<<endl;
c1=c3+6;
c2=8+c4; // ERREUR à la compilation
}
181
181
EXERCICE
Exo19: définir quelques opérateurs dans la classe Chaine
182
182
SURCHARGE
D'OPERATEURS
les opérations: c4+=5; et c1=c3+6; sont acceptées contre toute
attente
les méthodes operator+=(int) et operator+(int) n'existant pas dans la
classe Counter, le compilateur fait de son mieux et utilise le
constructeur Counter(int) pour créer des objets à partir des
arguments de type int afin d'être en mesure d'utiliser les méthodes
operator+=(const Counter&) et operator+(const Counter&)
tout constructeur avec un argument joue donc le rôle d'un opérateur
de conversion du type de l'argument vers le type de la classe
l'opération c2=8+c4; est refusée par le compilateur car l'expression:
c2=8.operator(c4); est illégale pour lui, 8 n'étant pas un objet
183
183
SURCHARGE
D'OPERATEURS
afin que la conversion des opérandes puisse s'appliquer
indifféremment aux opérandes de gauche ou de droite, il faut utiliser
des fonctions amies en lieu et place des méthodes
class Counter {
. . .
friend Counter operator+(const Counter&,const Counter&);
};
Counter operator+(const Counter& cx,const Counter& cy) {
Counter temp(cx.count+cy.count);
return temp;
}
184
184
SURCHARGE
D'OPERATEURS
une fonction amie qui surcharge un opérateur nécessite un argument
de plus que la méthode équivalente, puisqu'elle n'est pas invoquée
pour un objet
en règle générale, les opérateurs binaires seront de préférence
surchargés comme fonctions amies de façon à permettre une plus
grande latitude à l'utilisation
les opérateurs suivants sont obligatoirement surchargés dans une
méthode:
= -> () [] opérateurs de conversion
185
185
EXERCICE
Exo20: définir un opérateur en fonction amie dans la classe Chaine
186
186
SURCHARGE
D'OPERATEURS
les opérateurs de conversion jouent le rôle inverse du constructeur:
ils permettent de convertir un objet de la classe dans un autre type
Counter c(12);
int a=c; // int a=c.operator int();
un opérateur de conversion ne possède pas de type de retour, ni
d'argument
la mise-en-œuvre de ces opérateurs est délicate car elle conduit
rapidement à des ambiguités lors de la compilation, le compilateur
ne pouvant décider de la conversion à appliquer parmi plusieurs
possibilités
187
187
SURCHARGE
D'OPERATEURS
class Counter {
. . .
public:
Counter(int); // conversion de int vers Counter
operator int() const; // conversion de Counter vers int
};
Counter::operator int() const { // opérateur de conversion Counter->int
return count;
}
188
188
EXERCICE
Exo21: définir un opérateur de conversion dans la classe Chaine
189
189
SURCHARGE
D'OPERATEURS
les opérateurs d'entrées/sorties sont très utiles lorsqu'il est possible
de les appliquer directement sur des objets
l'objet cout de la classe ostream est utilisé pour l'affichage sur le
terminal
l'opérateur << est abondamment surchargé dans la classe ostream de
façon à permettre l'affichage de toute donnée de type primitif
cet opérateur n'a pas été prévu pour afficher des objets Counter ni
Chaine, et la classe ostream ne peut être modifiée
Counter c1(5);
cout<<c1; // cout.operator<<(c1); ou operator(cout,c1);
il suffit de surcharger l'opérateur << comme fonction amie
190
190
SURCHARGE
D'OPERATEURS
class Counter {
. . .
friend ostream& operator<<(ostream&,const Counter&);
};
ostream& operator<<(ostream& os, const Counter& c) {
os<<c.count;
return os;
}
191
191
EXERCICE
Exo22: définir, dans la classe Chaine, l'opérateur d'entrées/sorties >>
permettant son utilisation avec un objet cout
192
LES CLASSES INTERNES
193
193
LES CLASSES INTERNES
une classe interne (nested class) est une classe définie à l’intérieur
d’une autre classe:
– soit comme membre de cette classe (inner class), au même titre que ses
attributs ou méthodes
– soit à l’intérieur d’une méthode de cette classe (local class)
l'intérêt principal d'une classe interne est de limiter sa visiblité à la
seule classe englobante, et aux classes dérivées de celle-ci
une classe interne n'a pas d'accès privilégié aux membres de sa
classe englobante et réciproquement
une classe interne définie comme membre d’une classe subit les
mêmes règles de visiblité que les autres membres
– les spécificateurs d’accès private, protected, public s’appliquent donc à une
classe interne
194
194
LES CLASSES INTERNES
class List {
ListItem *list;
ListItem *end;
class ListItem {
static int MAX_ELEM;
ListItem *next;
public:
ListItem(int =0);
. . .
};
public:
List();
. . .
};
195
195
LES CLASSES INTERNES
la définition des méthodes de la classe interne doit indiquer le nom
complet de la méthode, qui inclut celui de la classe englobante:
int List::ListItem::MAX_ELEM=10000;
List::ListItem::ListItem(int val):next(0)
{}
List::List():list(0),end(0)
{}
196
196
LES CLASSES INTERNES
une classe locale est une classe définie à l'intérieur d'une méthode
(ou d'une fonction): la définition de la classe est obligatoirement
monolithique, ce qui limite en général sa complexité
class A {
public:
void alpha(int val) {
class B { // classe interne locale à la méthode alpha
. . .
};
. . .
}
. . .
};
197
197
EXERCICE
Exo23: mise en œuvre d'une classe interne
198
LA COMPOSITION
IMPLEMENTATION DE LA COMPOSITION
CONSTRUCTION D'UN OBJET COMPOSE
AFFECTATION D'OBJETS COMPOSES
DESTRUCTION D'UN OBJET COMPOSE
199
199
IMPLEMENTATION DE LA
COMPOSITION
la composition traduit la relation A UN, POSSEDE, ou EST
COMPOSE DE
il se traduit simplement en C++ par la présence d'objets en attribut
class Personne {
string nom; // objet de la classe string
string prenom; // objet de la classe string
int age;
public:
Personne();
Personne(const string&, const string&, int);
. . .
};
200
200
CONSTRUCTION D'UN
OBJET COMPOSE
la construction d'un objet composé entraine automatiquement par
défaut la construction préalable des objets attributs le composant,
dans l'ordre de déclaration de ces attributs dans la classe
– dans l'exemple précédent, le constructeur de nom , puis celui de prenom, puis
celui de Personne est appelé
le constructeur sans argument des objets attributs est appelé par
défaut
Personne::Personne() // les objets nom et prenom sont initialisés
{} // avec le constructeur sans argument de string
201
201
CONSTRUCTION D'UN
OBJET COMPOSE
l'appel à un autre constructeur que celui sans argument peut être
indiqué dans la liste d'initialisateurs de membres:
Personne::Personne(const string& n, const string&, int a)
:nom(n), prenom(p), age(a) {}
par défaut, la création d'un objet composé à partir d'un autre du
même type revient à créer chacun des attributs du premier à partir
des attributs correspondants du deuxième
– s'il existe, le constructeur de copie des objets attributs sera appelé
– sinon, les objets attributs seront recopiés membre à membre
Personne martin("MARTIN","Pierre",30);
Personne m=martin; // les attributs nom, prenom de l'objet m sont crées à partir
// des objets nom et prenom de martin
202
202
AFFECTATION D' OBJETS
COMPOSES
l'affectation d'un objet composé à un autre du même type revient
également à affecter chacun des attributs du premier aux attributs
correspondants du deuxième
– s'il existe, l'opérateur d'affectation surchargé des objets attributs sera appelé
– sinon, les objets attributs seront recopiés membre à membre
Personne martin("MARTIN","Pierre",30);
Personne p;
p=martin; // les attributs nom, prenom de l'objet p sont initialisés par
// les objets nom et prenom de martin
203
203
DESTRUCTION D'UN
OBJET COMPOSE
la destruction d'un objet composé s'effectue dans l'ordre inverse de
sa création: le destructeur de l'objet composé est appelé avant ceux
des objets attributs le constituant
– dans l'exemple précédent, le destructeur de Personne, puis celui de prenom,
puis celui de nom est appelé (s'ils existent)
class Personne {
string nom; // objet de la classe string
string prenom; // objet de la classe string
int age;
public:
. . .
};
204
204
EXERCICE
Exo24: réalisation d'une classe Personne
205
L'HERITAGE
IMPLEMENTATION DE L'HERITAGE
LA CONSTRUCTION D'UN OBJET DERIVE
L'APPEL DES METHODES
HERITAGES PUBLIC, PRIVE ET PROTEGE
HERITAGE PUBLIC
HERITAGE PROTEGE
HERITAGE PRIVE
L'ACCES "PROTECTED"
DESTRUCTION D'UN OBJET DERIVE
LA MANIPULATION D'UN OBJET DERIVE
L'HERITAGE MULTIPLE
L'HERITAGE MULTIPLE REPETE
206
206
IMPLEMENTATION DE
L'HERITAGE
l'héritage traduit la relation "EST-UN"
l'héritage consiste à faire bénéficier une classe (les objets de cette
classe) des attributs et des méthodes d'une autre classe
C++ supporte l'héritage multiple
207
207
IMPLEMENTATION DE
L'HERITAGE
l'héritage s'exprime par l'opérateur : suivi du type de l'héritage, à
savoir public, protected ou private
class D : public B {
. .
};
la classe D est la sous-classe (ou classe dérivée ou classe fille) de B
la classe B est la super-classe (ou classe de base ou classe parente)
de D
plusieurs classes filles peuvent hériter d'une même classe parente
208
208
IMPLEMENTATION DE
L'HERITAGE
l'héritage public est le plus largement utilisé car il correspond aux
besoins les plus courants
class D : public B {
. .
};
en l'absence d'un spécificateur, l'héritage est privé et son usage
beaucoup plus rare
class E : A {
. .
};
209
209
IMPLEMENTATION DE
L'HERITAGE
l'héritage est transitif et conduit à une hiérarchie d'héritage
une sous-classe peut ainsi avoir plusieurs super-classes, au travers
des relations d'héritage qui peuvent exister entre ces dernières
en plus des méthodes publiques de sa classe, il est possible
d'invoquer les méthodes publiques de sa(ses) super-classe(s)
un objet d’une sous-classe est en général "plus gros" qu’un objet de
sa super-classe, car en plus des attributs définis dans sa propre
classe, il comporte ceux de sa(ses) super-classe(s)
il a de plus un comportement plus varié qu'un objet de sa super-
classe, puisqu’il dispose de toutes les méthodes héritées, en plus de
celles qu’il possède dans sa propre classe
210
210
LA CONSTRUCTION D'UN
OBJET DERIVE
l'initialisation des attributs d'un objet dérivé s'effectue en faisant
appel aux constructeurs, lesquels appellent à leur tour ceux de la
classe de base soit de manière implicite, soit explicitement
lors de la construction d'un objet dérivé, les constructeurs des classes
de base sont d'abord appelés, dans l'ordre de la hiérarchie de classe,
puis ceux des attributs de l'objet dérivé et enfin celui de l'objet dérivé
tout objet dérivé voit ses attributs correspondant à sa classe de base
initialisés implicitement par le constructeur sans argument de sa
classe de base
si l'on souhaite faire appel à un autre constructeur que celui sans
argument, il faut l'indiquer dans la liste d'initialisateurs de membres
211
211
LA CONSTRUCTION D'UN
OBJET DERIVE
class Personne {
char nom[20];
public:
Personne() {nom[0] = '\0'; }
Personne (const char* n) {strcpy( nom,n); }
};
class Employe:public Personne {
double salaire;
public:
Employe() {salaire=0.0;} // appel implicite du constructeur Personne()
Employe (const char* n, double s):Personne(n) // appel explicite au constructeur
// Personne(const char*)
{ salaire = s; }
};
212
212
L'APPEL DES METHODES
lorsqu'une méthode est invoquée pour un objet, le compilateur
recherche d'abord cette méthode dans la classe de cet objet
si aucune méthode de ce nom n'est présente dans la classe, il remonte
dans la hiérarchie de classes pour continuer sa recherche
toutes les méthodes publiques des classes de base peuvent ainsi être
invoquées pour un objet d'une classe dérivée, en plus des méthodes
publiques propres à cette classe
213
213
L'APPEL DES METHODES
class Personne {
char nom[20];
public:
. . .
void affiche_pers () {cout<<"nom : " << nom <<endl; }
};
class Employe:public Personne {
double salaire;
public:
Employe (const char* n, double s) :Personne(n),salaire(s){}
};
int main () {
Employe martin=new Employe ("MARTIN", 15000);
martin.affiche_pers (); // méthode héritée de la classe Personne
}
214
214
L'APPEL DES METHODES
si l'on souhaite compléter le code d'une méthode héritée, il est
avantageux de définir une méthode qui appelle la méthode héritée:
class Personne {
char nom[20];
public:
Personne (const char* n) { strcpy(nom,n); }
void affiche_pers () { cout<<"nom : " << nom <<endl;}
};
class Employe:public Personne {
double salaire;
public:
Employe (const char* n, double s):Personne(n),salaire(s) {}
void affiche_emp () {
affiche_pers (); // appel d'une méthode héritée
cout<<"salaire : "<< salaire<<endl;
}
};
215
215
L'APPEL DES METHODES
si l'on souhaite conserver le même nom qu'une méthode de la super-
classe, tout en faisant appel à cette dernière dans la sous-classe, il
faut indiquer au compilateur qu'il ne s'agit pas d'une méthode
récursive en faisant précéder l'appel à la méthode du nom de sa
classe suivi de l'opérateur ::
Personne::affiche()
plus généralement, cette notation est utilisée lorsque l'on souhaite
simplement préciser la classe dont fait partie la méthode appelée
216
216
L'APPEL DES METHODES
class Personne {
char nom[20];
public:
Personne (const char* n) { strcpy(nom,n); }
void affiche () { cout<<"nom : " <<nom<<endl;}
};
class Employe:public Personne {
double salaire;
public:
Employe (const char* n, double s):Personne(n),salaire(s) {}
void affiche () {
Personne::affiche(); // appel d'une méthode héritée de même nom
cout<<"salaire : "<<salaire<<endl;
}
};
217
217
EXERCICE
Exo25: réalisation d'une classe Employe sous-classe de Personne
218
218
HERITAGES PUBLIC,
PRIVE ET PROTEGE
l'héritage traduit le bénéfice, pour la classe dérivée, de tous les
membres de sa classe parente
l'accès aux membres hérités n'est pas pour autant garanti, car il suit
les règles de l'encapsulation, et dépend également du type de
l'héritage mis-en-œuvre
trois mode d'héritage sont proposés en C++:
public protégé privé
class B : public A { class C:protected A { class D: private A {// class D:A {
. . . . . .
}; }; };
219
219
HERITAGES PUBLIC,
PRIVE ET PROTEGE
le mode d'héritage se combine avec le spécificateur d'accès des
membres hérités pour donner accès ou non à ces derniers:
inaccessible inaccessible inaccessible private
private protected protected protected
private protected public public
private protected public mode
accès
220
220
HERITAGES PUBLIC,
PRIVE ET PROTEGE
soit la classe suivante:
class A {
private:
int x;
void f();
protected:
int y;
void g();
public:
int z;
void h();
};
221
221
HERITAGE PUBLIC
héritage public: le plus fréquemment rencontré
les membres hérités conservent leur niveau d'encapsulation dans la
classe dérivée
class B: public A {
public:
void f() {
// x=1; // inaccessible
y=2; // ok, y est dorénavant protected (ce qu'il était déjà)
z=3; // ok, z est dorénavant public (ce qu'il était déjà)
}
};
222
222
HERITAGE PUBLIC
int main() {
C cc;
// cc.f(); // inaccessible
// cc.g(); // inaccesible car g() est protected
cc.h(); // ok car h() est public
}
223
223
HERITAGE PROTEGE
héritage protégé: rarement rencontré
les membres hérités public et protected dans la classe de base
deviennent protected dans la classe dérivée
class C: protected A {
public:
void f() {
// x=1; // inaccessible
y=2; // ok, y est dorénavant protected (ce qu'il était déjà)
z=3; // ok, z est dorénavant protected (il était public)
}
};
224
224
HERITAGE PROTEGE
int main() {
C cc;
// cc.f(); // inaccessible
// cc.g(); // inaccessible car g() est dorénavant protected
// cc.h(); // inaccessible car h() est dorénavant protected
}
225
225
HERITAGE PRIVE
héritage privé: rarement rencontré
les membres hérités public et protected dans la classe de base
deviennent private dans la classe dérivée
class D: private A { // ou class D:A {
public:
void f() {
// x=1; // inaccessible
y=2; // ok, y est dorénavant private (il était protected)
z=3; // ok, z est dorénavant private (il était public)
}
};
226
226
HERITAGE PRIVE
int main() {
C cc;
// cc.f(); // inaccessible car f() est private
// cc.g(); // inaccessible car g() est dorénavant private
// cc.h(); // inaccessible car h() est dorénavant private
}
227
227
L'ACCES "PROTECTED"
si l'on souhaite encapsuler les données, il est souhaitable de les
rendre privées dans la classe
dans ce cas, une méthode d'une sous-classe n'a pas directement accès
aux attributs définis dans sa super-classe: elle doit passer par des
méthodes publiques de sa super-classe (en supposant qu'elles
existent)
l'encapsulation peut être conservée, tout en autorisant l'accès aux
données depuis les méthodes des sous-classes , en déclarant les
données protected dans la super-classe
– ces données restent privées pour un objet de la super-classe
– les méthodes peuvent également être déclarées en protected
228
228
L'ACCES "PROTECTED"
class Employe:public Personne {
protected:
double salaire;
public:
Employe (const char* n, double s):Personne(n), salaire(s){}
void setSalaire (int s) {salaire = s;}
};
int main () {
Employe Lucien ("Lucien", 17000);
Lucien.salaire = 20000; // ERREUR ! salaire est protected
Lucien.setSalaire (20000); // OK
}
229
229
L'ACCES "PROTECTED"
class Technicien:public Employe {
protected:
char specialite[20];
int echelon;
public:
Technicien (const char* n, double s, const char* sp, int e):Employe(n,s) {
specialite = sp;
echelon = e;
}
void fixer_salaire (int val) {
salaire = val; // accès à un attribut protected dans la classe Employe
}
};
230
230
EXERCICE
Exo26: mise en place d'un membre protected dans la classe Employe
231
231
DESTRUCTION D'UN
OBJET DERIVE
la destruction d'un objet dérivé s'effectue dans l'ordre inverse de sa
création: le destructeur de l'objet dérivé est appelé avant ceux des
attributs et avant ceux des classes de base
la destruction d'un objet Technicien entrainera l'appel aux
destructeur de Technicien, puis à celui de Employe et enfin à celui
de Personne
232
232
LA MANIPULATION D'UN
OBJET DERIVE
un objet d'une classe dérivée peut être implicitement converti en
objet d'une classe de base dans la hiérarchie:
Employe martin ("MARTIN", 15000);
Personne durand("DURAND");
Personne m = martin; // l'objet martin est une Personne
Employe d = durand; // ERREUR! l'objet durand n'est pas un Employe
il est donc possible de passer en paramètre à une méthode un objet
qui sera reçu dans un argument du type d'une de ses classes de base
int main () {
Employe martin("MARTIN", 15000);
majus(martin);
}
void majus(Personne p) {
. . .
}
233
233
LA MANIPULATION D'UN
OBJET DERIVE
en pratique, il est plus courant et beaucoup plus utile de disposer de
pointeurs ou de références sur les objets dérivés pour les manipuler
Employe martin ("MARTIN", 15000);
Personne *p = &martin; // l'objet martin est une Personne
Personne& rm=martin; // idem
Personne durand("DURAND");
Employe *p = &durand; // ERREUR! l'objet durand n'est pas un Employe
Employe & rd=durand; // idem
234
234
LA MANIPULATION D'UN
OBJET DERIVE
l'héritage autorise donc la manipulation d'objets dérivés en les
considérant comme des objets de leur classe de base
int main () {
Personne durand("DURAND");
Employe martin("MARTIN", 15000);
durand.affiche_pers (); // nom : DURAND
martin.affiche_emp (); // nom : MARTIN
// salaire : 15000
Personne *p = &martin; // l'objet martin est une Personne
p->affiche_pers (); // nom : MARTIN
. . .
}
235
235
LA MANIPULATION D'UN
OBJET DERIVE
manipuler un objet dérivé via un pointeur ou une référence du type
de sa classe de base revient à réduire son comportement à celui que
fournit sa classe de base
– un objet Employe manipulé via un pointeur ou une référence de type Personne
se comporte comme une Personne
un objet dérivé peut nécessiter d’être manipulé via un pointeur de sa
propre classe, de façon à pouvoir utiliser les méthodes (non
polymorphes) de sa classe
– comment faire si l'on veut fixer le salaire de cet Employe alors qu'on ne
dispose que d'un pointeur de type Personne sur cet objet ?
un transtypage est alors nécessaire pour y parvenir
236
236
LA MANIPULATION D'UN
OBJET DERIVE
int main () {
Employe duval ("DUVAL", 30000);
f(&duval);
}
void f(Personne *p)
Employe *pe=(Employe*)p; . .
p->fixer_salaire(5);
}
237
237
EXERCICE
Exo27: création d'un tableau d'objets recevant des objets des classes
Personne, Employe et Technicien
238
238
L'HERITAGE MULTIPLE
l'héritage multiple consiste à faire hériter une classe de plusieurs
classe parentes: il suffit d'indiquer la liste des classes parentes,
séparées les unes des autres par une virgule
class A {
. . .
};
class B {
. . .
};
class C : public A, public B {
. . .
};
Toute représentation ou reproduction intégrale ou partielle faite sans le consentement de l'auteur
Denis MADEC ou de ses ayants droits ou ayant cause est illicite selon le code de la propriété
intellectuelle du 1er juillet 1992 et constitue une contefaçon réprimée par le code pénal.
239
239
L'HERITAGE MULTIPLE
class Bateau {
float tonnage;
float tirant;
float longueur;
public:
Bateau();
Bateau(float,float,float);
float getTonnage() const;
float getTirant() const;
float getLongueur() const;
void setTonnage(float);
void setTirant(float);
void setLongueur(float);
void affiche() const;
};
240
240
L'HERITAGE MULTIPLE
class Avion {
float charge_alaire;
float envergure;
int vitesse_min;
public:
Avion();
Avion(float,float,int);
float getCharge_alaire() const;
float getEnvergure() const;
int getVitesse_min() const;
void setCharge_alaire(float);
void setEnvergure(float);
void setVitesse_min(int);
void affiche() const;
};
241
241
L'HERITAGE MULTIPLE
Un Hydravion EST-UN Avion ET un Bateau:
class Hydravion: public class Avion, public class Bateau {
float capacite;
public:
Hydravion();
Hydravion(float,float,int,float,float,float,float);
float getCapacite() const;
void setCapacite(float);
void affiche() const;
};
242
242
L'HERITAGE MULTIPLE
Hydravion::Hydravion(): capacite(0.0)
{}
Hydravion::Hydravion(float chg,float env, int vit ,float ton,
float tir,float lg,float cap):
Avion(chg,env,vit),Bateau(ton,tir,lg),capacite(0.0)
{}
float Hydravion::getCapacite() const {return capacite;}
void Hydravion::setCapacite(float cp) {capacite=cp;}
void Hydravion::affiche() const {
Avion::affiche();
Bateau.affiche();
cout<<"Capacite: "<<capacite<<endl;
}
243
243
L'HERITAGE MULTIPLE
REPETE
en supposant maintenant qu'une classe Vehicule existe:
class Vehicule {
int puissance;
int vitesse_max;
public:
Vehicule ();
Vehicule (int,float);
int getPuissance() const;
int getVitesse_max() const;
void setPuissance(int);
void setVitesse_max(int);
void affiche() const;
};
244
244
L'HERITAGE MULTIPLE
REPETE
il devient naturel d'établir les relations d'héritage entre Avion et
Vehicule d'une part, entre Bateau et Vehicule d'autre part:
class Avion:public Vehicule {
. . .
};
class Bateau:public Vehicule {
. . .
};
class Hydravion: public class Avion, public class Bateau {
. . .
};
245
245
L'HERITAGE MULTIPLE
REPETE
class Bateau: public Vehicule {
float tonnage;
float tirant;
float longueur;
public:
Bateau();
Bateau(int,float,float,float,float);
float getTonnage() const;
float getTirant() const;
float getLongueur() const;
void setTonnage(float);
void setTirant(float);
void setLongueur(float);
void affiche() const;
};
246
246
L'HERITAGE MULTIPLE
REPETE
class Avion: public Vehicule {
float charge_alaire;
float envergure;
int vitesse_min;
public:
Avion();
Avion(int,float,float,float,int);
float getCharge_alaire() const;
float getEnvergure() const;
int getVitesse_min() const;
void setCharge_alaire(float);
void setEnvergure(float);
void setVitesse_min(int);
void affiche() const;
};
247
247
L'HERITAGE MULTIPLE
REPETE
une instance d'Hydravion comporte alors les attributs de Vehicule en
double, puisqu'elle en hérite via la classe Avion mais aussi par la
classe Bateau
la modification de la puissance d'un objet Hydravion s'appliquera
seulement à l'un des deux attributs puissance, l'autre conservant sa
valeur, aucun mécanisme n'assurant la mise en phase des éléments
du doublon
le C++ permet de traiter ce cas particulier par utilisation de classes
de base virtuelles
248
248
L'HERITAGE MULTIPLE
REPETE
La classe Vehicule devient une classe de base virtuelle: une seule
instance de Vehicule apparaitra dans un objet Hydravion
class Avion: public virtual Vehicule {
. . .
};
class Bateau: public virtual Vehicule {
. . .
};
class Hydravion: public class Avion, public class Bateau {
. . .
};
249
249
L'HERITAGE MULTIPLE
REPETE
l'instance unique de Vehicule qui apparaît désormais dans
Hydravion doit par contre être initialisée explicitement par la classe
la plus dérivée, soit Hydravion
en effet, les constructeurs de Avion et Bateau n'initialiseront pas
l'instance Vehicule de l'objet Hydravion
250
250
L'HERITAGE MULTIPLE
REPETE
Hydravion::Hydravion(): capacite(0.0)
// appels implicites aux constructeurs sans argument:
// Avion(), Bateau(),Vehicule()
{}
Hydravion::Hydravion(int puis, int vmax,float chg,float env, int vmin,
float ton,float tir,float lg,float cap):
Avion(puiss,vmax,chg,env,vit),
Bateau(puiss,vmax,ton,tir,lg),
Vehicule(puiss,vit),
capacite(0.0)
{}
251
251
EXERCICE
Exo28: mise en place d'un héritage multiple répété
252
LE POLYMORPHISME
INTERET DU POLYMORPHISME
LES METHODES VIRTUELLES
LES DESTRUCTEURS VIRTUELS
LES METHODES ABSTRAITES
LES CLASSES ABSTRAITES
253
253
INTERET DU
POLYMORPHISME
le polymorphisme consiste à manipuler des objets sans se soucier de
leur classe (faisant néanmoins partie d'une hiérarchie de classes)
l'héritage autorise la manipulation d'objets dérivés en les considérant
comme des objets de leur classe de base (relation "EST-UN"),
cependant ils perdent alors leur comportement d'objet dérivé
si l'on souhaite manipuler des objets dérivés en les considérant
comme des objets de leur classe de base, tout en bénéficiant des
spécificités qui leurs sont rattachées, il faut définir des méthodes
polymorphes dites aussi "virtuelles"
des méthodes polymorphes ou virtuelles sont des méthodes
appartenant à des classes liées par héritage et qui ont le même
prototype, comportant de plus le préfixe virtual
254
254
LES METHODES
VIRTUELLES
class Personne {
char nom[20];
public:
virtual void affiche () { // méthode virtuelle
cout<<"nom : " <<nom<<endl;
}
};
class Employe:public Personne {
double salaire;
public:
void affiche() { // méthode virtuelle (le mot-clé virtual n'est pas nécessaire)
Personne::affiche();
cout<<"salaire : "<<salaire <<endl;
}
};
255
255
LES METHODES
VIRTUELLES
int main () {
Employe martin("MARTIN", 15000);
Personne *p=martin; // l'objet martin est une Personne
p->affiche(); // nom : MARTIN
// salaire : 15000
Personne& rp = martin; // l'objet martin est une Personne
rp.affiche(); // nom : MARTIN
// salaire : 15000
. . .
}
256
256
LES METHODES
VIRTUELLES
les méthodes polymorphes peuvent ne pas être redéfinies dans les
classes dérivées. Dans ce cas, ce sera la méthode polymorphe de la
classe de base immédiatement au-dessus qui sera utilisée
pour mettre en oeuvre le polymorphisme, la méthode polymorphe
doit être soit héritée, soit déclarée dans la classe de base au travers
de laquelle est manipulé l'objet dérivé
une méthode virtuelle dans les classes dérivées ne peut avoir un
accès plus restrictif que la méthodes d'origine: si cette dernière est
publique, la méthode virtuelle dans une classe dérivée doit aussi être
publique
257
257
LES METHODES
VIRTUELLES
class A {
public:
virtual int display() { ... }
virtual void affiche() { ... }
};
class B:public A {
public:
virtual char* getchaine() { ... }
virtual void affiche() { ... }
};
class C:public B {
public:
virtual char* getchaine() { ... }
virtual int display() { ... }
};
258
258
LES METHODES
VIRTUELLES
int main() {
B b;
B *pb=&b;
A *pa=pb;
C c;
B *ppb=&c;
A *ppa=ppb;
pa->affiche(); // affiche() de B : polymorphisme
pb->affiche(); // affiche() de B
pa->display(); // display() de A
pb->display(); // display() de A : héritage
ppa->affiche(); // affiche() de B : polymorphisme
ppa->display(); // display() de C : polymorphisme
ppa->getchaine(); // ERREUR! getchaine() n'existe pas dans A
ppb->getchaine(); // getchaine() de C : polymorphisme
ppb->display(); // display() de C : polymorphisme
}
259
259
LES DESTRUCTEURS
VIRTUELS
lorsque des objets sont manipulés via des pointeurs ou des références
du type d'une classe de base, la destruction des objets manipulés peut
présenter quelques difficultés
class A{
public:
. . .
~A() {…} // destructeur
};
class B:public A {
public:
. . .
~B() {…} // destructeur
};
260
260
LES DESTRUCTEURS
VIRTUELS
int main(){
A *pa=new B;
. . .
delete pa; // seul de destructeur de A est appelé
}
il est alors pudent de rendre virtuels les destructeurs de façon à ce
que le destructeur de l'objet réellement manipulé soit également
appelé
seul le destructeur de la classe de base nécessite la présence du mot-
clé virtual
afin de faciliter la compréhension des programmes et garantir leur
fonctionnement, il est souhaitable de placer virtual devant tous les
destructeurs
261
261
LES DESTRUCTEURS
VIRTUELS
class A{
public:
. . .
virtual ~A() {…} // destructeur vituel
};
class B:public A {
public:
. . .
~B() {…} // destructeur virtuel
};
262
262
EXERCICE
Exo29: mise en place d'une méthode virtuelle dans les classes
Personne, Employe, Actionnaire, Directeur
263
263
LES METHODES
ABSTRAITES
les méthodes abstraites sont des méthodes sans définition qui doivent
par conséquent être définies dans les sous-classes
en C++, les méthodes abstraites sont mises en œuvre par des
méthodes virtuelles pures
class Maclasse {
. .
virtual void f()=0; // méthode virtuelle pure (méthode sans définition)
};
une classe dont au moins une méthode est virtuelle pure est
nécéssairement une classe abstraite
toutes les méthodes virtuelles pures doivent être définies dans les
sous-classes sous-peine d'obtenir des sous-classes abstraites.
264
264
LES CLASSES
ABSTRAITES
une classe est abstraite si elle comporte au moins une méthode
virtuelle pure
une classe abstraite est une classe non instanciable
une classe abstraite a uniquement pour rôle de généraliser d'autres
classes en devenant ainsi leur classe parente
une classe abstraite correspond en général à un concept tout en
n'étant pas nécessairement justifié dans le cadre de la modélisation
du monde réel
class Component { // il s’agit de modéliser un composant
// graphique
. .
};
265
265
LES CLASSES
ABSTRAITES
l’intérêt est de factoriser les caractéristiques (attributs) et
comportement (méthodes) d’objets différents
s’il n’est pas possible de créer des objets d’une classe abstraite, il est
en revanche possible de définir des pointeurs ou des références sur
des objets des sous-classes
Component c; // ERREUR !
Button b;
Component *cx=&b; // OK, car Button hérite de Component
266
266
EXERCICE
Exo30: définition d'une classe abstraite comportant une méthode
virtuelle pure
267
IDENTIFICATION DE TYPE A
L'EXECUTION (RTTI)
Toute représentation ou reproduction intégrale ou partielle faite sans le consentement de l'auteur
Denis MADEC ou de ses ayants droits ou ayant cause est illicite selon le code de la propriété
intellectuelle du 1er juillet 1992 et constitue une contefaçon réprimée par le code pénal.
268
268
IDENTIFICATION DE TYPE
A L'EXECUTION (RTTI)
lorsque des objets sont manipulés via des références ou des pointeurs
du type de leur classe de base, une partie seulement de leurs
méthodes peut seule être utilisée
il est parfois nécessaire de pouvoir manipuler ces mêmes objets via
une référence ou un pointeur du type de leur propre classe: une
conversion explicite sera alors nécessaire, en utilisant dynamic_cast
dynamic_cast vérifie en effet que la conversion est légitime, sinon
renvoie la valeur 0 s'il s'agit d'une conversion entre pointeurs, et
lance une exception (Cf chapitre "Exceptions") de type bad_cast s'il
s'agit d'une conversion entre références
269
269
IDENTIFICATION DE TYPE
A L'EXECUTION (RTTI)
la fonction suivant reçoit en argument un tableau dans lequel se
trouvent des objets Avion, Bateau ou Hydravion
l'instruction dynamic_cast<Hydravion*>(t); essaie d'effectuer une
conversion du pointeur t de type Vehicule* vers le pointeur hp de
type Hydravion*
void getCapaciteFlotte(Vehicule *t, int n) {
float capacite=0.0f;
Hydravion *hp;
for(int i=0;i<n;i++,t++) {
hp=dynamic_cast<Hydravion*>(t);
if(hp!=NULL) capacite+=hp->getCapacite();
}
}
270
270
IDENTIFICATION DE TYPE
A L'EXECUTION (RTTI)
dans certains cas, il nécessaire de connaître précisément le type d'un
objet manipulé via un pointeur ou une référence d'un type d'une
classe de base
on parle alors de RunTime Type Information (RTTI)
dynamic_cast utilise ce mécanisme pour son fonctionnement
l'opérateur typeid va plus loin et renvoie un objet de la classe
type_info permettant d'obtenir le nom du type d'un objet ou
d'effectuer des comparaisons
syntaxe:
typeid(objet)
typeid(type)
271
271
IDENTIFICATION DE TYPE
A L'EXECUTION (RTTI)
l'exemple précédent peut être remodelé de façon à utiliser typeid
void getCapaciteFlotte(Vehicule *t, int n) {
float capacite=0.0f;
Hydravion *hp;
for(int i=0;i<n;i++,t++) {
if(typeid(*t)==typeid(Hydravion)) {
hp=dynamic_cast<Hydravion*>(t);
capacite+=hp->getCapacite();
}
}
}
272
272
IDENTIFICATION DE TYPE
A L'EXECUTION (RTTI)
les objets type_info peuvent être comparés grâce aux opérateurs ==
et != surchargés
le nom du type d'un objet peut également être obtenu grâce à la
méthode name de la classe type_info qui renvoie un pointeur char*
sur le nom du type
– Si p est un pointeur sur un objet, le type de cet objet apparaît par l'instruction:
cout<<"type: "<<typeid(*p).name()<<endl;
l'utilisation de type_info suppose la présence de la directive:
#include <typeinfo>
273
273
EXERCICE
Exo31: mise en œuvre de l'opérateur typeid sur les objets du tableau
tev
274
LES EXCEPTIONS
275
275
LES EXCEPTIONS
les anomalies survenant lors de l'exécution d'un programme C++
peuvent être traités par un mécanisme d'exceptions
ce mécanisme est contrôlé par les mots-clés try, catch, throw
le mot-clé try permet de définir un bloc d'instructions susceptible de
lancer une ou plusieurs exceptions
suivant immédiatement le bloc try, le mot-clé catch, suivi d'un
argument, permet de capturer une exception d'un certain type
il peut y avoir plusieurs catch successifs après un même bloc try.
Ceci permet de capturer des exceptions différentes pouvant se
produire dans les instructions du bloc try
276
276
LES EXCEPTIONS
try {
Chaine c1("Bonjour");// lance un objet de la classe bad_alloc si
c1.concat("à tous!"); // l'allocation dynamique ne peut avoir lieu
char c=c1[20]; // lance un objet de la classe Debordement
. . .
}catch(bad_alloc){
cerr<<"problème d'allocation!"<<endl;
exit(1);
}catch(Debordement d) {
cerr<<"debordement d'indice: "<<d.getIndice()<<endl;
exit(1);
}
277
277
LES EXCEPTIONS
class Chaine {
char *pch;
int longueur;
public:
Chaine();
Chaine(const char*);
~Chaine();
Chaine& copie(const char*);
Chaine& concat(const char*);
int compare(const char*) const;
char& operator[](int) const;
int length() const;
void print() const;
};
278
278
LES EXCEPTIONS
class Debordement {
int indice;
public:
Debordement(){indice=0;}
Debordement(int i){indice=i;}
int getIndice() const {
return indice;
}
};
char& Chaine::operator[](int) const {
if(i<0 || i>longueur) thow Debordement(i); // lance un objet exception
return *(pch+i);
}
279
279
LES EXCEPTIONS
lorsque l'on souhaite capturer toute exception, sans se préoccuper de
son type, on peut utiliser catch(…)
try {
. . .
}catch(…) {
cerr<<"exception inattendue!"<<endl;
exit(1);
}
280
280
EXERCICE
Exo32: mise en place d'une gestion d'exception dans la classe Chaine
281
LA GENERICITE
TEMPLATE DE FONCTION
TEMPLATE DE CLASSE
282
282
TEMPLATE DE FONCTION
lorsqu'une fonction doit être surchargée, il faut la définir autant de
fois que nécessaire, parfois en ne modifiant que légèrement le code
les patrons (template) de fonctions permettent de ne définir la
fonction qu'une seule fois, le type des arguments étant paramétré
tout template de fonction doit être précédé du mot-clé template suivi
de <class identificateur>, identificateur représentant le type qu'il
faudra préciser à l'appel
template <class Type> Type max(Type x, Type y) {
return x>y?x:y;
}
283
283
TEMPLATE DE FONCTION
int main() {
int a=5, b=7,c;
double d=78.90, e=564.89, f;
Counter c1(45), c2(876),c3;
c=max(a,b);
cout<<"max de a, b: "<<c<<endl;
f=max(d,e);
cout<<"max de d, e: "<<f<<endl;
c3=max(c1,c2);
cout<<"max de c1, c2: "<<c3<<endl;
. . .
}
284
284
TEMPLATE DE FONCTION
le compilateur substitue le paramètre Type du template de fonction
par le type correspondant à l'appel
le code de la fonction template doit être accessible au compilateur
lorsqu'il rencontre l'appel à cette fonction: ce code est en général par
commodité placé dans un fichier d'entête .h
plusieurs paramètres peuvent être précisés dans un template
template <class T1, class T2> T1 fonc(T1 x, T2 y) {
. . .
}
285
285
TEMPLATE DE CLASSE
comme les patrons de fonctions, les patrons de classe permettent de
définir une classe une seule fois, cette classe étant prévue pour
opérer sur un ou des types de données paramétrés
l'exemple suivant montre un patron de classe destiné à créer des piles
d'objets
dans leur définition, le nom des méthodes est précédé de Stack<T>:: et
chaque méthode est également précédée de template <class T>
lors de la création d'une instance d'un patron de classe, il faut préciser
explicitement le type utilisé, pour permettre au compilateur de générer de code
des méthodes
Stack<int> s1(50);
Stack<Counter> s2;
286
286
TEMPLATE DE CLASSE
template <class T> class Stack {
enum limits{EMPTY=-1, MAX_DEFAUT=100};
const int MAX_SIZE;
T* p;
int top;
public:
Stack();
Stack(int);
~Stack();
bool isempty() const;
T get() const;
Stack& put(T);
};
287
287
TEMPLATE DE CLASSE
template <class T> Stack<T>::Stack(): MAX_SIZE(MAX_DEFAUT),
p(new T[MAX_DEFAUT]), top(EMPTY)
{}
template <class T> Stack<T>::Stack(int taille):MAX_SIZE(taille),
p(new T[taille]), top(EMPTY)
{}
template <class T> Stack<T>::~Stack() {
delete[] p;
}
template <class T> bool Stack<T>::isempty() const {
return (top==EMPTY)?true:false;
}
288
288
TEMPLATE DE CLASSE
template <class T> T Stack<T>::get() const {
if(top==EMPTY) throw StackEmpty();
return p[top--];
}
template <class T> Stack& Stack<T>::put(T x) {
if(top<MAX_SIZE) p[++top]=x;
else throw StackFull();
return *this;
}
289
289
TEMPLATE DE CLASSE
int main() {
Stack<int> s1(50);
Stack<Counter> s2;
for(int i=0;i<10;i++) {
s1.put(2*i);
}
Counter c1,c2(5),c3(456),c4(765);
s2.put(c1).put(c2).put(c3).put(c4);
while(!s1.isempty()) cout<<s1.get()<<endl;
while(!s2.isempty()) cout<<s2.get()<<endl;
. . .
}
290
290
EXERCICE
Exo33: réalisation d'un template de classe
291
LA BIBIOTHEQUE STL
PRESENTATION
ENTREES/SORTIES
CHAINES DE CARACTERES
292
292
PRESENTATION
une des lacunes majeures du C++ a été, dès le départ, l'absence de
réelle bibliothèque associée au langage (hormis celle du C et
quelques classes d'entrées/sorties)
les éditeurs ont donc vendu leur compilateur C++ avec une
bibliothèque généralement bien garnie, liée à leur plateforme de
développement
aucune compatilité n'a été envisagée entre les bibliothèques des
différents éditeurs
cette lacune a été partiellement comblée avec l'apparition progressive
de la Standard Template Library (STL)
plusieurs versions de la STL ont vu le jour, la dernière étant définie
dans l'espace de noms std
293
293
PRESENTATION
la STL est une petite bibliothèque standard qui offre au développeur
des classes et fonctions template utilitaires
en plus des chaines de caractères et des entrées/sorties, cette
bibliothèque est en fait composée de 5 catégories principales
d'objets:
– les algorithmes
– les conteneurs
– les itérateurs
– les fonctions
– les adaptateurs
294
294
PRESENTATION
sur de nombreux compilateurs cohabitent encore l'ancienne et la
nouvelle bibliothèque
l'ancienne bibliothèque n'utilise pas les espaces de noms et il suffit
d'inclure les fichiers d'entête .h adéquats pour compiler
#include <iostream.h>
la nouvelle bibliothèque définit toutes ses classes et fonctions dans
l'espace de noms std et utilise des fichiers d'entête sans extension .h
#include <iostream>
using namespace std;
il est donc possible d'utiliser les classes des deux bibliothèques dans
un même programme
295
295
PRESENTATION
afin de faciliter la cohabitation avec les programmes C, la
bibliothèque du C intégrée avec les compilateurs C++ possède
également une version équivalente dans l'espace de noms std: ses
fichiers d'entête reprennent le même nom que ceux du C, sans
l'extension .h et avec la lettre c comme préfixe
#include <cstdio>
#include <cstring>
296
296
ENTREES/SORTIES
les entrées/sorties sont gérées en C++ sous la forme d'objets,
représentant par exemple l'écran, le clavier, un fichier, auxquels on
applique des méthodes
les classes d'entrées/sorties les plus utilisées sont istream et ostream
il existe 3 objets prédéfinis cin, cout, cerr correspondant
respectivement aux fichier standard d'entrée, fichier standard de
sortie, fichier standard d'erreur
par défaut, ces fichiers standards sont associés au clavier pour cin, à
l'écran pour cout et cerr
les méthodes les plus couramment utilisées pour cin et cout sont
respectivement les opérateurs surchargés >> et << pour les types
bool, short, int, long, float, double, char et char*
297
297
ENTREES/SORTIES
int main() {
int a;
float b;
char c;
char s[32];
cout<<"saisir un entier: ";
cin>>a;
cout<<"saisir un float: ";
cin>>b;
cout<<"saisir un caractère: ";
cin>>c;
cout<<"saisir une chaine de caractères: "
cin>>s;
. . .
}
298
298
ENTREES/SORTIES
des manipulateurs peuvent être utilisés conjointement avec les
opérateurs >> et << pour formater les données saisies ou affichées:
endl: : pour insérer le caractère '\n' et vider le buffer de sortie
dec : pour passer au mode décimal (mode par défaut)
oct : pour passer au mode octal
hex:: pour passer au mode hexadécimal
ws : : pour enlever des espaces de part et d'autre d'une donnée
flush : pour vider le buffer de sortie sur le fichier de sortie
setw(int) : pour indiquer la largeur du champ de la donnée
setprecision(int) : pour indiquer la precision des nombres flottants
setfill(char) : pour indiquer le caractère de remplissage des champs
299
299
ENTREES/SORTIES
int main() {
int a=10;
char temp[128];
cout<<"a en octal: "<<oct<<setfill('.')<<setw(6)<<a<<endl;
cout<<"a en hexadécimal: "<<hex<<setfill('.')<<setw(6)<<a<<endl;
cout<<"a en décimal: "<<dec<<setfill('.')<<setw(6)<<a<<endl;
const double pi=3.141592654;
cout<<setprecision(12)<<pi<<endl;
cout<<"saisir une chaine de caractères:"<<flush;
cin>>ws>>temp;
. . .
}
300
300
ENTREES/SORTIES
les caractères séparateurs pris en compte par l'opérateur >> sont:
espace, tabulation, saut de page et retour chariot
– il suffit ici d'un espace entre les deux nombre saisis sur la même ligne pour les
attribuer aux variables a et b
int a;
double b;
cout<<"saisir un entier, puis un flottant: "
cin>>a>>b;
301
301
ENTREES/SORTIES
l'opérateur >> renvoie par référence l'objet auquel il est appliqué et
null (false) lorsque la fin du flux est atteinte
– ceci permet d'effectuer des boucles très simples:
while (cin>>c) cout<<c;
d'autres méthodes que l' opérateur >> sont disponibles dans la classe
istream, dont:
istream& get(char& c) pour saisir un caractère quelconque dans c
void getline(char* buffer,int limite,char delim) pour saisir une chaine de
caractères
void putback(char c) pour replacer le caractère c dans le flux
302
302
ENTREES/SORTIES
la manipulation de fichiers consiste à mettre en œuvre des méthodes
des classes ofstream, ifstream ou fstream
en plus de méthodes et opérateurs cités précédemment, ces classes
disposent de méthodes spécifiques aux fichiers
l'ouverture d'un fichier est effectuée simplement lors de la
construction d'un objet, sa fermeture doit être explicite
ofstream ficout("beta.txt");
char buffer[512];
while(cin>>buffer) ficout<<buffer;
ficout.close();
303
303
ENTREES/SORTIES
l'ouverture réussie d'un fichier peut être testée simplement par le test
de l'objet
ifstream ficin("alpha.txt");
char buffer[512];
if(!ficin) {
cerr<<"ouverture du fichier impossible!"<<endl;
. . .
}else {
while(ficin>>buffer) cout<<buffer<<endl;
ficin.close();
}
304
304
ENTREES/SORTIES
par défaut, un objet ifstream correspond à un fichier ouvert en mode
lecture seule (le mode "r" de la fonction fopen du C)
par défaut, un objet ofstream correspond à un fichier ouvert en mode
écriture seule (le mode "w" de la fonction fopen du C)
d'autres modes d'ouverture sont disponible en indiquant des
constantes binaires en argument supplémentaire du constructeur
ces constantes de classe publiques sont définies dans la classe ios et
peuvent être combinées ensemble par l'opérateur sur bits | (OU)
305
305
ENTREES/SORTIES
Constantes de classe de ios équivalence au mode fopen
ios::in "r"
ios::out | ios::trunc "w"
ios::out | ios::app "a"
ios::in | ios::out "r+"
ios::in | ios::out | ios::trunc "w+"
ios::in | ios::out | ios::app "a+"
par exemple, pour ouvrir un fichier en mode "a", il suffit d'écrire:
ofstream ficout("beta.txt", ios:out | ios::app);
306
306
EXERCICE
Exo34: copie octet par octet d'un fichier dans un autre
307
307
CHAINES DE
CARACTERES
les chaines de caractères sont avantageusement traitées en C++ sous
la forme d'objets de la classe string
cette classe est définie dans le fichier d'entête string
308
308
CHAINES DE
CARACTERES
class string {
string();
string(const char*);
string(const String&);
int length() const;
int size() const;
string substr(index0,index1) const;
void insert (index, const string&);
void erase (index0, index1);
void replace (index0,index1, const string&);
string operator+(const string&);
String& operator=(const string&);
bool empty() const;
. . .
};
309
BIBLIOGRAPHIE
310
310
BIBLIOGRAPHIE
C++, La synthèse de G. Clavel, éditions Dunod
– très pédagogique
– 320 pages en français
– idéal pour débuter
Langage C++ de Nino Silverio, éditions Eyrolles
– pédagogique
– plus de 600 pages en français
– assez complet
Programmer en langage C++ de C. Delannoy, éditions Eyrolles
– très pédagogique
– plus de 600 pages en français
– très complet
311
311
BIBLIOGRAPHIE
The C++ programming language de Bjarne Stroustrup, éditions
Addison Wesley
– élaboré par l'auteur du langage
– très complet
– plus de 1000 pages en anglais
– pas spécialement pédagogique
C++ Primer de S. Lipmann, éditions Addison Wesley
– assez pédagogique
– très complet
– plus de 1200 pages en anglais