Professional Documents
Culture Documents
Support de Cours de POO en C++ M ELWAFIQ Version 2017
Support de Cours de POO en C++ M ELWAFIQ Version 2017
Support de Cours de POO en C++ M ELWAFIQ Version 2017
Mohamed EL WAFIQ
Version 2017
Avant-propos
Ce cours a été développé et amélioré au fil des années afin de donner un support résumé
et simplifié aux étudiants ayant un bon niveau en programmation structurée en langage
de programmation C.
Structure du cours
Tous les programmes sources de ce cours sont compilés sous Dev-C++, Version 4.9.9.2,
en incluant les fichiers entête correspondants.
C++ dispose d’un certain nombre de spécificités qui ne sont pas obligatoirement
relatives à la programmation orientée objet.
Exemple
//ceci est un commentaire.
int a; // déclaration de a.
Exemple
main()
{
int a=7, b;
b=a;
float x, y;
x=2*a+y;
}
Exemple
for(int i=0; i<7; i++)
3. Notion de référence
Exemple
main()
{
void echange(int,int);
int a=2, b=5;
cout<<”Avant appel : ”<<a<<” - ”<<b<<” \n”;
echange(a,b);
cout<<”Après appel : ”<<a<<” - ”<<b<<” \n”;
}
//-------------------------------------------------------
void echange(int m,int n)
{
int z; z=m; m=n; n=z;
}
Exécution
main()
{
void echange(int &,int &);
int a=2, b=5;
cout<<″Avant appel : ″<<a<<″ - ″<<b<<″\n ″;
echange(a,b);
cout<<″Apres appel : ″<<a<<″ - ″<<b<<″\n ″;
}
//----------------------------------------------------------
void echange (int & x,int & y)
{
int z; z=x; x=y; y=z;
}
Exécution
NB :
- La fonction f est déclarée avec un deuxième argument initialisé par défaut par la
valeur 7.
- Pendant l’appel de la fonction f, si le deuxième argument n’est pas précisé, il est alors
égal à la valeur 7.
- Un appel du genre f() sera rejeté par le compilateur.
Exemple2
main()
{
void f(int=33,int=77);
f(99,55);
f(44);
f();
}
//-----------------------------------
void f(int a,int b)
{
cout<<″Valeur 1 : ″<<a<<″ - Valeur 2 : ″<<a<<″\n″;
}
Exécution
Remarque
Lorsqu’une déclaration prévoit des valeurs par défaut, les arguments concernés doivent
obligatoirement être les derniers de la liste.
Exemple
void f(int x)
{ cout<<″fonction numéro 1 : ″<<x<<″\n″; }
void f(double x)
{ cout<<″fonction numéro 2 : ″<<x<<″\n″; }
main()
{
int a=2; double b=5.7;
f(a); f(b); f(‘A’);
}
Exécution
Remarque
Le C++ a choisi donc la bonne fonction en fonction de ses arguments.
Cas 1
void f(int); //f1
void f(double); //f2
char c;
float b;
Cas 4
void f(int a=0 ; double c=0) ; // f1
void f(double y=0 ; int b=0) ; //f2
int m;
double z ;
Exemple
int *p;
p=new int;
ou
int *p=new int; // Allocation dynamique d’un entier.
int *p2=new int[5]; // Allocation dynamique de 5 entiers.
char *t;
t=new char[30];
ou
char *t=new char [30];
Remarque
new fournit comme résultat :
- Un pointeur sur l’emplacement correspondant, lorsque l’allocation réussie.
- Un pointeur NULL dans le cas contraire.
Exemple
inline double norme(double vec[3])
{
for(int i=0,double s=0; i<3; i++) s=s+vec[i]*vec[i];
return sqrt(s);
}
//---------------------------------------------------------
main()
{
double V1[3], V2[3];
for (int i=0; i<3; i++) { V1[i]=i; V2[i]=i*3; }
cout<<″Norme de V1 est : ″<<norme(v1);
cout<<″ - Norme de V2 est : ″<<norme(v2);
}
Commentaire
- La fonction norme a pour but de calculer la norme d’un vecteur passé comme
argument.
- La présence du mot inline demande au compilateur de traiter la fonction norme
d’une manière différente d’une fonction ordinaire, à chaque appel de norme, il devra
incorporer au sein du programme, les instructions correspondantes (en langage
machine). Le mécanisme habituel de gestion de l’appel est de retour n’existe plus, ce
qui réalise une économie de temps.
Remarque
Une fonction en ligne doit être définie dans le même fichier source que celui utilisé. Elle
ne peut être compilée séparément.
Une classe est la généralisation de la notion de type défini par l’utilisateur, dont lequel se
trouvent associées à la fois des données (données membres) et des méthodes (fonctions
membres).
En programmation orientée objet pure, les données sont encapsulées et leur accès ne
peut se faire que par le biais des méthodes.
Commentaire
Le mot clé ‘public’ précise que tout ce qui le suit (données membres ou fonctions
membres) sera public, le reste étant privé. Ainsi :
- x et y sont deux membre privés.
- ‘initialise’, ‘deplace’ et ‘affiche’ sont trois fonctions membres publics.
- La classe ‘point’ ci-dessus peut être définie et utilisée comme dans l’exemple
suivant :
Exemple
class C
{
public :
………………………………. ;
…………………………….. ;
private :
………………………………. ;
………………………………. ;
};
2. Affectation d’objets
Elle correspond à une recopie de valeurs des membres donnés (publics ou privés).
Ainsi, avec les déclarations :
class point
{ int x, y;
public :
………………………… ;
};
point p1, p2;
3.1. Introduction
Un constructeur est une fonction qui sera appelée automatiquement après la création
d’un objet. Ceci aura lieu quelque soit la classe d’allocation de l’objet : statique,
automatique ou dynamique.
- De la même façon, un objet pourra posséder un destructeur, il s’agit également d’une
fonction membre qui est appelée automatiquement au moment de la destruction de
l’objet correspondant.
- Par convention, le constructeur se reconnaît à ce qu’il porte le même nom que la classe.
Quand au destructeur, il porte le même nom que la classe précédé du symbole ~.
Exemple
Reprendre l’exemple utilisant la classe ‘point’ définie avec son constructeur.
class demo
{
int num ;
public :
demo(int);
~demo();
};
//------------------
demo::demo(int n)
{
num=n;
cout<<″Appel constr numéro : ″<<num<<endl;
}
//-----------------------------------------------
demo::~demo()
{
cout<<″Appel destr numéro : ″<<num<<endl;
}
//--------------------------------------
Exécution
Exemple
Construction d’un tableau de 10 valeurs entières prises au hasard, tel que l’argument
passé au constructeur est la valeur entière maximum à ne pas dépasser.
Ajouter une fonction membre appelée : ‘affiche’ pour afficher le contenu du tableau.
class tableau
{
int t[10];
public :
tableau();
void affiche();
};
//---------------------
tableau::tableau(int max)
{
for(int i=0;i<10;i++) t[i]=i*2;
}
//--------------------------------------------------
void tableau::affiche()
{
for(int i=0;i<10;i++) cout<<t[i]<<endl;
}
4. Membres statiques
Lorsqu’on crée différents objets d’une même classe, chaque objet possède ces propres
données membres.
Exemple
calss point
{
int x ;
int y ;
……………….
……………….
…………………..
};
Objet p1 Objet p2
p1.x p2.x
p1.y p2.y
- Pour pouvoir partager des données entre objets de la même classe, on les déclare
statiques.
Exemple
class point
{ static int x;
int y;
………………….
…………………
……………..
};
p1.x p2.x
Objet p1 Objet p2
p1.y p2.y
Commentaire
- Les membres statiques existent en un seul exemplaire quelque soit le nombre
d’objets de la classe correspondante, et même si aucun objet de la même classe n’a
été créé.
- Les membres statiques sont toujours initialisés par 0.
Exemple
class cpt_obj
{
static int c;
public :
cpt_obj();
~cpt_obj();
};
//----------------------
int cpt_obj::c=0;
//----------------------
cpt_obj()::cpt_obj()
{cout<<″Construction, il y a maintenant : ″<<++c<<″ Objet\n″;}
//------------------------------------------------------------
cpt_obj::~cpt_obj()
{cout<<″Destruction, il reste : ″ <<--c<<″ Objet\n″;}
//-----------------------------------------------------
main()
{
void f();
cpt_obj O1; f(); cpt_obj O2;
}
//-------------
void f(){ cpt_obj a, b; }
Exercice 1
Ecrire un programme permettant de créer des objets ayant chacun :
- un tableau de 5 éléments de type entier en tant que donnée ;
- une fonction pour remplir le tableau, une fonction pour trier le tableau et une
fonction pour afficher le contenu du tableau en tant que méthodes.
Exercice 2
Reprendre le même programme en remplaçant le tableau de 5 éléments par un tableau
dynamique de ‘ne’ éléments et instancier des objets ayant des tableaux dynamiques de
différentes tailles.
//-----------------------------------------
void tableau::remplir()
{ cout<<"Veuillez remplir le tableau avec "<<ne<<" entiers
:"<<endl;
for(int i=0;i<ne;i++) cin>>t[i];
}
//-----------------------------------------
void tableau::afficher()
{ for(int i=0;i<ne;i++) cout<<t[i]<<"\t"; cout<<endl; }
//-------------------------------------------------------
void tableau::trier()
{ int a;
for(int i=0;i<ne-1;i++)
for(int j=i+1;j<ne;j++)
if(t[i]>t[j]){a=t[i]; t[i]=t[j]; t[j]=a;}
}
//----------------------------
Exécution
Exemple
Surdéfinir les fonctions membres ‘point’ et ‘affiche’ de la classe ‘point’.
class point
{
int x, y;
public :
point();
point(int);
point(int,int);
void affiche();
void affiche(char *);
};
//--------------
point::point()
{ x=0; y=0; }
//-----------------------
point::point(int a)
{ x=y=a; }
//-----------------------
point::point(int a,int b)
{ x=a; y=b; }
//-------------------------
void point::affiche()
{
cout<<″on est a : ″<<x<<″ - ″<<y<<endl;
}
//--------------------------------------------
void point::affiche(char *t)
{
cout<<t;
affiche();
}
//----------------------------
main()
{
point p1; p1.affiche();
point p2(7); p2.affiche();
point p3(44,52); p3.affiche();
p3.affiche(″Troisième point : ″);
}
Exemple
Modifier l’exemple précédent de telle façon à ce qu’on remplace les deux fonctions
‘affiche’ par une seule, à un argument de type chaîne : le texte à afficher avant le
message : ″on est à : x – y ″. Sa valeur par défaut est la chaîne de caractères vide.
Exemple
Reprendre le dernier exemple en définissant les trois constructeurs en ligne.
class point
{
int x, y;
public :
point(){ x=0; y=0; }
point(int a){ x=y=a; }
point(int a,int b){ x=a; int b; }
void affiche(char *);
};
//---------------------------
void point::affiche(char *t)
{
cout<<t<<″on est à : ″<<x<<″-″<<y<<endl;
}
//---------------------------------------------
main()
{
………………….. ;
………….. ;
}
Exemple
class point
{
int x, y;
public :
point(int a,int b){ x=a; y=b; }
int coincide(point);
};
//----------------------------
int point::coincide(point O)
{
if(x==O.x && y==O.y) return 1;
return 0;
}
//-----------
main()
{
point p1(5,7), p2(5,7), p3(6,6);
cout<<″p1 et p2 : ″<<p1.coincide(p2)<<endl;
cout<<″p2 et p3 : ″<<p3.coincide(p2)<<endl;
cout<<″p1 et p3 : ″<<p1.coincide(p3)<<endl;
}
Exécution
class point
{
int x, y;
public :
point (int a=0,int b=0){ x=a; y=b; }
int coincide(point *);
};
//----------------------------
main()
{
point p1, p2(57), p3(57,0);
cout<<″p1 et p2 : ″<<p1.coincide(&p2)<<endl;
cout<<″p2 et p3 : ″<<p3.coincide(&p2)<<endl;
cout<<″p1 et p3 : ″<<p1.coincide(&p3)<<endl;
}
Exemple
Reprendre le dernier exemple en adaptant la fonction ‘coincide’ de telle façon à ce que
son argument soit transmis par référence.
class point
{
int x,y ;
public :
point(int a=0,int b=0){ x=a; y=b; }
int coincide(point &);
};
//---------------------------------
int point::coincide(point & O)
{
return (x==O.x && y==O.y) ? 1 : 0;
}
//----------------------------------------
main()
{
point p1, p2(57), p3(57,0);
cout<<″p1 et p2 : ″<<p1.coincide(p2)<<endl;
cout<<″p2 et p3 : ″<<p3.coincide(p2)<<endl;
cout<<″p1 et p3 : ″<<p1.coincide(p3)<<endl;
}
Exécution
Exemple
class point
{
int x, y;
public :
point(int a=0,int b=0){ x=a; y=b; }
void affiche(){ cout<<″Point : ″<<x<<″ - ″<<y<<″de
l’objet dont l’adresse est : ″<<this<<endl; }
};
//------------------------------------------------------------
main()
{
point p1, p2(5,3), p3(6,70);
p1.affiche(); p2.affiche(); p3.affiche();
}
Exécution
Remarque
Les objets statiques sont créés avant le début de l’exécution de la fonction ‘main’ et ils
sont détruits après la fin de son exécution.
Exemple
class point
{
int x,y;
public :
point(int a,int b){ x=a; y=b; }
...
};
- point p1(2, 7) ; est une déclaration correcte.
- point p2 ; et point p3(17) ; sont des déclarations incorrectes.
Remarque
- Le constructeur est appelé après la création d’un objet.
- Le destructeur est appelé avant la destruction d’un objet.
Exemple
class point
{
int x,y;
public :
point(int a,int b){ x=a; y=b; }
...
};
point p(1,1);
p=point(2,7);
Exercice
Ecrire un programme permettant de créer un objet automatique dans la fonction ‘main’,
deux autres objets temporaires affectés à cet objet tout en affichant le moment de leur
création et leurs adresses.
……………….
……………………….
main()
{
point p(1,1);
p=point(5,2); p.affiche();
p=point(7,3); p.affiche();
}
Exemple
………………………
main()
{
point *p;
p=new point(7,2);
p->affiche(); ou (*p).affiche();
p=new point(8,4);
p->affiche();
delete p;
}
4. Tableaux d’objets
Un tableau d’objet est déclaré sous la forme :
Exemple
point courbe[3];
Exemple
Ecrire un programme permettant de définir une classe appelée ‘point_coul’ à partir de la
classe ‘point’ définie précédemment et ayant comme données : x, y et couleur.
class point
{
int x,y;
public :
point (int a=0,int b=0)
{
x=a; y=b;
cout<<″Construction du point : ″<<x<<″ - ″
<<y<<endl;
}
~point()
{
cout<<″Destruction du point : ″<<x<<″ - ″
<<y<<endl;
}
};
//--------------------------------------
class point_coul
{
point p;
int couleur;
public :
point_coul(int,int,int);
~point_coul()
{
cout<<"Destruction du point colore En couleur : "
<<couleur<<endl; getch();
}
};
point_coul::point_coul(int a,int b,int c):p(a,b)
{
couleur=c;
cout<<″Construction de point_coul en couleur : ″
<<couleur<<endl;
}
//----------------------------
main()
{
point_coul pc(3,7,8);
}
Exécution
Exemple
int n=2;
int m=3*n-7;
int t[3]={5,12,43};
Remarque
La partie suivant le signe ‘=’ porte le nom d’initialiseur, il ne s’agit pas d’un opérateur
d’affectation.
C++ garantie l’appel d’un constructeur pour un objet créé par une déclaration sans
initialisation (ou par ‘new’). Mais, il est également possible d’associer un initialiseur à la
déclaration d’un objet.
- point p1(3) ; est une déclaration ordinaire d’un objet ‘p1’ aux coordonnées 3 et 0.
- point p2=7 ; entraîne :
La création d’un objet appelé ‘p2’.
L’appel du constructeur auquel on transmet en argument la valeur de
l’initialiseur ‘7’.
En fin, les deux déclarations :
‘point p1(3) ;’ et ‘point p2=7 ;’ sont équivalentes.
Exemple
point p1(33);
Cette situation est traitée par C++, selon qu’il existe un constructeur ou il n’existe pas de
constructeur correspondant à ce cas.
a. Il n’existe pas de constructeur approprié
Cela signifie que, dans la classe ‘point’, il n’existe aucun constructeur à un seul argument
de type ‘point’. Dans ce cas, C++ considère que l’on souhaite initialiser l’objet ‘p3’ après
sa déclaration avec les valeurs de l’objet ‘p1’. Le compilateur va donc mettre en place
une recopie de ces valeurs. (Analogue à celle qui est mise en place lors d’une affectation
entre objets de même type).
Ce cas est le seul ou C++ accepte qu’il n’existe pas de constructeur.
Une déclaration telle que : ‘point p(x);’ sera rejetée si ‘x’ n’est pas de type ‘point’.
b. Il existe un constructeur approprié
Cela signifie qu’il doit exister un constructeur de la forme : ‘point (point &);’.
Dans ce cas, ce constructeur est appelé de manière habituelle, après la création de
l’objet, sans aucune recopie.
Remarque
C++ impose au constructeur en question que son unique argument soit transmis par
référence.
La forme ‘point (point) ;’ serait rejetée par le compilateur.
Exécution
Commentaire
La déclaration : ‘tableau t1 ;’ a créé un nouvel objet sans faire appel au constructeur,
dans lequel on a recopié les valeurs des membres ‘ne’ et ‘p’ de l’objet t1.
A la fin de l’exécution de ‘main’, il y a appel du destructeur pour ‘t1’ et pour ‘t2’, pour
libérer le même emplacement.
Exécution
Mais grâce à la notion d’amitié entre fonction et classe, il est possible, lors de la
définition de cette classe d’y déclarer une ou plusieurs fonctions (extérieurs de la classe)
amies de cette classe.
Une telle déclaration d’amitié les autorise alors à accéder aux données privées, au même
titre que n’importe quelle fonction membre.
class point
{
int x,y;
public :
point(int a=0,int b=0) {x=a; y=b;}
friend int coincide(point,point);
};
//------------------------------------------
int coincide(point p1,point p2)
{
if(p1.x==p2.x && p1.y==p2.y) return 1; else return 0;
}
//--------------------------
main()
{
point o1(15,2), o2(15,2), o3(13,25);
if(coincide(o1,o2)) cout<<″les objets coïncident\n″;
else cout<<″les objets sont différents\n″;
Remarques
-L’emplacement de la déclaration d’amitié dans la classe est quelconque.
-Généralement, une fonction amie d’une classe possédera 1 ou plusieurs arguments ou
une valeur de retour du type de cette classe.
Syntaxe
class B;
class A
{
…………………
public :
friend int B::f(int,A);
};
//------------------------------------------------
class B
{
…………………
public:
int f(int,A);
};
//-----------------------------------------------------
int B::f(int,A)
{
…………………
………………
}
Commentaire
1. la fonction membre ‘f’ de la classe ‘B’ peut accéder aux membres privées de
n’importe quel objet de la classe ‘A’.
2. Pour compiler correctement la déclaration de la classe ‘A’, contenant une
déclaration d’une fonction amie, il faut :
- Définir ‘B’ avant ‘A’.
- Déclarer ‘A’ avant ‘B’.
2.3. Toutes les fonctions d’une classe sont amies d’une autre classe
Au lieu de faire autant de déclarations de fonctions amies qu’il y a de fonctions
membres, on peut résumer toutes ces déclarations en une seule.
Exemple
‘friend class B;’ déclarée dans la classe ‘A’ signifie que toutes les fonctions
membres de la classe ‘B’ sont amies de la classe ‘A’.
Remarque
Pour compiler la déclaration de la classe ‘A’, il suffit de la faire précéder de : ‘class B ;’
Ce type de fonction évite d’avoir à déclarer, les entêtes des fonctions concernées par
l’amitié.
Exercice
Ecrire un programme permettant de réaliser le produit d’une matrice par un vecteur à
l’aide d’une fonction indépendante appelée ‘produit’ amie des deux classes ‘matrice’ et
‘vecteur’.
Exécution
Exemple
Point operator + (point,point);
main()
{
point o1(10,20); o1.affiche();
point o2(45,50); o2.affiche();
point o3; o3.affiche();
o3=o1+o2; o3.affiche();
o3=operator+(o1,o2); o3.affiche();
o3=o1+o2+o3; o3.affiche();
o3=operator+(operator+(o1,o2),o3); o3.affiche();
}
Exemple
class point
{
int x,y;
public :
point(int a=0,int b=0) {x=a; y=b;}
void affiche()
{ cout<<″Point : ″<<x<<″ - ″<<y<<endl; }
point operator + (point);
};
//----------------------------------------------
//---------------------------
main()
{
point o1(10,20); o1.affiche();
point o2(40,50); o2.affiche();
point o3; o3.affiche();
o3=o1+o2; o3.affiche();
o3=o3+o1+o2; o3.affiche();
}
Lorsque plusieurs opérateurs sont combinés au sein d’une même expression, ils
conservent leurs priorités relatives et leurs associativités.
class tableau
{
int ne;
int *p;
public :
tableau(int n)
{
p=new int[ne=n]; for(int i=0;i<ne;i++) p[i]=(i+1)*10;
}
void affiche()
{for(int i=0;i<ne;i++) cout<<p[i]<<"\t"; cout<<endl;}
int operator[](int n){ return p[n]; }
~tableau(){delete []p;}
};
//---------------------------------------
main()
{
tableau t1(3);
t1.affiche();
cout<<t1[2];
t1.affiche();
}
La seule précaution à prendre consiste à faire en sorte que cette notation puisse être
utilisée non seulement dans une expression, mais également à gauche d’une affectation.
Il est donc nécessaire que la valeur de retour fournie par l’opérateur ‘[ ]’ soit transmise
par référence :
class tableau
{
int ne;
int *p;
public :
tableau(int n)
{
p=new int[ne=n]; for(int i=0;i<ne;i++) p[i]=(i+1)*10;
}
void affiche()
{for(int i=0;i<ne;i++) cout<<p[i]<<"\t"; cout<<endl;}
int & operator[](int n){ return p[n]; }
~tableau(){delete []p;}
};
Exécution
Remarque
L’opérateur ‘[]’ n’est pas imposé ici par C++, on aurait pu le remplacer par l’opérateur ‘()’
: ‘o(i)’ au lieu de : ‘o[i]’, ou un autre opérateur.
class tableau
{
int ne;
int *p;
public :
............;
};
Le problème de l’affectation est donc voisin de celui de la construction par recopie, mais
non identique :
Dans le cas d’affectation d’objets, il existe deux objets complets (avec leurs tableaux
dynamiques). Mais après affectation (t2=t1), t2 et t1 référencent le même
tableau dynamique (celui de t1), le tableau dynamique de t2 n’est plus référencé.
class tableau
{
int ne;
int *p;
public :
tableau(int n)
{
p=new int[ne=n];
for(int i=0;i<ne;i++) p[i]=(i+1)*10;
}
void affiche(){for(int i=0;i<ne;i++) cout<<p[i]<<"\t";
cout<<endl;
}
tableau & operator=(tableau & t)
{
delete []p;
p=new int[ne=t.ne];
for(int i=0;i<ne;i++) p[i]=t.p[i];
return *this;
}
~tableau()
{
delete []p;
}
};
//---------------------------------------
main()
{
tableau t1(3); t1.affiche();
tableau t2(4); t2.affiche();
tableau t3(5); t3.affiche();
t1=t3;
t1.affiche(); t3.affiche();
t1=t2=t3;
t1.affiche();
}
class tableau
{
int ne;
int *p;
public :
tableau(int n)
{
p=new int[ne=n];
for(int i=0;i<ne;i++) p[i]=(i+1)*10;
}
void affiche(){for(int i=0;i<ne;i++) cout<<p[i]<<"\t";
cout<<endl;}
tableau & operator=(tableau & t)
{
if(this!=&t)
{ delete []p;
p=new int[ne=t.ne];
for(int i=0;i<ne;i++) p[i]=t.p[i];
}
return *this;
}
~tableau()
{
delete []p;
}
};
//---------------------------------------
main()
{
tableau t1(3); t1.affiche();
t1=t1;
t1.affiche();
}
Exécution
Exercice
Refaire le même traitement pour des tableaux dynamiques de caractères.
1.2. Commentaire
- La déclaration ‘class point_colore : public point’ spécifie que la classe 'point_colore' est
dérivée de la classe de base 'point'.
- Le mot clé ‘public’ signifie que les membres publics de la class de base seront des
membres publics de la classe dérivée.
Le programme principal
main()
{
point_colore p;
p.inisialise(12,27); p.colorer(7); p.affiche();
p.deplace(1,2); p.affiche();
}
Exécution
void affiche_c()
{
cout<<"Point : "<<x<<" - "<<y;
cout<<"En couleur : "<<couleur<<endl;
}
Or, une classe dérivée n'a pas accès aux membres privés de la classe de base. La solution
est donc :
void affiche_c()
{ affiche(); cout<<" en couleur : "<<couleur<<endl; }
class point
{
int x, y;
public:
void initialise(int a,int b){x=a;y=b;}
void deplace(int a,int b){x=x+a; y=y+b;}
void affiche(){cout<<"Point : "<<x<<" - "<<y;}
} ;
//----------------------------------------------------
class point_colore:public point
{
int couleur;
public:
void initialise_c(int c)
{couleur=c;}
void colorer(int c)
{couleur=c;}
void affiche_c()
{
affiche();
cout<<" En couleur : "<<couleur<<endl;
}
};
Exécution
Exemple
#include <point.h>
class point_colore:public point
{
int couleur;
public:
void colorer(int c){couleur=c;}
void affiche()
{
point::affiche();
cout<<" en couleur : "<<couleur<<endl;
}
void initialise(int a,int b,int c)
{point::initialise(a,b); couleur=c;}
};
//--------------------------------------------
main()
{
point_colore p;
p.initialise(12,27,9);
p.affiche();
p.deplace(10,20); p.affiche();
p.colorer(7); p.affiche();
}
class point
{
int x,y;
public:
point(int,int);
};
//---------------------------------------------
class point_colore:public point
{
int couleur;
public:
point_colore(int,int,int);
};
Pour créer un objet de la classe ‘point_colore’, il faut tout d’abord créer un objet de la
classe ‘point’, donc faire appel au constructeur de la classe ‘point’, le compléter par ce
qui est spécifique a la classe ‘point_colore’ et faire appel au constructeur de la classe
‘point_colore’.
Exercice
Reprendre le programme précédent en ajoutant les constructeurs et les destructeurs
correspondants, tout en affichant les moments de constriction et de destruction des
objets. Prévoir aussi des objets dynamiques.
Exemple
pointcolore(int a=5, int b=5, int c=4):point(a*2, b*5);
Exemple
class point
{
int x, y;
public:
void initialise(int,int) {x=a; y=b;}
void deplace(int,int) {x=x+a; y=y+b;}
void affiche(){ ……………………… }
};
//----------------------------------------
class point_colore : private point
{
int couleur;
public:
void colorer(int c)
{couleur=c;}
};
Si on a :
point_colore O(12,4,6);
O.affiche(); ou O.point::affiche();
O.deplace(1,5); ou O.point::depace(1,5);
Exemple
Supposons qu'on a :
class point
{
protected:
int x,y;
public:
void initialise(int,int);
void deplace(int,int);
void affiche();
};
On peut donc déclarer dans la classe 'point_colore' dérivée de la classe 'point' une
fonction 'affiche' qui accède aux membres protégés x et y.
Remarque
Lorsqu'une classe dérivée possède une classe amie, cette dernière dispose des mêmes
droits d'accès que les fonctions membres de la class dérivée.
A
B C
D E F G H
Héritage simple
B C
Héritage complexe
Si on a :
point o1;
point_colore o2;
Exercice
A. Ecrire un programme écran de veille affichant des points (objets de type 'pointg' à
définir) en couleur à tout endroit de l'écran, et ce, jusqu'a ce que l'utilisateur appuie
sur une touche quelconque.
point coul
pointcolore
class point
{
int x,y;
public:
point(int a,int b)
{
x=a ;y=b ;
cout<<"Constructeur de point ";
}
~point(){cout<<"Destructeur de point ";}
void affiche(){cout<<"point "<<x<<" - "<<y;}
};
//-----------------------------
class coul
{
int couleur ;
public :
coul(int c)
{
couleur=c ;
cout<<"Construction de coul ";
}
~coul(){cout<<"Destruction de coul ";}
void affiche()
{cout<<"Couleur : "<<couleur<<endl;}
};
//-------------------------------------------------
class point_colore:public point,public coul
{
public:
point_colore(int a,int b,int c):point(a,b),coul(c)
{cout<<"construction de pointcolore\n";}
Exécution
B C
class A
{
int x,y;
………………
};
class B:public A
{ ……………… };
class C:public A
{ ……………… };
class D:public B, public C
{ ……………… };
Donc :
- si on veut laisser cette duplication, on utilise :
A::B::x ≠ A::C::x ou B::x ≠ C ::x
- si on ne veut pas de cette duplication, on précisera la classe ‘A’ comme classe virtuelle
dans les déclarations des classes ‘B’ et ‘C’.
class A
{
int x,y;
…………………..
};
class B:public virtual A
{
…………………….
};
class C:public virtual A
{
………..
};
class D:public B, public C
{
…………….
};
Remarque
- le mot-clé ‘virtual’ apparaît dans la classe ‘B’ et la classe ‘C’.
- le mot-clé ‘virtual’ peut être placé avant ou après le mot-clé ‘public’.
3. Appel des constructeurs et des destructeurs dans le cas des classes virtuelles
A1 A2
A
B C B C
D D
- Si ‘A’ n’est pas déclarée ‘virtual’ dans les classes ‘B’ et ‘C’, les constructeurs seront
appelés dans l’ordre : ‘A1’, ‘B’, ‘A2’, ‘C’ et ‘D’.
- Si ‘A’ a été déclarée ‘virtual’ dans ‘B’ et ‘C’, on ne construira qu’un seul objet de type de
‘A’ (et non pas deux objets).
Bien entendu, il sera inutile de préciser des informations pour ‘A’ au niveau des
constructeurs ‘B’ et ‘C’.
En ce qui concerne l’ordre des appels, le constructeur d’une classe virtuelle est
toujours appelé avant les autres. Dans notre cas, on a l’ordre ‘A’, ‘B’, ‘C’ et ‘D’.
Exemple
C D
Exercice
Interpréter et commenter le programme utilisant l’héritage multiple dans l’exemple
suivant de liste simplement chaînée de points ( objets appartenant à la classe ‘point’).
liste point
liste_point
Exécution
Le polymorphisme forme avec l’encapsulation et l’héritage les trois piliers des langages
orientés objets.
Exemple
Utilisation d’un pointeur de base pour manipuler un objet d’une classe dérivée.
class materiel
{
protected :
char ref[20];
char marque[20];
public :
materiel(char *r, char *m)
{
strcpy(ref,r);
strcpy(marque,m);
}
void affiche()
{cout<<"Reference : "<<ref<<"\tMarque : "<<marque<<endl;}
};
//-----------------------------------------------------------
Exécution
Commentaire
- Le pointeur ‘p’ est utilisé pour stocker l’adresse d’un objet de la classe ‘materiel’,
appeler la fonction ‘affiche’ et détruire l’objet créé.
- Le pointeur ‘p’ est ensuite utilisé pour réaliser les mêmes opérations sur un objet de la
classe ‘micro’.
-Dans le cas des deux objets créés, c’est la méthode ‘affiche’ de la classe ‘matériel’ qui a
été utilisée. Le compilateur a donc tenu compte du type de pointeur (matériel*) et non
du type d’objet pointé par ‘p’.
- L’idéal serait donc que le compilateur appelle ‘affiche’ de ‘micro’ quand ‘p’ pointe sur
‘micro’ et ‘affiche’ de ‘matériel’ quand ‘p’ pointe sur ‘matériel’.
- Pour obtenir ce résultat, il suffit d’indiquer au compilateur que la fonction ‘affiche’ est
une fonction polymorphe, c.à.d une fonction virtuelle.
Définition
Une fonction polymorphe (ou virtuelle) est une méthode qui est appelée en fonction du
type d’objet et non en fonction du type de pointeur utilisé.
Exemple
Pour que le programme précédent fonctionne correctement, il faut déclarer la
fonction ‘affiche’ une fonction polymorphe ou virtuelle en précédant son prototype par
le mot clé ‘virtual’.
Exécution
Exercice
Ajouter une classe ‘imprimante’ et une classe ‘scanner’ dérivées de la classe ‘matériel’ et
écrire un programme global sous forme de menu donnant à l’utilisateur la possibilité de
choisir le type de matériel à afficher.
Remarque
- Lorsqu’on appelle une fonction virtuelle pour un objet, le C++ cherche cette méthode
dans la classe correspondante. Si cette fonction n’existe pas dans la classe concernée, le
C++ remonte la hiérarchie des classes jusqu'à ce qu’il trouve la fonction appelée.
- Une fonction virtuelle ne peut être appelée par une autre méthode de la classe.
- Une fonction virtuelle ne peut être utilisée dans le corps d’un constructeur ou d’un
destructeur.
Pour manipuler les flux d’entrées/sorties, le c++ met à notre disposition des opérateurs
surchargés << et >> qui permettent respectivement d’afficher et de lire des données, ces
opérateurs pourront parfaitement être redéfinis dans le cas de nos classes, ce qui
permettra par exemple d’afficher le contenu des données membres d’une classe à
l’écran, ou d’écrire ces mêmes variables dans un fichier.
Un flux, aussi appelé canal de données, offre une transparence vis-à-vis de la source ou
de la destination des données :
En C++, tous les flux sont symbolisés par des classes qui font partie de la librairie
‘iostream.h’.
La classe de base est iostream et regroupe les caractéristiques communes aux flux
d’E/S.
Les deux principales classes dérivées de la classe iostream sont ostream pour les flux
de sortie, et istream pour les flux d’entrées.
Toute les classes de la librairie ‘iostream.h’ disposent des deux opérateurs surchargés
<< et >>. L’opérande de gauche de l’opérateur << doit correspondre à un objet de la
classe ostream, pour l’opérateur >> cet opérande doit être un objet de la classe istream.
Ces deux opérateurs ont été définis pour les types de données suivants : char, short, int,
long, float, double, long double, char*, void*.
C++ fournie quatre flux prédéfinis pour afficher ou lire des informations.
cout : Flux de sortie standard (l’écran par défaut). Cette variable est un objet d’une
classe dérivée de ostream.
cin : Flux d’entrée standard (le clavier par défaut). Cette variable est un objet d’une
classe dérivée de istream.
cerr : Sortie erreur standard (l’écran par défaut). Cette variable est un objet d’une
classe dérivée de ostream.
clog : Permet à la fois d’envoyer des messages d’erreur vers la sortie erreur standard
(l’écran par défaut) et de remplir un fichier d’alerte. Cette variable est un objet de
la classe ‘ostream’.
Exemple
main()
{
int e=37;
float x=4,5;
cout<<"debut \n";
cout<<"Entier : "<<e<<endl;
cout<<"Reel : "<<x<<endl;
}
Commentaire
- Ce programme utilise 3 fois l’objet ‘cout’ pour afficher du texte à l’écran.
- La librairie ‘iostream.h’ fournit un certain nombre de mots clés qui permettent de
modifier les caractéristiques d’un flux. Ces commandes sont utilisées directement avec
l’opérateur << en respectant la syntaxe : ‘cout<<manipulateur ;’.
Manipulateur Rôle
dec Convertir en base décimale
hex Convertir en base hexadécimale
oct Convertir en base octale
ws Supprimer les espaces
endl Ajouter un saut de ligne en fin de flux (\n)
ends Ajouter un caractère de fin de chaîne
flush Vider un flux de sortie
setbase(int a) Choisir la base (0, 8, 10 ou 16) . 0 est la valeur par défaut
setfill(int c) Choisir le caractère de remplissage (padding)
setprecision(int c) Indiquer le nombre de chiffres d’un nombre décimal
setw Choisir la taille du champ (padding)
Exemple
int e=31;
cout<<e;
cout<<hex<<e;
Remarque
Le padding consiste à compléter généralement avec des espaces ou des zéros, un
élément affiché à l’écran en précisant la taille d’un champ ou d’une colonne (set w). Tout
élément affiché dont la taille est insuffisante sera complété par défaut par des espaces.
De cette manière chaque élément sera affiché sur la même longueur de caractères.
Méthodes Rôle
fill() Renvoie le caractère de remplissage
fill(char c) modifie le caractère de remplissage
Exemple
main()
{
int e=15;
cout<<"conversion"<<endl;
cout<<"Entier : "<<e<<endl;
cout<<"Hexadecimal : "<<hex<<e<<endl;
cout<<"Octal : "<<oct<<e<<endl;
cout<<"------------------------------------------\n";
cout<<"Largeur et Padding\n";
float x=3.15;
cout.setf(ios::right,ios::adjustfield);
cout<<"Decimal : "<<setw(10)<<x<<endl;
cout<<"Decimal : "<<setw(10)<<x+1000<<endl;
cout<<"Decimal : "<<setw(10)<<setfill('#') <<x<<endl;
cout<<"-------------------------------------------\n";
cout<<"Nombre de chiffres\n";
double pi=3.14151617;
cout<<"PI : "<<pi<<endl;
cout<<"PI : "<<setprecision(1)<<pi<<endl;
cout<<"PI : "<<setprecision(6)<<pi<<endl;
cout<<"-------------------------------------------\n";
char chaine[10];
cout<<"saisir une chaine : ";
cin.width(sizeof(chaine)); cin>>chaine;
La classe istream contient la fonction membre getline qui permet de saisir des chaînes
de caractères comprenant des espaces non acceptés par l’opérateur >>.
Avec :
//---------------------------------------------------------
void voiture::saisie()
{
cout<<"Marque : "; cin.getline(marque,sizeof(marque));
cout<<"Modele : "; cin.getline(modele,sizeof(modele));
cout<<"Prix : "; cin.getline(prix,sizeof(prix));
}
//---------------------------------------------------------
void voiture::affiche()
{
cout.setf(ios::left,ios::adjustfield);
cout<<"Marque : "; cout.width(15); cout<<marque;
cout<<"Modele : "; cout.width(15); cout<<modele;
cout<<"Prix : "; cout.width(15); cout<<prix; cout<<endl;
}
//------------------------------------------------------------
main()
{
voiture v1;
v1.saisie();
cout<<"\nAffichage \n";
v1.affiche();
cout<<endl;
voiture v2;
v2.saisie();
cout<<"\nAffichage \n";
v2.affiche();
}
Pour redéfinir les opérateurs de flux, on doit respecter une syntaxe telle qu’elle est
définie dans l’exemple suivant :
Exemple
class demo
{
public :
friend ostream& operator<<(osream&,demo&);
friend istream& operator>>(isream&,demo&);
};
Ostream & operator<<(osream& …,demo& …)
{ … }
Istream & operator>>(isream& …,demo& …)
{ … }
Commentaire
- Les deux opérateurs << et >> sont surdéfinis en tant que fonctions amies de la
classe ‘demo’, de cette manière, ils pourront accéder à tous ses membres.
- Dès que l’opérateur << sera utilisé avec comme opérande de gauche un objet de
type ‘ostream’ (ou dérivé) et comme opérande de droite, un objet de type ‘demo’
ce sont les instructions de la fonction operator << qui seront exécutées.
- L’opérateur >> est utilisé d’une façon analogue à celle de l’opérateur <<.
- Les opérateurs << et >> peuvent être surdéfinis pour des objets dynamiques.
Exécution
Dans le cas d’un pointeur vers la classe voiture, il faut redéfinir les opérateurs << et >>
comme par exemple :
Exemple 1
Extrait d’un programme qui permet d’écrire dans un fichier.
ofstream fs("c:\\demo.txt");
if(!fs)
{
cout<<"Erreur d'écriture dans ce fichier ! \a\a";
return 1;
}
fs<<"valeur de la donnée: "; fs<<17.12; fs<<"Fin";
fs.close(); …
Commentaire
Exemple 2
Extrait d’un programme permettant de lire un fichier
ifstream fe("c:\\demo.txt");
if(!fe)
{
cout<<"Erreurs d'ouverture ! \a\a";
return 1;
}
char texte1[30];
double x;
char texte2[30];
fe>>texte1; fe>>x; fe>>texte2;
cout<<texte1<<x<<texte2;
fe.close(); …
Commentaire
- Un objet ‘fe’ de type ‘ifstream’ est créé pour accéder en lecture au fichier
‘demo.txt’.
- Le flux ‘fe’ est utilisé pour récupérer les données qui sont affichées ensuite sur
l’écran.
Remarque
La fonction close, qui permet de fermer un fichier, est appelée automatiquement par le
destructeur des classes ofstream et ifstream.
Exercice
Ecrire un programme permettant de rassembler les deux extraits.
getch();
}
Exécution
Syntaxe
template<arguments du template>
type fonction(arguments)
{
…………………
}
Ou
template<arguments du template> type fonction(arguments)
{
…………………
}
Description
- ‘arguments du template’ : s’écrit ‘class type1, type2, …’ où type1, type2...
représentent les types paramétrés (par convention on prend la lettre T, U …).
- La fonction déclarée peut utiliser ensuite le ou les types fictifs.
Exemple
Les fonctions surchargées suivantes :
void affiche(short v)
{ cout<<"Valeur : "<<v<<endl; }
//----------------------------
void affiche (float v)
{ cout <<"Valeur : "<<v<<endl; }
//--------------------------------
void affiche (double v)
{ cout <<"Valeur : "<<v<<endl; }
Exécution
Remarque
Une fonction template peut employer plusieurs arguments :
Exercice
Définir une fonction template ou modèle appelée ‘max’ dont l’objectif est de renvoyer le
plus grand des deux éléments passés en argument.
Syntaxe
template <arguments du template>
class nom_de_la_classe
{
………………;
………………;
};
- Une classe modèle ou template peut également contenir des fonctions membres qui
n’utilisent pas les types paramétrés. Cependant, même dans ce cas, le corps de la
fonction doit respecter la contrainte liée au nom complet de la fonction membre.
Exercice 1
Reprendre l’exemple de la classe ‘tableau’ et écrire un programme global à partir des
déclarations suivantes :
template <class T>
class tableau
{
int ne;
T *p;
public :
tableau(int n){ p=new T[ne=n];}
T & operator[](int n){ return p[n]; }
~tableau(){delete []p;}
};
//---------------------------------------
main()
{
tableau<int> t1(5);
for(int i=0;i<5;i++) t1[i]=i+1;
for(int i=0;i<5;i++) cout<<t1[i]<<"\t"; cout<<endl;
//-----------------------------------
tableau<float> t2(7);
for(int i=0;i<7;i++)t2[i]=i*2.3;
for(int i=0;i<7;i++)cout<<t2[i]<<"\t"; cout<<endl;
//-----------------------------------------------------
tableau<char> t3(8);
for(int i=0;i<8;i++)t3[i]='A'+i;
for(int i=0;i<8;i++)cout<<t3[i]<<"\t"; cout<<endl;
getch();
}
Exercice 2
Redéfinir la classe ‘point’ pour des coordonnées de type paramétré (short, int ou long) et
donner des exemples d’instanciation de points.
Programme
template <class U>
class point
{
U x;
U y;
public :
point(U a,U b){x=a; y=b;}
void affiche(){cout<<"X="<<x<<" - Y="<<y<<endl;}
};
//-----------------------------------------------------------
main ()
{
point<int> p1(10,20); p1.affiche();
point<double> p2(23.45,20.76); p2.affiche();
point<float> p3(1.98,2.907); p3.affiche();
point<char> p4('F','K'); p4.affiche();
}
Exécution
C++ propose, à part l’utilisation de la valeur de retour des fonctions, une nouvelle
solution pour intercepter les erreurs : le mécanisme des exceptions.
int positive(int v)
{ if(v<0) return 0;
cout<<«Valeur positive \n»;
return 1 ;
}
//---------------------------
int inf(int v,int max)
{ if(v>max) return 0;
cout<<"Valeur inférieure à : "<<max<<endl;
return 1;
}
//-----------------------------------------------------------
int sup(int v,int min)
{ if(v<min) return 0;
cout<<"Valeur supérieure à : "<<min<<endl;
return 1;
}
//------------------------------------------------------------
int main()
{
int minimum=10;
int maximum=100;
int n,retour;
cout<<"Saisir une valeur entière : "; cin>>n;
retour=positive(n);
if(retour==1)
{ retour=inf(n,maximum);
if(retour==1)
{ retour=sup(n,minimum);
if(retour==1)
{ cout<<"Valeur correcte !";
return 1;
}
else
{
Remarque
Dans ce programme le contrôle d’erreurs est alourdie par les ‘if’ imbriqués.
Un constructeur ne peut pas renseigner sur sa valeur de retour, qui n’en possède
pas, pour indiquer qu’une erreur s’est produite.
Exemple
class erreur
{
………………
};
Remarque
On peut ajouter à la classe ‘erreur’ autant de données et de fonctions membres.
Exemple
void positive(int v)
{
if(v<0)
{
erreur er;
throw er;
}
cout<<"Valeur positive \n";
}
Remarque
Si ‘v’ est < à 0 la fonction construit un objet statique de la classe d’exception ‘erreur’ et
envoie cet objet avec l’opérateur throw qui ressemble à return.
void positive(int v)
{
if(v<0) throw erreur();
cout<<"Valeur positive \n ";
}
Commentaire
Il y a création d’un objet de type ‘erreur’ sans nom, cette solution n’est intéressante que
si on n’a pas besoin d’appeler des fonctions membres de la classe d’exception.
Remarque
Exemple
void contrôle(int v) throw (erreur,invalid)
{
……………
………………………
}
Pour intercepter une exception, C++ fournit une syntaxe basée sur l’utilisation des blocs
try et d’un ou plusieurs blocs catch :
try
{
//Appels de fonctions pouvant générer des erreurs
}
catch (classe d’exception)
{
//Ce bloc s’exécute pour une exception de type
//‘classe d’exception’
}
catch(…)
{
//Ce bloc s’exécute pour toutes les exceptions
//en dehors de la classe d’exception
}
Description
Si une exception est envoyée par une de ces fonctions appelées dans le bloc ‘try’, le
mécanisme d’exception entraînera les étapes suivantes :
Remarque
Exercice
Prolongement
Afficher les messages d’erreurs à l’aide des fonctions membres des classes d’exception.
class erreur_positive
{
public :
void affiche(){cout<<"Erreur ! valeur negative ! \n";}
};
class erreur_inf
{
int maxi;
public:
erreur_inf(int ma){maxi=ma;}
void affiche()
{cout<<"Erreur ! valeur superieure A : "<<maxi<<endl;}
};
class erreur_sup
{
int mini;
public:
erreur_sup(int mi){mini=mi;}
void affiche()
{cout<<"Erreur ! valeur inferieure A : "<<mini<<endl;}
};
void positive(int v)
{
if(v<0) { erreur_positive er; throw er;}
cout<<"Valeur positive \n";
}
Remarque
Les exceptions sont symbolisées par des classes dans lesquelles on peut intégrer les
notions d'héritage et de polymorphisme.
Programme
class erreur
{
public:
virtual void affiche(){cout<<"Erreur !";}
};
class erreur_positive:public erreur
{
public :
void affiche(){cout<<"Erreur ! valeur negative ! \n";}
};
class erreur_inf:public erreur
{
int maxi;
public:
erreur_inf(int ma){maxi=ma;}
void affiche()
{cout<<"Erreur ! valeur superieure A : "<<maxi<<endl;}
};
//--------------------------------------------------------
main()
{
int minimum=10; int maximum=100;
try
{
int n;
cout<<"Saisir une valeur entière : "; cin>>n;
positive(n);
inf(n,maximum);
sup(n,minimum);
cout<<"Valeur correcte !\n";
}
catch(erreur *e)
{
e->affiche(); delete e ;
}
catch(...)
{
cout<<"Erreur inconnue !\n";
}
}
- La bible du programmeur C/C++, Kris Jamsa et Lars Klander, Editions G. Reynald, 1999.
- Comment programmer en C++, Deitel et Deitel, Editions G. Reynald, 2001.
- Apprendre le C++, C. Delannoy, Editions Eyrolles, 2007.
- Exercices en langage C++, C. Delannoy, Editions Eyrolles, 2007.
- C++ pour les programmeurs C, C. Delannoy, Editions Eyrolles, 2007.