Vous êtes sur la page 1sur 178

UNIVERSITE SULTAN MOULAY SLIMANE

ECOLE NATIONALE DES SCIENCES APPLIQUÉES


KHOURIBGA

Chap 1:
Rappels C++
Pr SAADI Mostafa
1
Programmation Orientée Objets

Processus et procédures
 Les normes ISO sont élaborées au travers de processus précis comprenant
plusieurs stades prédéfinis afin de créer un consensus à l’échelle d’un secteur
industriel.
 La normalisation internationale est un processus multi-parties prenantes
impliquant l’industrie, la science, les milieux universitaires, les consommateurs et
les gouvernements.
 L’ISO publie des normes, mais aussi d’autres référentiels comme les spécifications
publiquement disponibles (PAS), les guides, les spécifications techniques (TS), les
rapports techniques (TR) et les accords internationaux d’atelier (IWA).
 Les procédures pour l’élaboration des normes ISO sont définies dans les Directives
ISO/CEI (en deux parties) et le Supplément ISO, trois documents essentiels qui
décrivent les procédures et les règles de rédaction à suivre par les comités
techniques de l’ISO.
Cette section présente les trois documents centraux qui décrivent les règles
procédurales fondamentales à suivre par les comités de l'ISO, à savoir:
 Directives ISO/CEI, Partie 1: Procédures pour les travaux techniques
 Directives ISO/CEI, Supplément - Procédures spécifiques à l'ISO
 Directives ISO/CEI, Partie 2: Règles de structure et de rédaction des Normes
internationales
L'accès est également donné aux références normatives citées dans ces documents
centraux et aux Guides ISO. (Note: le visionnement des Références normatives est
soumis à un contrôle d'accès - un nom d'utilisateur et un mot de passe sont exigés).
Les formulaires à utiliser conjointement avec les trois documents centraux sont
disponibles pour un téléchargement sur www.iso.org/forms.
L'ISO/CEI JTC 1 (Technologies de l'information) a adopté les Directives ISO/CEI,
Partie 1 avec le Supplément JTC 1 (en anglais).

Regardez la version brouillon ISO/CEI pour les nouvelles pour le C++.


Programmation Orientée Objets


Critères de ''qualité'' d'un logiciel


L'exactitude

La robustesse

L'extensibilité

L'efficience

La portabilité

La réutilisabilité

La modularité
Programmation Orientée Objets

Programmation structurée

La construction d'un logiciel se base sur l'équation de WIRTH:
Algorithmes + Structures de données = Programme

La construction d'un programme structuré peut être basée soit:
 Sur les traitements
 Sur les données.
Concepts fondamentaux des objets


Encapsulation des données
consiste à faire une distinction entre l'interface de l'objet et son
implémentation.

Implémentation
Données Méthodes * Interface: décrit ce que fait
l'objet

Priv Priv Priv


é é é * Implémentation: définit
Publi Publi Publi comment réaliser l'interface.
c c c

Interface
Concepts fondamentaux des objets

Encapsulation des données (suite)
le principe de l'encapsulation est qu'on ne peut agir que sur les propriétés
publiques d'un objet.
Exemple de la nécessité de protéger les données:
supposons qu'on a une classe Température;
class Temperature:
attributs public: int TC; // température en ° Celsius
int TK; // température en Kelvin
supposons qu'on a une température TC=35 et TK=108 ; et que dans le code on a modifié
TC=40 (TK est toujours égale à 108) il n' y a plus de cohérence des données.
Le bon code serait:
class Temperature:
attributs private: int TC; méthode public: set_TC(t){ TC=t;
int TK;

TK=t+273;}
Concepts fondamentaux des objets


Communication par messages
les objets communiquent entre eux par des messages ( un objet demande un
service à un autre objet) pour résoudre un problème final .
 Association simple
Objet 1 Message
 Agrégation
 Composition
Objet 3
 ...

Objet 2
Remarque: Les relations d'association, d'agrégation et de composition s'expriment
en insérant des variables membres dans une définition de classe.
Concepts fondamentaux des objets


Identité et classification
Consiste à regrouper les objets ayant le même comportement pour former un
même ensemble , appelé: classe ( c.à.d un nouveau type).
Un objet d'une classe s'appellera une instance de cette classe.
En C++ on déclare une classe par :
class Maclasse { ..........};
Exemple: class Livre {
char* titre; Une instance de cette classe par exemple:
Le livre:
char* auteur; Titre= ''Léon l'africain''
Auteur=''Amine Maâlouf''
void lire(){....};
};
Concepts fondamentaux des objets


Héritage
Consiste à définir une nouvelle classe à partir d'une classe existante à laquelle
on va ajouter de nouvelles propriétés ( attributs et/ou méthodes).
Concepts fondamentaux des objets


Polymorphisme
possibilité à divers objets de classes dérivées d'une même classe de répondre
au même message chacun à sa façon. i.e un même nom peut désigner des
propriétés de classes différentes.
Exemple: on peut invoquer la méthode ouvrir() quelque soit le type de la
porte automatique ou classique.

Généricité
consiste à définir des classes paramétrées. Une classe générique n'est pas
directement utilisable, mais permet de créer des classes dérivées qui
peuvent être manipulées.

Modularisation
les modules sont construits autour des classes.
Concepts fondamentaux des objets


La programmation basée sur les objets et leurs concepts fondamentaux
est dite la
Programmation Orientée Objets
(POO)

L'approche objet répond bien aux critères de qualités de production d'un
logiciel.
Le Langage C++

Un peu d'histoire
C++ se base essentiellement sur 2 langages:
 Simula 67 (1967) crée pour le traitement des problèmes de simulation, dont il hérite le concept
objet ( un programme autonome actif pouvant communiquer et se synchroniser avec d'autres
objets) .
 Langage C (1972) aux Bell Labs.

Bjarne Stroustrup (Bell Labs), le concepteur du C++, désirant ajouter au C


les classes de simula,créa le C++ après plusieurs version il aboutit en 1983
à une version stable.
Le Langage C++


C++ versus C
C++ dispose d'un certain nombre de spécificités par rapport à C en dehors de
l'orienté objet:
 Les commentaires
 L'emplacement libre des déclarations des variables
 Les arguments par défaut
 La surcharge (surdéfinition) des fonctions
 Les opérateurs new et delete
 Les fonctions en ligne ( inline)
 Les références
 ...
Le Langage C++

La fonction principale :
La fonction main() est, comme en C, le point d'entrée de tout programme C+
+. Elle peut être définie de 2 manières:
 Pour les programmes sans paramètres: int main() {....}
 Pour les programmes avec paramètres:
int main( int argc, char* argv[]){...}

où argc: le nombre de paramètres


et argv[]: tableau de paramètres
Le Langage C++

Les commentaires
 Sur plusieurs lignes: /* .............comme en C....
...............*/

 Sur une seule ligne: //.... spécifique C++......


Le Langage C++
Les E/S
Entrées/sorties fournies à travers la librairie < iostream>
 cout << expr1 << … << exprn
− Instruction affichant expr1 puis expr2, etc.
− cout : « flot de sortie » associé à la sortie standard (stdout)
− << : opérateur binaire associatif à gauche, de première opérande
cout et de 2ème l’expression à afficher, et de résultat le flot de sortie
− << : opérateur surchargé (ou sur-défini)⇒ utilisé aussi bien pour les chaînes de
caractères, que les entiers, les réels etc.
 cin >> var1 >> … >> varn
− Instruction affectant aux variables var1, var2, etc. les valeurs lues au clavier.
− cin : « flot d’entrée » associée à l’entrée standard (stdin)
− >> : opérateur similaire à <<
Le Langage C++
E/S (2)
Possibilité de modifier la façon dont les éléments sont lus ou écrits dans le flot :

#include <iostream.h>
#include <iomanip.h> // attention a bien inclure cette librairie
int main() {
int i=1234;
float p=12.3456;
cout << "|" << setw(8) << setfill('*')
<< hex << i << "|" << endl << "|"
<< setw(6) << setprecision(4)
<< p << ''|'' << endl;
}
Le Langage C++


Les types de base
 Héritage des mécanismes de bases du C (pointeurs inclus)
Attention : typage fort en C++!!
 Déclaration et initialisation de variables :
bool var_bool = true; // variable boléenne nouveu type en C++
int i = 0; // entier
long j = 123456789; // entier long
float f = 3.1; // réel
// réel à double précision
double pi = 3.141592653589793238462643;
char c=‘a’; // caractère
 « Initialisation à la mode objet » :
int i(0) ;
Long l (123456789);
Le Langage C++

Type de base (2)
Le type d’une donnée détermine :
􀂃 La place mémoire (sizeof())
􀂃 Les opérations légales
􀂃 Les bornes
Le Langage C++

Les constantes
 Le qualificatif const peut être utilisé pour une expression constante:
const type_var var=cte;

 Une expression déclarée avec const a une portée limitée au fichier


source qui la contient.
Exemple:
const int N=5; // par convention une constante est écrite en majuscule
int t[N]; // en C il fallait #define N 5
Le Langage C++

Les références
 une référence sur une variable est un identificateur qui joue le rôle d'un alias (pseudo)
de cette variable.

Syntaxe: type &nom_ref = var;

exemple:
int n;
int &rn=n; // rn est une référence de n
n=10;
cout<< rn; //affiche 10

Une référence ne peut être vide, elle doit toujours être initialisée lors de sa déclaration,
i.e : int &rn ; // erreur!

Il est possible de référencer des valeurs numériques, dans ce cas il faut les déclarer
comme constantes, i.e: int &rn=3; // erreur!
const int &rn=3; // OK

Les références et les pointeurs sont liés.
Le Langage C++


Déclaration des fonctions

L'utilisation d'une fonction sans aucune déclaration ou définition au préalable
erreur à la compilation.

Le prototype doit figurer dans tout fichier source qui utilise cette fonction et ne
contenant pas sa définition.

Une fonction en C++ doit spécifier son type de retour, void si elle ne retourne rien.
fct(int, int); // erreur!
void fct(int, int); //OK

 int fct(void); int fct();


Le Langage C++


Transmission des arguments
En C++ il y a 3 méthodes de passage des variables en paramètres à une fonction:
 Passage par valeur: la valeur de la variable en paramètre est copiée dans une variable
temporaire. Les modifications opérées sur les arguments dans la fonction n'affectent pas la
valeur de la variable passée en paramètre.
 Passage par adresse: consiste à passer l'adresse d'une variable en paramètre. Toute
modification du paramètre dans la fonction affecte directement la variable passée en
paramètre.
 Passage par référence: le passage par adresse présente certains inconvénients, pour
résoudre ces inconvénients, C++ introduit le passage par référence.
En pratique, il est recommandé ( pour des raisons de performances) de passer par référence
tous les paramètres dont la copie peut prendre beaucoup de temps.
Le Langage C++


Arguments par défaut
 C++ offre la possibilité de donner des valeurs par défaut aux
paramètres d'une fonction ( exo TD).
 Une fonction peut définir des valeurs par défaut pour tous ses
paramètres ou seulement une partie.
 Les valeurs par défaut doivent être mentionnées soit dans le prototype
de la fonction soit dans sa définition.
 Les paramètres ayant une valeur par défaut doivent être placés en
dernier dans la liste des arguments.
Void fct ( int = 33 , int); //erreur!
Le Langage C++


Surcharge des fonctions
 La surcharge (surdéfinition) des fonctions consiste à donner un même nom à plusieurs
fonctions.
int max(int a, int b); // fonction 1
int max( int a, int b, int c); // fonction 2
int max( int* tab, int taille); // fonction 3
 Pour différencier entre deux fonctions qui portent le même nom, le compilateur
regarde le type et le nombre des arguments effectifs: la liste des types des arguments
d'une fonction s'appelle la signature de la fonction.
 La surcharge n'est acceptable que si toutes les fonctions ont des signatures différentes,
et n'a un sens que si les surdéfinitions ont un même but.
 Il est également possible de surcharger les opérateurs( voir les classes)
Le Langage C++


Les fonctions inline
 Une fonction ''inline'' est une fonction dont les instructions sont
incorporées par le compilateur à chaque appel.
Syntaxe: inline type fonct( arguments...) {… … }
 Les fonctions ''inline'' permettent de gagner au niveau temps d'exécution,
mais augmentent la taille des programmes en mémoire.

Contrairement aux macro dans C, les fonctions ''inline'' évitent les effets
de bord ( dans une macros l'argument peut être évalué plusieurs fois avant
l'exécution de la macro).
Le Langage C++

Allocation dynamique
en C la manipulation dynamique de la mémoire se fait avec malloc et free
(<stdlib.h>). En C++, ces fonctions remplacées avantageusement par les
opérateurs unaire new et delete.
main()
{
int *pi = new int;
int *tab = new int[10];
if ((pi != NULL) && (tab != NULL))
{
...
delete pi;
delete [] tab;
}
}
Classes & objets


Définition d'une classe
La déclaration d'une classe consiste à décrire ses membres (membres
données et prototypes de ses fonctions membres) groupés en sections.
Chaque section est étiquetée par l'un des mots clés : private, public, ou
protected, qui précisent le mode d'accès aux membres contenus dans la
section.
♦ private: accès autorisé seulement par les fonction membres
♦ public: accès libre
♦protected: accès autorisé seulement dans les fonctions membres de la classe
et de ses dérivées (voir héritage).
Pour déclarer une classe, on utilise le mot clé class.
Classes & objets


Exemple
class Point
{
private:
int x;
Données membres ( ou attributs) privés
int y;
public:
void initialise(int,int); Méthodes public
void deplace(int,int);
void affiche();
} ;
Attention au point virgule après la définition d'une classe
Classes & objets


La mention d'accès par défaut dans une classe est private:
class Point
{
int x;
int y; // membres privés par défaut
public: // membres publiques
……};

La définition d'une classe consiste à définir les prototypes des
fonctions membres. Pour les définir on utilise l'opérateur de
portée (::) :
type nom_class::fct(arguments) {……}
Classes & objets

 Au sein de la définition d'une fonction membre, les autres


membres (privés ou publiques) sont directement accessibles (il
n'est pas nécessaire de préciser le nom de la classe):
void point::initialise(int abs, int ord)

x = abs;

y = ord;

Remarques:
 Toutes les possibilités offertes par C++ pour les fonctions restent valables
pour les fonctions membres (surcharge, arguments par défaut, …).
 Toute fonction membre définie dans sa classe (dans la déclaration de la
classe) est considérée par le compilateur comme une fonction inline. Le mot
clé inline n'est plus utilisé.
Classes & objets

Utilisation d'une classe
 Un objet (ou instance) nom_objet d'une classe, nommée nom_classe
est déclaré comme une variable de type nom_classe :
nom_classe nom_objet;

 On peut accéder à n'importe quel membre publique d'une classe


en utilisant l'opérateur (.) (point). Par exemple:

Point A;
……
A.initialise(10,12); // appel de la fct membre initailise de la classe 'Point'
//Cout<<'' l'abscisse du point A est'' <<A.x<<endl; //erreur car x est un attribut privé!!
A.affiche();
Classes & objets
Constructeur/Destructeur
 C++ permet l'utilisation de fonctions membres dites constructeurs et
destructeur qui sont implicitement appelées respectivement lors de la
création et la destruction d'un objet.

 Le(s) constructeur(s) sont des fonctions membres qui portent le même


nom que leur classe. Ces constructeurs sont appelés après l'allocation de
l'espace mémoire destiné à l'objet.
 Un constructeur peut être surchargé et avoir des arguments par défaut.
Classes & objets

Constructeur / Destructeur (suite)


Exemple:
Class Point
{
int x;
int y;
public:
Point(int,int); // constructeur
void deplace(int,int);
void affiche();
};
Dans cet exemple, la définition du constructeur sera de la forme:
Point::Point(int abs, int ord)
{
x = abs; Remarque: Même si le constructeur ne retourne aucune valeur,
y = ord; il est déclaré et défini sans le mot clé void .
}
Classes & objets
Constructeur/Destructeur (suite 2)
 Pour déclarer une instance d'une classe ayant un constructeur, on doit
spécifier les valeurs des arguments requis par le constructeur.
Point A(2,5);
Point B; // erreur , car on a déclaré la façon de construire les objets
 Le destructeur est une fonction membre qui porte le même nom que sa
classe, précédé du symbole (~). Le destructeur est appelé avant la libération
de l'espace associé à l'objet.
 Un destructeur ne peut pas être surchargé.

Exemple:
class Point {
int x;
int y;
public:
Point(int,int); // constructeur
~Point(); // destructeur Dans cet exemple, la définition du destructeur sera de la forme:
……
Point::~Point() {… … }
};
Classes & objets


Constructeur/Destructeur
 Le destructeur est une fonction qui ne prend aucun argument et ne
renvoie aucune valeur.
 En pratique les destructeurs sont utilisés pour libérer d'éventuels
emplacements mémoire occupée par des membres données.
Classes & objets
Exemple:
Considérons par exemple une classe Tab_entiers, qui permet de traiter des tableaux d'entiers
dont les dimensions sont fournies en données. Le constructeur aura donc la tâche d'allouer
dynamiquement l'espace mémoire nécessaire pour l'instance à créer et le destructeur doit
libérer cette zone.

class Tab_entiers
{ /*------------------- définition du constructeur ---------------------*/
int nb; Tab_entiers::Tab_entiers(int n)
{
int * tab; nb = n;
tab = new int [nb];
public: }
/*------------------ définition du destructeur -----------------------*/
Tab_entiers(int); // constructeur
Tab_entiers::~Tab_entiers()
~Tab_entiers(); // destructeur {
delete tab;
…… }
};
Classes & objets


Exercice:
1) On désire munir la classe Point d'un constructeur qui permet de créer le point :
• (0,0) si aucun argument n'est fourni
• (abs,0) si on lui fournit abs comme seul argument
• (abs,ord) si on lui fournit les deux arguments abs et ord

2) On désire maintenant que le constructeur de la classe Point créer le point


• (abs,abs) si on lui fournit un seul argument (abs)
Classes & objets


Affectation entre objets
 C++ autorise l'affectation d'un objet d'un type donnée à un autre objet de
même type. Dans ce cas il recopie tout simplement les valeurs des
membres données (privés ou publiques) de l'un dans l'autre.
Point A,B; // déclare deux instances de la classe Point
A.initialise(2,5); // A.x = 2 et A.y = 5
B =A; // B.x = 2 et B.y = 5
 Il faut noter que les pointeurs ne sont pas pris en considération dans un cas
simple d'affectation : Si parmi les membres données, se trouve un pointeur,
l'emplacement pointé ne sera pas recopié (voir surcharge des opérateurs).
Classes & objets


Attribut statique
 Un membre donnée déclaré avec l'attribut static est une donnée partagée
par toutes les instances d'une classe.
 Un membre donnée statique est initialisé par défaut à zéro. Mais :
♦ Il doit être défini à l'extérieur de la déclaration de la classe, même s'il est privé, en
utilisant l'opérateur de porté (::).
♦ Ne peut être initialisé à l'intérieur de la classe.

 L'accès à un membre donnée statique d'une classe suit les mêmes règles
que les autres membres. D'autre part, un membre donnée statique est une
donnée qui existe même si aucun objet de cette classe n'est déclaré, dans ce
cas l'accès se fait à l'aide du nom de la classe et l'opérateur de porté (::).
Classes & objets
Exemple:
class cpt_obj
{
static int nb_obj;
int a;
public:
cpt_obj(int); // constructeur
……
};
/*-------------------------------- définition du membre statique -----------*/
int cpt_obj::nb_obj; // par défaut nb_obj=0
/*------------------------ définition du constructeur -------------------------*/
cpt_obj::cpt_obj(int n) {
a = n;
nb_obj++;
}
Classes & objets

Exploitation des classes dans des projets C++:


Pour les applications nécessitant beaucoup de classes, il est souhaitable que
celles-ci figurent dans des fichiers différents. On travaille alors de la manière
suivante :
- les déclarations des classes sont faites dans des fichiers d’extension .h
(fichiers d’entête)
- les définitions des fonctions membres sont dans des fichiers
d’extension .cpp
- l’utilisation de ces classes apparaît finalement dans un fichier
d’extension .cpp contenant la fonction void main(void).
Classes & objets
Exemple:
Classes & objets

Exemple:

 #ifndef, #define et #endif sont ajoutés aux fichiers include pour que le
fichier ne soit inclus qu’une seule fois lors d’une compilation.
 Enfin, dans tout programme utilisant la classe nom_classe, on doit inclure
le fichier d'entête ''nom_classe.h''. Un tel programme doit aussi pouvoir
accéder au module objet résultant de la compilation du fichier source
contenant la définition de la classe.
Classes & objets
Objets transmis en argument:
Considérons une classe T et une fonction F dont l'un des paramètres est un
objet de T transmis par valeur, par adresse ou par référence. Soit U une
instance de T transmis en argument à F, alors:
1. Si F est une fonction membre de T, elle aura accès à tous les membres
données de U, sinon elle n'aura accès qu'aux membres publiques de U.
2. Si la transmission de U se fait par valeur, il y a recopie des membres
données de U dans un emplacement locale à F, ce qui entraîne certains
problèmes si la classe contient des pointeurs.
Classes & objets
Exemple:
Définir une fonction qui permet de comparer deux instances de la classe
Point. Cette fonction devra retourner "true" si les deux objets coïncident et
"false" sinon.
La comparaison nécessite l'accès aux coordonnées des points, qui sont des
données privés, par suite, la fonction doit être une fonction membre de la
classe.
La déclaration de la fonction dans la classe sera :
bool coincide(Point);

Et sa définition:
bool Point::coincide(Point pt)
{
return ( (pt.x == x) && (pt.y == y));
}
Classes & objets
Objet fourni en valeur de retour
Etant donné une classe T et une fonction F qui a l'une des formes
suivantes : T F(arguments); // retour par valeur
T * F(arguments); // retourne l'adresse
T & F(arguments); // retourne une référence
Alors F aura accès à tous les membres de l'objet retourné si elle est une fonction
membre de T, si non elle n'aura accès qu'aux membres publics de la classe.
• Notez bien que d'une manière générale, une fonction ne doit pas retourner un
pointeur (ou une référence) sur une variable locale, du fait que la zone mémoire
occupée par une variable locale à une fonction est automatiquement considérée
comme libre à la fin de la fonction. Ainsi, par exemple :
int * fct_adr() { int n; … …, return &n}
int & fct_ref() { int n; … …, return n}
fournirons des résultants imprévisibles et erronés.
Classes & objets

Exemple:
On désire définir la fonction symetrique() qui permet de retourner le symétrique d'un
point de la classe Point.
Cette fonction doit être une fonction membre de la classe Point, puisqu'elle doit
accéder aux coordonnées du point, qui sont des données privées. La valeur de
retour sera de type Point La déclaration de la fonction dans la classe sera :
Point symetrique();
et sa définition
Point Point::symetrique()
{
Point pt;
pt.x = -x; pt.y = -y;
Return pt;
}

Question: Peut-on transformer cette fonction pour qu'elle fournisse un pointeur


(ou une référence) du résultat ?
Non, puisque le calcul du résultat doit se faire sur une variable locale à la fonction.
Classes & objets


Fonctions membres statiques
On distingue deux types de membres :
♦ Membres d'instance : membres associés à une instance de la classe.
♦ Membres de classe : membres associés à la classe et qui ne dépendent d'aucune instance de
la classe.
• Les membres de classe, sont aussi dits membres statiques. Ils sont déclarés
avec l'attribut static, et existent même si aucun objet de la classe n'est crée.
• Ainsi on peut définir un membre donnée statique comme on peut définir une
fonction membre statique. L'accès à ces membres se fait avec l'opérateur de
résolution de portée (::) précédé par le nom de la classe ou d'un quelconque
objet de la classe
Classes & objets

Exemple:
// Interface de la classe : POINT.H
// Corps de la classe : POINT.CPP
#ifndef POINT_H
#include "Point.h"
#define POINT_H // definition obligatoire du membre donné statique
int point::nb_obj;
#include <iostream.h> // utilisé dans affiche()
Point::Point(int abs, int ord)
class Point{ {
int x; x = abs; y = ord;
nb_obj++;
int y; }
static int nb_obj; Point::~Point()
{
public: nb_obj--;
Point(int = 0, int = 0); }
void Point::affiche()
~Point(); {
void affiche(); cout << "(" << x << "," << y << ")" << endl;
}
static void affiche_nbobj();
// definition de la fonction membre static
}; void Point::affiche_nbobj()
#endif {
cout<< ''le nombre d'objets est''<<nb_obj<<endl;
}
----
Classes & objets

Exemple (suite)
// Programme test
#include "Point.h"
void main()
{ //acces à la fonction membre static avant la création des objets
Point::affiche_nbobj();
// cout << Point::nb_obj ; // erreur : nb_obj est privée
// Appel de la fct membre en utilisant un objet
Point A;
A.affiche_nbobj();
// Appel de la fonction membre statique en utilisant le nom de la classe
Point B(5,6);
Point::affiche_nbobj();
}
Classes & objets

Les fonctions membres constantes
Les objets, comme les autres types de C++, peuvent être déclarées constants
avec l'attribut const. Dans ce cas, seules les fonctions membres déclarées et
définies avec l'attribut const peuvent être appelées par des objets constants.
Exemple:
class T { … ...
public:... // déclarations
type_a F(...); // fct membre ordinaire T u; // instance non constante
const T v; // instance constante
type_b G(…) const; // fct membre constante // appels
type_c K(const T); // fct avec argument constant u.F(…); // OK
v.F(…) ; // erreur: instance constante
}; u.G(…) ; // OK
v.G(…); // OK

T w;
u.K(v); //OK
u.K(w); // OK
v.K(w); // erreur:instance constante et fct non
constante

Remarque: Une méthode constante peut être appelée sur un objet variable
ou constant.
Classes & objets

On adoptera donc la discipline de programmation suivante :


. méthode d’accès aux données ou « accesseur » : il s’agit des méthodes
qui retournent ou affichent des données de l’objet sans les modifier. Les
accesseurs doivent être des méthodes constantes.
. méthode de modification des données ou « modificateur » ou «
mutateur » : il s’agit des méthodes qui modifient les données de l’objet.
Un modificateur ne doit donc pas être appelé sur un objet constant. Par
conséquent, un modificateur ne doit surtout pas être déclaré comme
méthode constante.
Classes & objets

Déclaration et initialisation des objets


• Les objets suivent les mêmes règles que les variables ordinaires, nous
distinguerons alors :
♦ Les objets globaux : ceux qui sont déclarés en dehors de tout bloc.
♦ Les objets automatiques : ceux qui sont déclarés au sein d'un bloc
♦ Les objets statiques : ce sont les objets locaux statique (définie avec le mot
clé static)
Classes & objets

Cas d'une classe sans constructeur


• Les données membres des objets globaux et statiques, qui ne sont pas des
pointeurs, seront initialisés par défaut à 0.
• L'initialisation explicite des objets lors d'une déclaration ne peut se faire
qu'avec des objets de même type, comme par exemple :
T a;
T b = a;
où T est une classe. Dans ce cas, le compilateur fera une copie simple des
valeurs des membres données de a dans ceux de b.
• Notez que, l'utilisation d'une classe sans constructeur et comportant un
pointeur est fortement déconseillée, du fait qu'aucune initialisation implicite ne
sera parfaite et que lors de l'initialisation d'un objet par un autre, les deux
objets concernés partageront la même zone mémoire pointée par le pointeur
Classes & objets
Cas d'une classe avec constructeur
• Pour une telle classe, toute déclaration doit comporter une initialisation.
Ainsi, pour déclarer par exemple un objet u d'une classe T, on doit utiliser
l'une des syntaxes suivantes :
syntaxe 1: T u=v;
syntaxe 2: T u(liste_des_valeur);
syntaxe 3: T u = T(liste_des_valeur);
Dans syntaxe 1, v est un objet de la classe T (défini antérieurement).
Les syntaxes 2 et 3 sont équivalentes. Dans ces deux formes, liste_des_valeur
est une liste de valeurs qui correspond à la liste des arguments du
constructeur, en tenant compte des valeurs par défaut définies par le
constructeur et des surcharges éventuelles du constructeur (autrement dit,
T(liste_des_valeurs) doit être un appel valable de la fonction membre T)
Classes & objets

Exemple:
class T {
int i;char c;
public:
T(int n, char cc = 'a' ){i=n; c=cc;}
……
};
T a(5,'A'); // ou T a=T(5,'A')
T b = T(3); // ou T b(3) et équivalent à T b(3,'a')
T c = a;
Classes & objets
Cas d'une classe avec constructeur (suite)

Par ailleurs, notons les cas particuliers suivants:
♦ Si tous les arguments du constructeur ont une valeur par défaut
T u(); et T u; seront équivalentes.
♦ Si la classe ne comporte qu'un membre donnée du type de base
T u(valeur); et T u = valeur; seront équivalentes. Dans la deuxième il y aura une
conversion implicite du type de valeur vers T.
Exemple:
class T{

float z;

public:
T(float x =0.0){z=x;}
……
};
//La déclaration
T u = 1.2; //conversion implicite float->T
T u(1.2); // équivalente à la première
Classes & objets
Constructeur par recopie (copy constructor)
Considérons une classe T, dont l'un des membres données est un pointeur
nommé adr. Cette classe doit alors comporter un constructeur qui alloue
dynamiquement une zone mémoire à ce pointeur pour l'initialiser et un
destructeur qui libère cette zone mémoire lors de destruction des objets. Dans
ce cas, si
T u = v; // où v est un objet de la classe T
Il y aura une copie simple des valeurs des membres données de v dans ceux de u.
Par suite, les pointeurs u.adr et v.adr désigneront la même adresse, ce qui
posera deux problèmes :
♦ Toute modification contenu de *adr de l'un des objets se fera aussi pour l'autre.
♦ La destruction de l'un des objets, entraînera la destruction du pointeur du
deuxième.
Classes & objets
Exemple
#include <iostream.h> // --------------- Définition des fonctions membres
//------------ Déclaration de la classe T::T(int n, int p)
{
class T{
i = n;
int i; pi = new int;
int *pi; *pi = p;
}
public: T::~T()
T( int = 0, int = 0); {
if(pi != NULL)
~T(); delete pi;
void affiche(); }
void T::affiche()
void modifier(int,int); {
}; cout << "(" << i << "," << *pi << ") --> " << pi <<
endl;
}
void T::modifier(int n, int p)
{
i = n ; *pi = p;
}
Classes & objets

Exemple (suite)
// ----------------------------------------- Test
void main()
{
T u;
cout << "u : ";u.affiche();
// initialisation d'un objet avec un autre
T v = u;
cout << "v : ";v.affiche();
// on modifie v
cout << "\n------------Modification" << endl;
v.modifier(2,2);
cout << "v : ";v.affiche();
cout << "u : ";u.affiche();
}
Classes & objets
Constructeur par recopie (suite)
• Pour résoudre ce type de problèmes, C++ offre la possibilité de définir un constructeur
particulier approprié à ce genre de situation. Ce constructeur est appelé constructeur par
recopie (copy constructor) et il est déclaré comme suit:
T ( T &);
• Le code du constructeur par recopie doit contenir les instructions nécessaires pour créer un
nouvel objet à partir de l'objet passé en paramètre.
Exemple:
On ajoute à la classe T de l'exemple précédent , un constructeur par recopie définie comme
suit :
T::T( T & v)
{
i = v.i;
pi = new int;
*pi = *(v.pi);
}
et on refait le même test que dans l'exemple précédent, pour montrer que les problèmes posés
par l'initialisation d'un objet par un autre sont bien résolus.
Classes & objets
Tableau d'objets
• En théorie, un tableau peut posséder des éléments de n'importe quel
type. Ainsi nous pouvons déclarer un tableau de N objets d'une classe T
par :
T tab[N];
Or, du fait qu'on ne peu déclarer un objet sans l'initialiser, cette déclaration ne
sera possible que si la classe T admet un constructeur sans arguments (ou un
constructeur dont tous les arguments ont des valeurs par défaut).
• Ces mêmes remarques s'appliquent pour les tableaux dynamiques d'objets.
Une déclaration de type :
T * adr = new T[N];
nécessite aussi un constructeur sans arguments.
Classes & objets
Objet d'objets
Une classe peut comporter un membre donnée de type classe. Considérons alors la
situation suivante, où la classe T comporte un membre donnée de type classe A :
class A{
……
public:
A(liste_arguments_a);
……
};
class T{
A a;
……
public:
T(liste_arguments);
……
};
Classes & objets

Objet d'objets (suite)


Lors de la création d'un objet de type T, il y aura appel du constructeur de T puis un appel du
constructeur de A, car la création d'un objet nécessite la définition de tous les membres
données de l'objet (en particulier le membre a de type A).
Par suite, la création d'un objet de type T ne sera possible que si A possède un constructeur sans
arguments.
Pour gérer ce genre de situation, C++ nous offre la possibilité de mentionner, dans la
définition du constructeur de T, la liste des arguments à fournir au constructeur de A
et ce de la manière suivante :
T::T (liste_arguments) : a (liste_arguments_a)
{……}
où liste_arguments_a est une sous liste de liste_arguments, valable pour le
constructeur de A.
Classes & objets

Fonctions amies
Si une fonction Fct est “amie” (friend) d’une classe C1, alors Fct peut accéder
aux champs privés de C1.
Si une classe C2 est “amie” de C1, toutes les fonctions membres de C2
peuvent accéder aux champs privés de C1.
Ces déclarations se font dans la définition de C1 :
c l a s s C1 {
...
f r i e n d type-ret Fct( param−de−F ) ;
f r i e n d c l a s s C2 ;
...
};
Classes & objets

'Friend' en résumé
 Les amis d’une classe sont des classes ou des fonctions
 Les amis d’une classe peuvent accéder à toutes les méthodes et
données membre de la classe quel que soit le niveau de protection
 Les amis d’une classe sont définis à l’intérieur de la classe
 Violation parfois utile, mais très souvent déconseillée du principe
d'encapsulation
Classes & objets

La surcharge des opérateurs


En C++, les opérateurs définis pour les types de base sont traités par le
compilateur comme des fonctions, ce qui permet donc, avec la technique de
la surcharge, d'utiliser la plus part d'entre eux avec les types classe
Les opérateurs redéfinissables sont :

Les opérateurs qui ne peuvent être surchargés .


. .* :: ?: # ##
Classes & objets

• Pour surcharger un opérateur X, on définit la fonction operatorX (par


exemple operator+ pour l'addition).

• L'implémentation de la surcharge d'un opérateur peut se faire soit comme une


fonction classique (amie d'une classe), soit comme une fonction membre
d'une classe.

• La surcharge d'un opérateur est appelée implicitement par le compilateur


chaque fois que l'opérateur est rencontré dans le code, mais elle peut être
aussi appelée explicitement.
Classes & objets

Surcharge d'opérateurs : SYNTAXE DE DECLARATION


Syntaxe générale :
type_de_retour operator op(liste arg);

Le symbole op qui suit le mot clé operator doit obligatoirement être un


opérateur déjà défini pour les types de base

Exemple :
class Point {int x,y; …};
Prototype de la fonction amie operator + :
Point operator +(Point,Point) ;

Prototype de la fonction membre operator + :


Point operator +(Point) ;
Classes & objets

Surcharge d'opérateurs (suite)


 La surcharge d’opérateurs doit conserver la pluralité (unaire,binaire) de
l’opérateur initial
 Opérateurs unaires : + - ++ -- ~ * & new delete (cast), etc ...
 Opérateurs binaires:
* / % + - << >> < <= > >= || && | & [], etc …
 L’opérateur . ne peut pas être surchargé
 Les opérateurs =,[], (), ->, new et delete doivent être redéfinis comme fonctions
membre d'une classe (et non comme fonctions amies)
Classes & objets

Surcharge d'opérateurs: Fonction amie


 Les arguments de la fonction correspondent aux opérandes de l’opérateur

 Au moins un des arguments est de type classe

 Exemple :
class Point {int x,y; …};
Prototype de la fonction amie operator +:
Point operator +(Point,Point) ;
 déclarer l’amitié dans la classe Point
 définir la fonction amie
Classes & objets

Surcharge d'opérateurs (suite)


Exemple:
#include <iostream.h>
main()
class Point {
{ int x,y ; Point a(1,2), b(2,5) ;
public : Point c ;
c=a+b;
Point (int abs = 0 , int ord = 0)
}
{x=abs; y=ord; }
friend Point operator+ (Point,Point) ; Remarques :
}; expression a+b interprétée par le compilateur
comme l’appel : operator+ (a,b)
on pourrait écrire :
Point operator + (Point a, Point b) c = operator +(a,b);
{ Point p ;
p.x = a.x + b.x ; p.y = a.y + b.y ;
return p ;
}
Classes & objets

Surcharge d'opérateurs: Fonction membre


 La première opérande de l’opérateur transmise implicitement :
l’objet auquel on applique la fonction membre
 Si l’opérateur est binaire, pas de contrainte de type imposé sur l’argument
explicite
 Exemple :
class Point {int x,y; …};
Prototype de la fonction membre operator + :
Point operator +(Point) ;
Classes & objets

Surcharge d'opérateurs (suite)


Exemple:
#include <iostream.h>
class Point
main()
{
{ x,y ;
Point a(1,2), b(2,5) ;
public : Point c ;
Point (int abs = 0 , int ord = 0) c=a+b;
{x=abs; y=ord; } }
Point operator+ (Point) ;
}; Remarques :
expression a+b interprétée par le compilateur
Point Point :: operator + (Point b)
comme l’appel : a.operator+ (b)
on pourrait écrire :
{ Point p ;
c = a.operator +(b);
p.x = x + b.x ; p.y = y + b.y ;
return p ;
}
Classes & objets

Surcharge d'opérateurs (suite): Passage par référence


 La transmission des arguments peut se faire par référence (préférable
pour objets de grande taille)
 Prototype de la fonction amie :
Point operator +(Point & a, Point & b) ;
ou
Point operator +(const Point & a, const Point & b) ;
 Prototype de la fonction membre :
Point operator +( Point & b) ;
ou
Point operator +(const Point & b) ;
Classes & objets
Surcharge d'opérateurs: opérateur d'affectation
Exemple :
Point a(1,2), b(3,4) ;
...
b=a;
 Si non explicite, opérateur d’affectation par défaut : recopie des
valeurs de la seconde opérande dans la première
 Doit être déclaré comme une fonction membre en C++

 Différences entre l'implémentation du constructeur par recopie et celle


de la surcharge de l'opérateur =:
 affectation d'un objet à lui-même possible
 avant affectation : 2 objets déjà construits
Classes & objets

Forme canonique d'une classe


class A
{
public:
A(…) ; // constructeur
A(const A &) ; // constructeur de recopie
~A(…) ; // destructeur
A & operator = (const A &) ; // opérateur d’affectation
};
Classes & objets
Surcharge d'opérateurs: opérateur de conversion

Si un opérateur de conversion est défini, on peut affecter l’objet dans
une variable du type correspondant

Doit être déclaré comme une fonction membre en C++

Pas de type de retour spécifié :
type = nom de l’opérateur

Pas de paramètre

Retour de l’objet converti
Classes & objets
Surcharge d'opérateurs: opérateur de conversion
Définition :
class Point
{
public:
operator double(); // opérateur unaire
private:
int x,y ;
};
Implémentation :
Point::operator double()
{
return sqrt(x*x+y*y) ;
}
Utilisation :
main()
{
Point pt(10,3) ;
double f ;
f = (double)pt ; // ou double (pt) ;
}
HERITAGE
A l'origine
Impératif de réutiliser les portions de code déjà écrites dans les classes
utilisées pour des projets antérieurs (comme cela se fait dans les
modules en C).
Pourquoi?
recopier est peu valorisant
recopier est coûteux en développement (écriture, mise au point, tests)
recopier est coûteux en maintenance (multiplication des erreurs)
HERITAGE

L'idée

Organiser ses propres classes en réutilisant les classes existantes (les
siennes ou celles des autres (bibliothèques,…))

Une classe dérivée hérite de (récupère) toutes les données membre et
toutes les fonctions membre de la classe de base
HERITAGE

 L'idée (suite)

Une classe dérivée peut

accéder aux données membre et fonctions membre de sa classe de base selon
certaines règles
(accès si public ou protected)

ajouter des données membre et fonctions membre à sa classe de base

redéfinir* certaines fonctions membre de sa classe de base
(*) définir des fonctions aux en-têtes identiques, à savoir noms identiques et
paramètres identiques. Différent de la surdéfinition ou surcharge!
HERITAGE

Le Principe
L'héritage simple structure un ensemble de classes en une hiérarchie.
Au sommet, on trouve la classe correspondant au concept le plus
général.
Du sommet vers la base, on spécialise en cas de plus en plus
particuliers.
La relation d'héritage:
est_un possède les caractéristiques de
HERITAGE

Le principe (suite)

Véhicule

Véhicule

Avion Voiture Bateau


Avion Voiture Bateau

Jeep Voilier
Jeep Voilier

Hydravion
HERITAGE
Résumé

Permet de définir une classe qui enrichit ou spécialise une classe existante

On peut dériver de une ou plusieurs classes (héritage multiple très
dangereux)

La classe dérivée peut accéder aux membres publics et protégés de la classe
de base

La classe dérivée ne peut pas accéder aux membres privés de la classe de
base
HERITAGE
Syntaxe

Héritage simple :

class MaClass : public MaClass_de_base


{
...
};


Héritage multiple :

class MaClass : public MaClasse_de_Base , public AutreClasse_de_deBase


{
...
};
HERITAGE
Construction
Quand un objet est construit, tous les constructeurs sont appelés
Les constructeurs des classes de base sont appelés AVANT les
constructeurs des classes dérivées
Exemple : Si A dérive de B qui dérive de C :
Le constructeur de C est appelé
Le constructeur de B est appelé
Le constructeur de A est appelé
HERITAGE

Exemple
class D
{public :
D(int,int) ;
};
class E : public D // E dérivée de D
{public :
E(int,int,int) ;
};
// initialisation de la classe de base
E::E(int x,int y,int z) : D(x,y)
{
}
HERITAGE

 Constructeurs

CAS 1 : Cas où le constructeur de la classe dérivée est synthétisé par le compilateur
(sans paramètres) : ce constructeur appelle le constructeur par défaut de la classe de
base (appel sans argument du constructeur synthétisé ou d'un constructeur défini sans
paramètres ou dont tous les paramètres ont une valeur par défaut)

class A class A
class A { {
{ private: private:
private: public: public:
public: A(); A(T1 a1, T2
}; }; a2);
};
class B : public A class B : public A class B : public A
{ { {
private: private: private:
public: public: ...}; public:
}; OK : le constructeur synthétisé de B };
appelle le constructeur par défaut A NON : le constructeur synthétisé de B ne trouve
OK : le constructeur synthétisé de B appelle défini sans paramètres pas de constructeur par défaut (défini sans
le constructeur par défaut A (ici synthétisé) paramètres ou avec paramètres par défaut)
dans A
HERITAGE

Constructeurs (suite)

CAS 2 : Un constructeur explicitement défini de la classe
dérivée appelle le constructeur de la classe de base en
accord avec la liste d'initialisation dans l'en-tête de sa
définition. Si la liste ne mentionne pas de constructeur, c'est
le constructeur par défaut qui est appelé (sans paramètres).
HERITAGE

Appels Constructeurs
class A
{
private:
public:
A(T1 a1, T2 a2);
};

class B : public A // B dérivée de A


{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};

// définition des méthodes


B::B(T1 b1, T2 b2, T3 b3) : A(b1,b2)
{
}
OK : le constructeur de B appelle le constructeur de A précisé dans la liste d'initialisation.
HERITAGE
Appels Constructeurs
class A
{
private:
public:
A();
A(T1 a1, T2 a2);
};
class B : public A
{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};
// définition des méthodes
B::B(T1 b1, T2 b2, T3 b3)
{
}

OK : le constructeur de B appelle le constructeur par défaut (sans paramètres et non


synthétisé ici) de A
HERITAGE

Appels Constructeurs
class A
{
private:
public:
};

class B : public A
{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};

// définition des méthodes


B::B(T1 b1, T2 b2, T3 b3)
{
}

OK : le constructeur de B appelle le constructeur par défaut


(synthétisé) de A .
HERITAGE
Appels Constructeurs
class A
{
private:
public:
A(T1 a1, T2 a2);
};
class B : public A
{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};
// définition des méthodes
B::B(T1 b1, T2 b2, T3 b3)
{
}

NON : le constructeur de B ne trouve pas le constructeur par défaut de A . Pas de


constructeur synthétisé ici.
HERITAGE
Destruction

Quand un objet est détruit, son destructeur et celui de toutes les classes de
base sont appelés

Le destructeur de la classe dérivée est appelé en premier

Exemple : Si A dérive de B qui dérive de C :

Le destructeur de A est appelé

Le destructeur de B est appelé

Le destructeur de C est appelé
HERITAGE

Accès aux membres


Statut des membres de la classe dérivée en fonction du statut des
membres de la classe de base et du mode de dérivation.

- les attributs privés, mais accessibles par l’instance


- les attributs inaccessibles : ils occupent de la place mémoire, mais personne ne peut plus
y accéder, y compris l’instance elle-même.
HERITAGE
Héritage privé

But : masquage du legs de la classe de base reçu par la classe dérivée =
technique de fermeture des accès à la classe de base

Syntaxe :
class MaClass : private MaClasse_de_Base
{
...
};

1) Un utilisateur de la classe dérivée MaClass ne pourra pas accéder aux membres


publics de la classe de base

2) Les fonctions membres de la classe dérivée peuvent accéder aux fonctions et


membres publics et protégés de la classe de base
HERITAGE
Héritage privé (suite)
Quand l’utiliser ?
Toutes les fonctions utiles de la classe de base sont redéfinies dans
la classe dérivée et on ne veut pas laisser l’utilisateur du
programme accéder aux fonctions de la classe de base
(exemple : Pile dérive de Tableau)
HERITAGE

 Héritage privé (suite)


 Exemple: Ce type d'héritage s'utilise lorsque la classe dérivée n'est pas un cas particulier
de la classe de base (bien que sa définition s'appuie sur celle de la classe de
base).
class Table class Pile : private Table
{ {
int nb; double *sommet;
double *tab; public:
public: Pile();
Table(); Pile(int taille);
Table(int taille); void empiler(double x);
Table(const Table&); double depile();
~Table(); bool isEmpty();
Table& operator=(const Table&); };
double& operator[](int);
};

L'implémentation de Pile s'appuie sur celle de Table, mais il est nécessaire de cacher à
l'utilisateur la possibilité d'accéder à n'importe quel élément de la pile par l'opérateur []; pour
cette raison on choisit l’héritage privé.
HERITAGE
Rétablissement des droits d'accès
Il est possible de rétablir les droits d'accès modifiés par la dérivation pour
rétablir les droits d'origine de ces attributs ou méthodes.
Le rétablissement ne concerne que les membres déclarés public ou
protected dans la classe de base.
class A
{
public:
int a1;
int a2; class B : private A int main()
{ {
}; public: B b;
using A::a1; b.a1 = 1; // OK
}; b.a2 = 1; // Illégal
return 0;
};
HERITAGE

Constructeur par copie


Rappel : le compilateur génère un constructeur par copie trivial si le
concepteur n'en écrit pas.
Le constructeur par copie généré pour une classe dérivée appelle
implicitement les constructeurs par copie des classes de base. Ce n'est pas le
cas si on écrit soi-même le constructeur par copie.
HERITAGE

Constructeur par copie


HERITAGE

Constructeur par copie


La meilleure solution consiste là encore à utiliser les listes
d'initialisation des constructeurs :
HERITAGE
Opérateurs
Tous les opérateurs sont hérités
Attention à l’opérateur = (affectation) :
surcharge de l’opérateur = dans la classe dérivée : il faut appeler celui de l
classe de base (pas d’appel implicite comme dans le cas du constructeur)
HERITAGE

Opérateurs (suite)
Si opérateur = non surchargé dans la classe dérivée:


affectation de la partie héritée de la classe de base selon la
surcharge de l'opérateur = dans la classe de base

affectation membre à membre (par défaut) pour les
membres propres à la classe dérivée
HERITAGE

Redéfinition des attributs


Il est possible de redéfinir un attribut dans la classe dérivée. L'attribut
redéfini masque celui issu de la classe de base. L'opérateur de résolution
de portée (::) permet cependant d'accéder à l'attribut masqué :

class A int x;
{ void B::f() Trois variables de
public: { même nom !!!
int x; x++; // incrémente B::x
}; A::x++; // incrémente A::x
class B : public A ::x++; // incrémente x global
{ };
public:
int x;
void f();
};
HERITAGE

Redéfinition des méthodes


De la même façon, une méthode masque toutes les méthodes de
même nom définies dans la classe de base.

class A int main()


{ {
public: B b;
void f(int); b.f(1); // Erreur : 'f' fonction ne prend pas de paramètre
void f(); b.A::f(1); // OK
}; b.f(); // OK, appelle B::f()
class B : public A return 0;
{ };
public:
void f();
};
HERITAGE

Conversions d’objets
Par un héritage public, une classe B dérivée de A est considérée comme une
"sorte" de A. Quel que soit l'objet a de type A et l'objet b de type B dérivé
publiquement de A, tous les services offerts par a sont aussi offerts par b
=> donc b peut remplacer a
Pour des classes en relation de dérivation publique, le compilateur effectue
certaines conversions implicites :
• objet de la classe dérivée => objet de la classe de base ;
• référence sur un objet de la classe dérivée => référence sur objet
classe de base ;
• pointeur sur un objet de la classe dérivée => pointeur sur objet classe de
base.
Cette conversion implicite n'existe pas si l'héritage est privé ou protégé.
HERITAGE

Conversions d’objets (suite)


On réalise les appels :
X xob;
Soient les classes: Y yob;
class X {}; P pob;
class Y : public X {};
fx1(yob); // Classe dérivée => base
class P : private X {};
fx2(&yob); // Idem

fy1(xob); // Erreur, pas dans ce sens


fy2(&xob); // Erreur, pas dans ce sens

fx1(pob); // Erreur, héritage privé


fx2(&pob); // Erreur, héritage privé

xob = yob; // Classe dérivée => base


Et soient les fonctions yob = xob; // Erreur, pas dans ce sens
suivantes: xob = pob; // Erreur, héritage privé
void fx1(X x);
void fx2(X* x); D’après les héritages choisis :
void fy1(Y y); • « Tous les Y sont des X », mais « Tous les X ne sont pas des Y »
void fy2(Y* y); • « Les P ne sont pas des X »
Le compilateur se charge de réaliser automatiquement les
conversions autorisées (ici seulement de la classe Y vers la classe X).
HERITAGE
 Conversions d’objets (suite)

Pour un pointeur ou une référence, il est possible de distinguer :


• le type statique du pointeur/référence : il peut être déterminé à la compilation,
par une analyse statique du code source ;
• le type dynamique : il est déterminé par la valeur courante, et peut changer en
cours d'exécution.
HERITAGE

 Conversions d’objets (suite)


 Grâce à ces conversions implicites de pointeurs et de références, tout objet
peut être traité comme un objet plus général. Cela permet de traiter
 collectivement un ensemble d'objets de types différents :
POLYMORPHISME

L'idée

Le polymorphisme :
dans le prolongement de la surdéfinition et de la redéfinition.

Surdéfinition ou surcharge (« overloading »): les fonctions ont le même nom, mais
le compilateur les distingue par la liste de paramètres

Redéfinition (« overriding »): les fonctions membre ont exactement le même en-tête
(nom et paramètres identiques), mais le compilateur les distingue selon le type déclaré
de l'objet auquel on les applique

Polymorphisme: les fonctions membre ont toujours le même en-tête, mais le compilateur
génère, pour celles qui sont déclarées virtuelles , des instructions supplémentaires qui
permettent de les distinguer lors de l'exécution selon le type effectif de l'objet auquel on
les applique, tout ceci dans les limites d'une même hiérarchie d'héritage
METHODE VIRTUELLE


Lorsqu’une méthode est virtuelle dans une classe de base, elle est
toujours virtuelle dans toutes les classes dérivées

Placer virtual avant le type de retour de la fonction

On répète virtual dans les classes dérivées pour la clarté (mais ce
n’est pas obligatoire)
METHODE VIRTUELLE (suite)

Remarque importante : Si p est déclaré comme un pointeur vers
une classe de base, p peut contenir un pointeur vers n'importe
quelle classe dérivée de cette classe de base.
La conversion de PCDERIVEE -> PCBASE est implicite.
Il est sous-entendu ici que la dérivation est publique
METHODE VIRTUELLE (suite)


L'appel d’une méthode virtuelle n'est plus traduit par un
branchement à une adresse connue d'avance, mais par une série
d'instructions capable de déterminer lorsque le programme s'exécute
à quelle adresse il convient de se brancher.

Lorsqu’une méthode est virtuelle, c’est l’implémentation de la
classe de l’objet auquel elle est appliquée qui est appelée
METHODE VIRTUELLE(suite)

 SYNTAXE DE DECLARATION
 class A
{
 public:
 A() {} ;
 virtual bool IsA() const ;
 };

 class B : public A
 {
 public:
 B() {} ;
 virtual bool IsA() const ;
 // virtual est optionnel dans la classe dérivée
 };
METHODE VIRTUELLE

 SYNTAXE D’IMPLEMENTATION
 bool A::IsA() const
{
 return TRUE ;
}

 bool B::IsA() const


{
 return FALSE ;
}

c.-à-d. rien de spécial …


METHODE VIRTUELLE:
 UTILISATION
 B dérive de A :

 A* pA = new A ;
 A* pB = new B ;
 if (pA->IsA())
 cout << " Objet de classe A ";
 if (pB->IsA())
 cout << " Objet de classe B";
 delete pA ;
 delete pB ; // ERREUR !!!!
 // Destructeur de B non appelé

 Rappel :
En POO, on considère qu’un objet d’une classe dérivée peut « remplacer » un objet de
la classe de base
Méthodes virtuelles

Remarque:Toujours déclarer virtuel le destructeur d’une classe de


base destinée à être dérivée pour s’assurer d' une libération complète
de la mémoire.

 Pas d’obligation de redéfinir une méthode virtuelle dans les classes


dérivées
 Possibilité de redéfinir une méthode virtuelle d’une classe de base,
par une méthode virtuelle ou non virtuelle dans une classe Dérivée

Attention!! Nécessité de respecter le prototype de la méthode


virtuelle redéfinie dans une classe dérivée (même arguments et
même type retour)
METHODE VIRTUELLE PURE


Une méthode virtuelle pure est une méthode virtuelle déclarée dans
une classe de base, mais n’ayant pas d’implémentation dans cette
classe

Les autres méthodes de la classe peuvent appeler cette méthode
METHODE VIRTUELLE PURE

 SYNTAXE DE DECLARATION

Ajouter = 0 à la fin de la déclaration de la méthode

class X
{
// Définition d’une fonction virtuelle pure
// =0 signifie qu’elle n’a pas de définition
// Attention, c’est différent d’un corps vide : { }
virtual bool IsA() const = 0 ;
};
CLASSE ABSTRAITE

Une classe abstraite est une classe qui contient au moins une méthode
virtuelle pure

Les classes dérivées d’une classe abstraite sont abstraites tant que
toutes les méthodes virtuelles ne sont pas implémentées (virtuelles
pures)

On ne peut pas créer un objet d’une classe abstraite

Mais possibilité de définir des pointeurs et des références sur une
classe abstraite

Quand une classe est abstraite, il est obligatoire d’en dériver et
d’implémenter toutes les méthodes virtuelles pures .
Les entrées-sorties
 Introduction
 Déjà rencontré :
int n ;
cin >> n ; // flot d'entrée cin
cout << n ; // flot de sortie cout
cerr << ''message erreur''; //flot de sortie des erreurs
 Flot = « canal » :
 recevant de l’information, si flot de sortie
 fournissant de l’information si flot d’entrée
 Rôle des opérateurs << et >> :
 transfert de l’information
 formatage (éventuel)
 cout = flot prédéfini connecté à la sortie standard (écran)
(en C : fichier prédéfini stdout)
 cin = flot prédéfini connecté à l’entrée standard (clavier)
(en C : fichier prédéfini stdin)
Les entrées-sorties

 Hiérarchie des classes IO


io s
io s
is tre a m o s tre a m

is tre a m o s tre a m
io s tre a m

ifs tre a m o fs tre a m

fs tre a m
Les entrées-sorties

 Classe OSTREAM
 cout est un objet de la classe ostream

 #include <iostream.h>
ou
#include <iostream>
using namespace std ;
 opérateur << surchargé pour les types de base

ostream & operator << (type_de_base);

exemple: cout << " valeur : " << n ;


Les entrées-sorties
 Classe OSTREAM: Fonctions membres principales
 ostream & put(char) ;

char c = 'a' ;
cout.put(c) ; // affiche le caractère c

 ostream & write(const char *,int) ;

char ptr[ ] = "bonjour" ;


cout.write(ptr,5) ;
// affiche 5 caractères à partir de l'adresse ptr
Les entrées-sorties

 Classe ISTREAM
 cin est un objet de la classe istream

 #include <iostream.h>

 opérateur >> surchargé pour les types de base

istream & operator >> (type_de_base &) ;

exemple: cin >> n >> x ;


Les entrées-sorties
 Classe ISTREAM: Fonctions membres principales
 int gcount() ;
// fournit le nombre de caractères lus par getline()

char ch[20] ;
int len ;
while (cin.getline(ch,20))
{ len = cin.gcount() ;
…}
 istream & read(char * t,int x);
// lecture de x bytes

char t[5] ;
cin.read(t,5) ;
Les entrées-sorties
 Connexion d'un flot à un fichier
 #include <iostream.h>
#include <fstream.h>
 Ecriture dans un fichier :
créer un objet de la classe ofstream (dérive de ostream)
ofstream fic_out ("fic",ios::out) ;

nom de fichier mode d'ouverture

 Lecture d'un fichier :


créer un objet de la classe ifstream (dérive de istream)
ifstream fic_in ("fic",ios::in) ;

nom de fichier mode d'ouverture


Les entrées-sorties

 Connexion d'un flot à un fichier (suite)


 Lecture et écriture dans un fichier :
créer un objet de la classe fstream (dérive de iostream, qui dérive de
istream et ostream)

fstream fic ("fic",ios::in|ios::out) ;

 Ouverture d’un fichier : par le constructeur de la classe ou par la méthode


open des classes ifstream , ofstream et fstream
void open (const char * filename, openmode mode = in | out);
 Fermeture du fichier :

fic.close() ;
Les entrées-sorties

 Exemple
 L’ouverture d’un fichier est réalisé à l’aide de la méthode « open »
 commune aux 3 classes,
 #include <fstream.h>
 int main( void )
{
 ifstream f1; // instanciation de l’objet
 f1.open("essai1.tmp"); // ouverture du flux de données
 ofstream f2("essai2.tmp"); // combinaison des opérations
 // ... ... ... ... ...
}
Les entrées-sorties

 Exemple complet
 But lire et écrire 1, 2, 3 dans un fichier!
 #include <fstream>
 #include <fstream>
 #include <fstream> main()
 void ecrire(char * nom_fic) { {
 ofstream osf(nom_fic, ios::out);
Ecrire(''toto.txt'');
Lire(''toto.txt'');
 osf<<1<<'' ''<<2<<'' ''<<3; }
 osf.close(); }
 void lire(char * nom_fic){
 int i; ifstream ifs(nom_fic);
 while(!ifs.eof()){ ifs>>i; // ifs lit et met le résultat dans la variable i
 If (!ifs.eof()) cout <<i<<'' ''<<endl;}
Les entrées-sorties
 Surcharge des opérateurs << et >> pour les types utilisateurs
 Opérateur prenant un flot en premier argument ,il doit être redéfinie en fonction amie
ostream & operator << (ostream &, expression_de_type_classe &)
istream & operator >> (istream &, expression_de_type_classe &)
 Valeur de retour obligatoirement égale à la référence du premier argument
class point
{ int x, y ;
public :
point (int abs=0, int ord=0){ x = abs ; y = ord ; }
friend ostream & operator << (ostream &, point &) ;
friend istream & operator >> (istream &, point &) ;
};
Les entrées-sorties
 // Redéfinition de l’opérateur << en fonction amie de la classe Point
 ostream & operator << (ostream & sortie, point & p)
 { sortie << "(" << p.x << "," << p.y << ")" ; return sortie ; }
 // Redéfinition de l’opérateur >> en fonction amie de la classe Point
 istream & operator >> (istream & entree, point & p) // Si la saisie a été correcte, on affecte à p
 { char c = '\0' ;
if (ok=true) { p.x = x ; p.y = y ; }
 int x, y ; bool ok = true ;
 entree >> c ; // saisie d’un caractère au clavier // Statut d’erreur du flot géré par
// un ensemble de bits d’un entier
 if (c != '(') ok = false ;
// clear (activation de bits d’erreur)
 else { entree >> x >> c ; // saisie de x (entier) // badbit (bit activé quand flot dans un état irrécupérable)
 // et d’un autre caractère au clavier // rdstate () pur activer le bit badbit sans activer les autres
 if (c != ',') ok = false ; else entree.clear (ios::badbit | entree.rdstate () ) ;
 else { entree >> y >> c ; // même chose pour y
// on retourne le flux d'entrée
 if (c != ')' ) ok = false ; } return entree ;
}
 }
Les entrées-sorties

 int main()
 {
 point b
 cout << "donnez un point : " ;
 if (cin >> b)
 cout << "Affichage du point : " << b << endl ;
 else cout << "** information incorrecte" << endl ;
 }
Patrons (Templates)

 Patrons de fonctions
template <typename T> T min (T a, T b)
{
if (a < b) return a;
else return b ; Paramètre de type T
} (T = type quelconque)
typename T ou class T
Condition d’utilisation de la fonction min():
2 paramètres de même type (ex. 2 int, 2 float, …)

min (3,7) ;
min(5.6,9.8) ;

Le compilateur fabrique (instancie) automatiquement la


fonction min avec le type T utilisé : c’est une fonction patron
Patrons (Templates)

 Patrons de fonctions

Pas possible de créer un .o d’un patron de fonction

 le mettre dans un fichier .h
Patrons (Templates)

 Patron de fonction : application à un type classe


class Point
{
public :
Point(int abs = 0, int ord = 0)
{ x =abs ; y = ord; }

int operator < (Point b)


{
return x*x+y*y < b.x*b.x+b.y*b.y;
}
private :
int x,y ;
};

main()
{
Point p1(2,5), p2(1,8),p3 ;
p3 = min(p1,p2) ;
}
Patrons (Templates)

 Patron de fonction: paramètres de type



plusieurs paramètres de type:
template <typename T, typename U> T fct(T a, T* b, U c)


Paramètres peuvent intervenir:

dans l’en-tête de définition

dans des déclarations de variables locales

dans des instructions de type new, sizeof(), …
template < typename T, typename U> T fct (T x,U y,T z)
{…
Ta;
U *b ;
b = new U[10] ;
int n = sizeof(T) ;

a=x+y+z;
return a ; }
Patrons (Templates)

 Les patrons de classes



Écrire une définition de classe

Le compilateur l’adapte à différents types
template < typename T> class Point
{
public :
Point(T abs = 0, T ord = 0)
{ x =abs ; y = ord; } // définition en ligne
void affiche() ; // définition hors classe
Exemple d'utilisation:
private :
Point <int> p1(3,5) ;
Point <float> p2(3.5,6.7) ;
T x,y ;
};

template < typename T> void Point<T>::affiche()


{
cout << " coordonnees : "
<< x << " " << y << endl ;
}
Patrons (Templates)

 Patrons de classes: Où?



Pas possible de fournir à un utilisateur une classe patron toute
compilée
 la mettre dans un .h (y compris la définition des fonctions
membres non en ligne)
Patrons (Templates)

 Patron de classe : paramètres expression



Exemple : Paramètre de type Paramètre expression

template <typename T, int n> class Tableau


{
T tab[n] ;
};

Les valeurs effectives devront être des constantes:

Tableau <int,4> t1 ;
Tableau <float,100> t2 ;
La gestion des exceptions

 Le principe de la gestion des exceptions par C++ est le suivant :


 • quand une fonction veut signaler une erreur, elle lance (throw) un objet erreur ;
 • lorsque l'utilisateur veut gérer l'erreur, il attrape (catch) l'objet lancé ;
 • une erreur non attrapée conduit à un arrêt du programme.
 Cette technique est supérieure à celle du renvoi de codes d'erreurs :
 • elle n'accapare pas la valeur de retour des fonctions ;
 • elle ne fait pas intervenir de variables globales ;
 • elle ne nécessite pas de paramètres supplémentaires, l'objet erreur pouvant
 véhiculer des informations aussi complexes qu'on le souhaite ;
 • elle peut signaler une erreur dans un constructeur ou un destructeur (qui n'ont
 pas de valeur de retour).
La gestion des exceptions

 On lève une exception par l'instruction throw suivie d'une expression quelconque
(entier, double, chaîne de caractères, objet, ...).

void ouvrir(char* name)


{
FILE *f;
f = fopen(name, "r");
if(f==NULL)
Exception de type int
throw(1);
}

void ouvrir(char* name)


{
FILE *f;
f = fopen(name, "r");
if(f==NULL)
Exception de type const char* throw("Pb de fichier");
}
La gestion des exceptions

 Lorsqu'on lève un objet-exception, on peut véhiculer des informations complexes


(nom de la fonction, fichier source, numéro de ligne, messages, codes divers, ...) :

Fichier MonException.h
class MonException {
public:
char *msg1, *msg2;
MonException (char *s1, char *s2);
};

void ouvrir(char* name)


{
FILE *f;
f = fopen(name, "r");
if(f==NULL)
Exception de type MonException throw MonException("Probleme avec", name);
}
La gestion des exceptions

 Interception d'une exception


 Si on ne fait rien de plus que lever l'exception par throw, le programme se
terminera faute de gestion de l'erreur au niveau de l'appelant ( appel de la
fonction terminate() dont on peut modifier le fonctionnement en utilisant
set_terminate()).
 Pour de petits programmes ou en phase de mise au point, cela peut suffire.
 Si le programmeur veut gérer son exception, il doit mettre en place :
 • un bloc réceptif try : il sera en mesure d'attraper une exception lancée par
une des instructions contenues dans le bloc ;
 • des gestionnaires catch : un par type d'exception que l'on veut capturer
 (correspondant au type de l'exception levée par throw).
La gestion des exceptions
 Interception d'une exception
 Pour que l'exception soit capturée par le bloc try, il faut qu'il y ait un gestionnaire de type
compatible avec celui de l'exception lancée :

int main()
{
try
{
// Demander a l'utilisateur d'entrer
void ouvrir(char* name) // un nom de fichier
{ ouvrir(filename);
FILE *f; // Utiliser le fichier ouvert pour
f = fopen(name, "r"); // lire des données
}
throw(1); catch (int n)
} {
if (n == 1)
std::cerr << "PB de fichier\n";
}
La gestion des exceptions
 Interception des exceptions
 A chaque type qui peut être utilisé pour lancer une exception, il faut prévoir un gestionnaire
(catch) adapté (si on veut attraper l’exception, bien sûr) :
 void ouvrir(char* name)
 {
 FILE *f;
 f = fopen(name, "r");
 if(f==NULL) try {
 throw("Pb de fichier"); ouvrir("input.txt");
}
 } catch (const char *s) {
 void ouvrir(char* name) std::cerr << s;
}
 {
 FILE *f;
 f = fopen(name, "r");
 if(f==NULL)
 throw MonException("Probleme avec",name); try {
 } ouvrir("input.txt");
}
catch (MonException &e) {
std::cerr << e.msg1<< e.msg2;
}
La gestion des exceptions

 Interception des exception


 S'il n'y a pas de gestionnaire de type compatible associé au bloc try, l'exception n'est pas
traitée dans le bloc try, elle remonte de fonction appelante en fonction appelante jusqu'à
trouver un gestionnaire compatible :

int main()
{
try
{
lire_donnees();
...
void lire_donnees()
}
{
catch(const char *s)
try
{
{
std::cerr << s;
ouvrir("in.txt"); void ouvrir(char* name)
}
... {
return 0;
} ...
}
catch (int n) throw("Pb de fichier");
{ }
// fait quelque chose
}}
La gestion des exceptions

 Interception des exceptions


 La levée d'une exception s'apparente à un branchement (goto). Les
 instructions non exécutées :
 • dans la fonction qui a lancé l'exception,
 • dans les fonctions rencontrées lors de la remontée de la pile d'appels,
 • dans le bloc try qui a capturé l'exception ,
 ... sont abandonnées.
 Les variables locales à ces fonctions sont détruites (leurs destructeurs sont
appelés).
La gestion des exceptions
 Interception des exceptions
 Un bloc try peut être suivi de plusieurs gestionnaires catch. Dans ce cas,
 ces gestionnaires sont essayés séquentiellement suivant l'ordre de leur
 définition, et ce sera le premier compatible qui sera exécuté.
 int main() {
 try {
 ...
 }
 catch (const char *s) { std::cerr << s;}
 catch (int n) { if (n==1) std::cerr << "PB de fichier";}
 catch (MonException &e) { std::cerr << e.msg1 << e.msg2;}
 return 0;
 }
La gestion des exceptions

 Interception des exceptions


 Il existe un gestionnaire particulier, catch(...) qui attrape tout type d'exception. Ce
gestionnaire ne peut se trouver qu'en dernière position (sinon, les gestionnaires
placés derrière lui n'attraperaient jamais rien).
 int main() {
 try {
 ...
 }
 catch (const char *s) { /*...*/ }
 catch (int n) { /*...*/ }
 catch (MonException &e) { /*...*/ }
 catch (...) { std::cerr << "Exception inconnue"; }
 return 0;
 }
La gestion des exceptions

 Utiliser les exceptions pour corriger

// Soit une classe Table(Y,X),


// avec une méthode de calcul :
double Table::interpole(double x) Les exceptions servent à afficher des
{
// S'il y a sortie de table par messages d'erreur explicites :
// excès on lève une exception
// avec le nom de la table, les catch (TableError &e)
// valeurs limites et la valeur {
// qui dépasse : std::cerr<< "Sortie dans table : " << e.name<< "La
throw TableError(name, max_y, max_x, valeur " << e.x<< " dépasse la limite " << e.max_x;
x); }
}

Les exceptions ne sont pas seulement un bon moyen pour savoir quelle partie
du code a produit une erreur et pourquoi, elles permettent aussi de corriger
ces erreurs et de permettre au programme de continuer.
La gestion des exceptions

 Utiliser les exceptions pour corriger


 Voici un exemple de correction appliquée dans le cas d’une sortie de table
d’interpolation. On décide d’accorder 5% de tolérance, mais au delà, on lève à nouveau
une exception :

try
{
coeffAxial = tabCoeffAxial.interpole(machNumber);
}
catch (TableError &e)
{
if (e.x < (1.05*e.max_x))
coeffAxial = e.max_y;
else
{
std::cerr << "Sortie de table " << e.name
<< " au dela des tolerances " << std::endl;
throw(e);
}
}
La gestion des exceptions
 Spécifier l'exception
 Il est possible de spécifier les exceptions qu’une fonction ou une méthode a le droit
de lever :
 class X {};
 class A {
 void f1(); // n’importe quelle type exception
 void f2() throw(); // ne peut lever d’exception
 void f3() throw(int); // peut lever des exceptions de type ‘int’
 void f4() throw(int, double, X); // peut lever 3 types d’exceptions
 };
 void g1();
 void g2() throw();
 void g3() throw(int);
 void g4() throw(int, double, X);
Remarque:
Durant l’exécution, si une exception d’un type interdit est levée,
le programme appelle une méthode spéciale « unexpected() »
que le programmeur peut par ailleurs modifier et utiliser à
sa guise par set_unexpected().
La gestion des exceptions
 Fichier en-tête <stdexcept> bibliothèque standard fournissant des classes
d’exceptions
 Plusieurs exceptions standard susceptibles d’être déclenchées par une fonction ou
un opérateur de la bibliothèque standard
Ex. classe Bad_alloc en cas d’échec d’allocation mémoire par new
vect::vect (int n) // Constructeur de la classe vect
{ adr = new int [nelem = n] ;}
int main ()
{ try { vect v(-3) ; }
catch (bad_alloc) // Si le new s’est mal passé
{ cout << "exception création vect avec un mauvaise nombre d'éléments " << endl ;
exit (-1) ;
}}

exception création vect avec un mauvais


nombre d'éléments
 En cas d’exception non gérée ⇒ appel automatique à terminate()
La gestion des exceptions

 Classe exception ayant une méthode virtuelle what() affichant une chaîne de
caractères expliquant l’exception
 int main ()
 {
 try { vect v(-3) ;}
 catch (bad_alloc b)
 { // Appel de la méthode what() pour l’exception bad_alloc
 cout << b.what() << endl ; exit (-1) ; // affichage: St9bad_alloc
 }
 }
La gestion des exceptions

 class exception {
 public:
 exception () throw();
 exception (const exception&) throw();
 exception& operator= (const exception&) throw();
 virtual ~exception() throw();
 virtual const char* what() const throw();
 };
 Classes dérivées de exception :
 bad_alloc, bad_cast, bad_exception, bad_typeid …
La gestion des exceptions

 Possibilité de dériver ses propres classes de la classe exception


 class mon_exception : public exception
 { public :
 mon_exception (char * texte) { ad_texte = texte ; }
 const char * what() const throw() { return ad_texte ; }
 private :
 char ad_texte ;
 };
La gestion des exceptions
 int main()
 { try
 { cout << "bloc try 1" << endl;
 throw mon_exception ("premier type") ; //construction à la volée
 }
 catch (exception & e)
 { cout << "exception : " << e.what() << endl; }
 try
 { cout << "bloc try 2" << endl;
Résultat:
 throw mon_exception ("deuxieme type") ; bloc try 1
 } exception : premier type
bloc try 2
 catch (exception & e) exception : deuxieme type
 { cout << "exception : " << e.what() << endl;
 }
 }
La gestion des exceptions

 Exceptions et constructeurs
 • Il est parfaitement légal de lancer une exception dans un constructeur, ce
qui permettra de signaler une erreur lors de la construction d’un objet.
 • Lorsqu’une exception est lancée à partir d’un constructeur, la construction
de l’objet échoue. Par suite, le destructeur pour cet objet ne sera pas appelé,
ce qui pose certains problèmes si l'objet est partiellement initialisé.
La gestion des exceptions
 Exemple:
 L'exemple suivant montre comment traiter le cas des objets partiellement initialisés,
lorsque la création de ces objets échoue.
 Considérons une classe "etudiant" qui permet de manipuler les notes d'un étudiant.
 Les notes de chaque étudiant seront stockées dans des tableaux d'entiers. On suppose
que le nombre de notes est variable et qu'il ne doit pas dépasser une certaine valeur
MAXNBNOTE.
 On prévoit aussi dans cette classe une surcharge de l'opérateur [] qui permet l'accès
en lecture et en écriture à une note donnée
 Pour gérer les différentes erreurs, on définit une classe de base "Erreur" et deux
autres classes DepMaxNbNote et InvalideIndice pour gérer respectivement le
dépassement de nombre de notes autorisé et le dépassement d'indice.
La gestion des exceptions
 #include <iostream>
 #include <string>
 #define MAXNBNOTE 4
 #define MAXSIZE 255
class DepMaxNbNote:public erreur{
 using namespace std; public:

//DepMaxNbNote():erreur(){}
//------ classes exception
DepMaxNbNote(const char *s):erreur(s){}
 class erreur{ void affiche(){

erreur::affiche();
protected:
cout << "Nombre max de notes est " << MAXNBNOTE << endl;
 const char * raison; } };
 public:
 erreur():raison(0){}
 erreur(const char* s):raison(s){}
class InvalideIndice:public erreur{
public:
 virtual void affiche(){ InvalideIndice():erreur(){}
 if (raison == NULL)
InvalideIndice(const char *s):erreur(s){}
void affiche(int i){
 cout << "Erreur inconnu..." << endl; erreur::affiche();
 else cout << raison << endl; cout << "Indice doit etre entre 0 et " << i << endl;
}
 } }; };
La gestion des exceptions

 //--------- classe etudiant //------------ constructeur


 etudiant::etudiant(char* nom, int nbnotes)
class etudiant{
{
 char * _nom; // nom try
{
 int _nbnotes; // nombre de notes _nom = new char[strlen(nom)+1];
 int * tabnotes; // tableau de notes strcpy(_nom,nom);
if( (nbnotes < 0) || (nbnotes > MAXNBNOTE) ){
 public: DepMaxNbNote err("Erreur dans Constructeur");
 throw err;
etudiant(char*,int);
}
 ~etudiant(); _nbnotes = nbnotes;
tabnotes = new int[nbnotes];
 int GetNbNotes(){ return _nbnotes;} }
 int & operator[](int); catch(DepMaxNbNote & err)
{
 void affiche(); delete _nom;
err.affiche();
 }; exit(-1);
}
}
La STL (standard template library)

 La STL fournit entre autre:


 – des classes pour les structures de données classiques
 • structures séquentielles : vector, list, …
 • structures associative : (multi)set, (multi)map …
 – des itérateurs offrant une interface uniforme pour manipuler ces
structures
 – des algorithmes variés sur ces structures
 –…
 Un lien utile : http://www.sgi.com/tech/stl/
La STL (standard template library)

 Les containers
 Les containers sont des objets qui permettent de stocker d’autres objets. Ils sont
décrits par des classes génériques représentant les structures de données logiques les
plus couramment utilisées : les listes, les tableaux, les ensembles... Ces classes sont
dotées de méthodes permettant de créer, de copier, de détruire ces containers, d’y
insérer, de rechercher ou de supprimer des éléments. La gestion de la mémoire, c’est-
à-dire l’allocation et la libération de la mémoire, est contrôlée directement par les
containers, ce qui facilite leur utilisation.
La STL (standard template library)

 containers disponibles:
 – vector : container implantant les tableaux, qui autorise les accès directs sur ses éléments.
Les opérations de mise à jour (insertion, suppression) sont réalisées en un temps constant à la
fin du container, et en un temps linéaire (dépendant du nombre d’éléments) aux autres
endroits.
 – list : container implantant les listes doublement chaînées, dédié à la représentation
séquentielle de données. Les opérations de mise à jour sont effectuées en un temps constant à
n’importe quel endroit du container.
 – deque : container similaire au vector, effectuant de plus les opérations de mise à jour en
début de container en un temps constant.
 – set : container implantant les ensembles où les éléments ne peuvent être présents au plus
qu’en unseul exemplaire.
 – multiset : container implantant les ensembles où les éléments peuvent être présents en
plusieurs exemplaires.
 – map : container implantant des ensembles où un type de données appelé clé est associé aux
éléments à stocker. On ne peut associer qu’une seule valeur à une clé unique. On appelle aussi
ce type de container tableau associatif.
La STL (standard template library)

 – multimap : container similaire au map supportant l’association de


plusieurs valeurs à une clé unique.
 – stack : container implantant les piles, qui sont des listes spéciales, dites
LIFO.
 – queue : container implantant les files, qui sont des listes spéciales, dites
FIFO.
 Exemple
#include <iostream>
#include <vector>
#include <list>
// Nombre d’éléments des containers
int main() cout << "Il y a" << tableauEntiers.size()
{ << " éléments dans le tableau" << endl;
vector<int> tableauEntiers; // Crée un tableau d’entiers vide cout << "Il a y " << listeEntiers.size()
list<int> listeEntiers; // Crée une liste d’entiers vide << " éléments dans la liste" << endl; // Accès à des éléments
int unEntier; cout << "Premier élément du tableau : "
// Saisie des entiers << tableauEntiers.front() << endl;
cout << "Saisir le prochain entier (-1 pour finir) : "; cout << "Premier élément de la liste : "
cin >> unEntier; << listeEntiers.front() << endl;
while (unEntier != -1) { int milieu = tableauEntiers.size() / 2;
tableauEntiers.push_back(unEntier); cout << "Élément de milieu de tableau : "
listeEntiers.push_back(unEntier); << tableauEntiers[milieu] << endl;
cout << "Saisir le prochain entier (-1 pour finir) : ";
}
cin >> unEntier;}
Saisir le prochain entier (-1 pour finir) : 4
Saisir le prochain entier (-1 pour finir) : 5
Saisir le prochain entier (-1 pour finir) : 3
Saisir le prochain entier (-1 pour finir) : 7
Saisir le prochain entier (-1 pour finir) : 6
Saisir le prochain entier (-1 pour finir) : 3
Saisir le prochain entier (-1 pour finir) : -1
Il a y 6 éléments dans le tableau
Il a y 6 éléments dans la liste
Premier élément du tableau : 4
Premier élément de la liste : 4
Élément de milieu de tableau : 7
La STL (standard template library)

 Les Itérateurs ( Iterators)


 Les itérateurs sont une généralisation des pointeurs, ce qui permet au
programmeur de travailler avec des containers différents de façon
uniforme.
 Ils permettent de spécifier une position à l’intérieur d’un container,
 Peuvent être incrémentés ou déréférencés (à la manière des pointeurs
utilisés avec l’opérateur de déréférencement ’*’),
 Deux itérateurs peuvent être comparés.
 Tous les containers sont dotés d’une méthode begin qui renvoie un
itérateur sur le premier de leurs éléments, et d’une méthode end qui
renvoie un itérateur sur une place se trouvant juste après le dernier de leurs
éléments.
 On ne peut pas ainsi déréférencer l’itérateur renvoyé par la méthode end.
La STL (standard template library)

 Exemple
 #include <iostream>
 #include <list>
 int main()
 {
 list<int> lesEntiers;
 // Ici, des instructions pour initialiser la liste des entiers
 …
 // Affichage des éléments contenus dans la liste
 list<int>::iterator it;
 for (it = lesEntiers.begin(); it != lesEntiers.end(); it++)
 cout << *it << endl;
 }
La STL (standard template library)

 Iterator (suite)
 Un itérateur :
 pointe sur un élément d’un conteneur
 peut être incrémenté par ++
 peut être déréférencé par *
Il y a 3 types d'itérateurs :

 unidirectionnel
 bidirectionnel (--)
 accès direct (it + i, it[i])

Remarque:
 Itérateur de fin pointe juste après le dernier élément du conteneur
 Si la liste vide : li.begin() possède la même valeur que li.end()
La STL (standard template library)

 Les algorithmes
 Les algorithmes sont des fonctions C++ génériques qui permettent
d’effectuer des opérations sur les containers.
 Afin de pouvoir s’appliquer à plusieurs types de containers, les algorithmes
ne prennent pas de containers en arguments, mais des itérateurs qui
permettent de désigner une partie ou tout un container.
 De ce fait, il est même possible d’utiliser ces algorithmes sur des objets qui
ne sont pas des containers.
 Certains algorithmes ne nécessitent que des itérateurs de base (d’entrée ou
de sortie), et d’autres nécessitent des itérateurs plus évolués, comme la
fonction sort (effectuant un tri) qui nécessite un itérateur à accès direct.
La STL (standard template library)

 Exemple

#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
vector<int> tableauEntiers; // Crée un tableau d’entiers vide
int unEntier;
// Saisie des entiers

// Tri du tableau
sort(tableauEntiers.begin(), tableauEntiers.end());
// Affichage des éléments triés
vector<int>::iterator it;
for (it = tableauEntiers.begin(); it != tableauEntiers.end(); it++)
cout << *it ;
}

Vous aimerez peut-être aussi