Académique Documents
Professionnel Documents
Culture Documents
OBJETS
APPLICATIONS EN C++
Version 1.2 /09 Novembre 2005
Historique :
Dans un premier temps vinrent des langages bas niveau: les assembleurs. Ces langages sont
spécifiques à chaque machine et s'adressent directement aux composants constituant celle-ci.
C'est en fait une traduction directe du train binaire en instructions simples. (ex: charger en
mémoire une valeur, lire une valeur, faire une addition,...)
Ensuite, dans un souci de simplicité, les langages devinrent de plus en plus complexes,
proches de l'anglais écrit. Se succédèrent alors: BASIC, COBOL, FORTRAN. Notons que
plus un langage est dit évolué plus il devient spécifique et facile d'utilisation (nous parlons ici
uniquement de la syntaxe).
En 1972, Dennis Ritchie créa le C ancêtre du C++. Ce langage peut être qualifié à la fois de bas
niveau car il permet de faire appel à toutes les ressources de la machine mais aussi de langage évolué.
En effet, il introduit une syntaxe assez complexe par rapport aux langages précités ainsi que de
nombreuses fonctions évoluées. Ces fonctions sont regroupées en librairies que tu peux considérer
comme des boîtes à outils.
La mise à jour la plus marquante du C fut apportée par Bjarne Stroustup en 1982. Il y intégra la
programmation objet. Le C++ est en fait une surcouche du C (C++ signifie une incrémentation du C).
Il hérite donc de tous les outils du C
Développé dans les laboratoires d’AT&T Bell au début des années 1982 par
Bjarne Stroustrup, le langage C++ est un langage:
➱ À typage fort,
➱ Compilé (impératif),
➱ Et orienté objet (POO1).
Schématiquement:
C++ = C + typage fort + objets (classes)
Remarquons toutefois que C++ n’est pas purement objet (comme le sont par exemple
Eiffel ou
Smalltak) mais est un langage hybride: on peut très bien programmer en C++ sans pour autant
programmer par objets
Avantages :
• Programmes exempts de bogues «syntaxiques», et [un peu] plus robuste, grâce au typage
fort
• Applications efficaces grâce à la compilation
• Compilateur disponible sur pratiquement toutes les plate-formes et documentation
abondante, grâce à sa large diffusion
Inconvénients :
Pour pouvoir être compilé en une séquence binaire exécutable, le code source doit fournir au
compilateur un «point d’entrée». Par convention, ce point d’entrée est en C++ une fonction
intitulée main:
Programme I.1
hello.CPP
#include <iostream.h>
void main()
{
cout << "Hello World !"
<< endl;
}
Hello.exe
0100100101000101110101001
1010101010010100101001010
0000101010100000010001000
0001001001110101010010011
1001000000000000100101011
...
bool : booléen
char : caractère
int : entier
long : entier long
float : reel
double : reel en double precision (2 mots machine)
long double : reel en double precision (3 ou 4 mots machine)
Les constants caractère :
‘a’,’G’,… ‘\n’, ‘\t’, ‘\a’
Arithmétiques
( + ): addition
( - ): soustraction
( * ) : multiplication
( / ) : division
( %) : modulo
Relationnels :
Comparaison > >= <= <
Égal different == / !=
Négation !
ET logique : &&
OU logique : || (touche ALT GR+6)
L’affectation
• a=1;
• b=2+3;
• c=a+b;
Incrémentation/décrémentation
a=5; b=6;
c=++a-b;
exemple I.2.1
#include <iostream.h>
void main()
{
int a=5;
int b=6;
exemple I.3.1
#include <iostream.h>
void main()
{
int a=10;
a+=6;
cout <<" a vaut :"<<a<<"\n";
}
Expr1?expr2:expr3
exemple 1.4.1
#include <iostream.h>
void main()
{
int z,a,b,c;
a=20;
b=15;
(a>b)?(a++) : b--;
z=(a>b)?a:b;
exemple I.6.1
# include <iostream.h>
void main()
{
int n;
n=10;
if (n>0)
{
int cumul=0;
cumul+=n;
cout <<" cumul vaut :"<<cumul<<endl;
}
// cout <<" cumul vaut :"<<cumul<<endl; // ERREUR DE
COMPILATION !!! error C2065: 'cumul' : undeclared identifier
if (<expression>)
<instruction-1>
[else
<instruction-2> ]
SWITCH
switch (<expression>) {
case <constante-1> : <suite d’instructions> break;
case <constante-2> : <suite d’instructions> break;
case <constante-n> : <suite d’instructions> break;
default : <suite d’instructions>
}
Remarque : Si on ne met pas “break”, l’exécution va continuer à la suite au lieu de sortir du
switch,
L'opérateur new
new : Alloue de l'espace mémoire et fournit comme résultat l'adresse
char * cp;
ip = new int;
cp = new char;
*ip = 35;
*cp = 'f';
initialisation
Nous pouvons initialiser un pointeur lors de sa déclaration
double *dp = new double (3.14);
/* c'est équivalent à
double * dp;
dp = new double;
*dp = 3.14;
*/
pb [5] = 32;
*(pb + 5) = 32;
Remarque:
Exemple:
int * tpb = new int [6] (0,0,0,0,0,0); //erreur
for (i=0;i<6;i++)
tpb[i]=0;
L'opérateur Delete
delete: Libère l'espace mémoire alloué par new.
delete pointeur;
delete ip;
delete c;
delete []pb;
Les fonctions
Théoriquement, toute fonction retourne une valeur, qui peut être utilisée ou non. Touefois, un
mot clé particulier void, permet d’indiquer qu’une fonction ne retourne pas de valeur (c’est
une procédure).
L’entête d’une fonction est appelé signature, les paramètres sont de 2 types par valeur ou par
référence. L’exemple qui suit montre ce mode de passage.
#include <iostream.h>
void IncrementerRef(int& a)
{
a++;
}
void IncrementerVal(int a)
{
a++;
}
void main()
{
int b=5;
IncrementerRef(b);
cout<<"Après {IncrementerRef} b vaut maitenant :"<<b<<endl; //6
IncrementerVal(b);
cout<<"Après {IncrementerVal} b vaut maitenant :"<<b<<endl; //6
}
La bibliothèque d’E/S :
Pour utiliser cette bibliothèque il faut inclure le fichier de déclarations
#include <iostream.h>
Les opérations standards d’entrée et de sortie sont fournies par 3 flots (streams):
- cin : désigne le flot d’entrée standard
- cout : flot de sortie standard
Les fonctions
Les fonctions en C peuvent être définies suivant deux modèles :
Const
Le C++ a quelque peu modifié l'utilisation "C" de ce qualificatif. Pour rappel, "const" est
utilisé pour définir une variable constante. C'est une bonne alternative à un define.
La portée en C++ est désormais plus locale. En C, un const permettait pour une variable
globale d'être "visible" partout. C++ limite quant à lui la portée d'une telle variable, au fichier
source contenant la déclaration.
En C/C++ l’utilisation de const signifie qu’un identicateur correspond à quelque chose dont la
valeur ne peut pas changer.
Lorsque const est appliqué à une variable locale automatique, la portée est limitée au bloc
dans lequel s’est effectuée la déclaration.
Lorsque const est appliqué à une variable globale, C++ limite la portée au fichier source.
Exemple :
#include <iostream.h>
#include "calcul.cpp"
void main()
{
cout <<somme(10,20)<<endl;
cout<<N;
}
Les commentaires
Les commentaires d’une source peuvent maintenant être indiqués de deux façons différentes :
Ces nouveaux commentaites sont utilisables uniquement dans le cas où tout le reste de la ligne
est un commentaire.
Déclarations
En C, vous avez été habitué à déclarer les variables en début de bloc, c'est-à-dire en début de
fonction ou de procédure. En C++, il est possible de déclarer une variable à tout moment dans
le code.
Cet exemple est bien entendu dénué de tout intérêt, mais il montre la liberté offerte par C++
en ce qui concerne les déclarations et les initialisations des variables.
Référence
C++ introduit une nouvelle notion fondamentale : les références. C'est une notion qui peut
sembler difficile à assimiler au départ, notamment pour des personnes qui ne sont pas encore
habituées à utiliser des pointeurs en C.
La référence est un nouveau type qui permet de manipuler un alias sur une autre variable
existante.
Déclaration :
Une référence s’effectue toujours sur une entité d’un type donné.
Une référence s’écrit : type & ;
Exemple :
int variable;
int & ref = variable; //ref est une variable de type référence sur un int.
int *ptr = &variable; //On récupère l’adresse de variable.
Une variable de type référence doit toujours être initialisée lors de sa déclaration.
Exemple :
//Declaration incorrecte
int & nombre;
//Declaration correcte
int number;
int & nombre = number;
Nous voyons tout de suite que dans le cas d'un appel comme celui-ci :
...
int a, b, c, d;
a = 0;
b = 5;
c = FaitQqch(a,b);
d = FaitQqch2(&a,&b);
...
return nRet;
}
Le "&" signifie que l'on passe par référence. La ligne de déclaration de la fonction est en fait
la seule différence avec une transmission par valeur, d'un point de vue code. C'est-à-dire que
l'utilisation des variables dans la fonction s'opère sans "*" et l'appel à la fonction sans "&".
C'est ce qui fait la puissance des références : c'est transparent pour l'implémentation, mais cela
possède la puissance des pointeurs.
Nous nous habituerons à leur utilisation au fur et à mesure.
En plus des "anciens" malloc et free du C, C++ possède un nouveau jeu d'opérateurs
d'allocation/désallocation de mémoire : new et delete.
Ils ont été créés principalement pour la gestion dynamique des objets, mais on peut les utiliser
également pour des variables simples.
Voici une comparaison d'utilisation :
...
// pour un simple pointeur
...
/* pour un simple pointeur */
int * pInt; int * pInt;
pInt = (int*)malloc(1*sizeof(int)); pInt = new int;
free(pInt); delete pInt;
... ...
/* pour un tableau */ // pour un tableau
pInt = (int*)malloc(100*sizeof(int)); pInt = new int[100];
delete pInt;
...
free(pInt);
// Tableau de classes
...
pToto = new MyClass[50];
delete []MyClass;
void main()
{
cout << "Hello World !";
cout << "Hello World !\n";
cout << "Hello World !" << endl;
int n = 5;
cout << "La valeur est " << n << endl;
float f = 3.14f;
char *ch = "Coucou";
cout << ch << " float = " << f << endl;
}
Cette nouvelle sortie standard est donc très intuitive à employer du fait qu'il est inutile de lui
préciser le format de la valeur que l'on souhaite afficher.
Le "endl" est en fait disponible pour éviter d'éventuels "\n", en fin de ligne.
void main()
{
int n;
cout << "Entrez un entier : ";
cin >> n;
cout << "Vous avez entré : " << n << endl;
char ch[81];
float f;
cout << "Entrez un entier, une chaine, puis un float :";
cin >> n >> ch >> f;
cout << "Vous avez entré : " << n << ch << f << endl;
}
Cet exemple illustre brièvement comment fonctionne "cin". Bien entendu, aucun contrôle de
type n'est effectué, c'est donc à l'utilisateur qu'il advient de faire attention.
• Lorsque des objets ont les mêmes attributs et comportements: ils sont regroupés dans une
famille appelée: Classe.
• Une classe est un modèle à partir duquel on peut générer de nouvelles classes ou de
nouveaux objets. Un objet est une instance d’une classe.
Voici un exemple d’une conception de la hiérarchie d’objets moyens de transport avec une
vision informatique de ce domaine :
Contrat
• Une classe passe un contrat avec d’autres classes elle s’engage à fournir les services publiés
dans sa spécification et les autres classes s’engagent à ne pas faire usage de connaissances
autres que celles connues dans cette spécification.
EN C++:
Commentaires :
Les champs x et y sont les attributs de classes, appelés aussi variables membres.
Les fonctions Afficher et Placer sont appelées méthodes ou fonctions membres.
exemple II.2.1
#include <iostream.h>
#include "Points.cpp"
void main()
{
Points p;
p.x=10;
p.y=20;
p.afficher();
p.placer(1,5);
p.afficher();
exemple II.2.2
#include <iostream.h>
#include "Points.cpp"
void main()
{
Points *p=new Points;
p->x=10;
p->y=20;
p->afficher();
p->placer(1,5);
p->afficher();
Nous pouvons maintenant imaginer vouloir comparer deux points, afin de savoir s'ils sont
égaux. Pour cela, nous allons mettre en oeuvre une méthode "Coincide" qui renvoie "1"
lorsque les coordonnées des deux points sont égales :
class Point
{
int x;
int y;
public :
int Coincide(Point p)
{
if( (p.x==x) && (p.y==y) )
return 1;
else
return 0;
}
Cette partie de programme fonctionne parfaitement, mais elle possède un inconvénient majeur
: la passage de paramètre par valeur, ce qui implique une "duplication" de l'objet d'origine.
Cela n'est bien sûr pas très efficace.
La solution qui vous vient à l'esprit dans un premier temps est probablement de passer par un
pointeur. Cette solution est possible, mais n'est pas la meilleure, dans la mesure où nous
savons fort bien que ces pointeurs sont toujours sources d'erreurs (lorsqu'ils sont non
initialisés, par exemple).
La vraie solution offerte par le C++ est de passer par des références. Avec ce type de passage
de paramètre, aucune erreur est possible puisque l'objet à passer doit déjà exister (être
instancié). En plus, les références offrent une simplification d'écriture, par rapport aux
pointeurs :
#include <iostream.h>
class Point
{
int x;
int y;
public :
int Coincide(Point & p)
{
if( (p.x==x) && (p.y==y) )
return 1;
else
return 0;
}
pp.x=0;
pp.y=1;
p.x=0;
p.y=1;
if( p.Coincide(pp) )
cout << "p et pp coincident !" << endl;
if( pp.Coincide(p) )
cout << "pp et p coincident !" << endl;
}
Utilisation de this : lorsque l’on a besoin de pointer l’objet à l’intérieur de la classe même.
This peut toutefois être utilisé partout à l’intérieur de la classe pour accéder aux attributs.
exemple II.2.3
#include <iostream.h>
class Points{
public :
void afficher()
{
//Notation Inutile avec this
cout <<this->x<<','<<this->y<<endl;
}
void placer (int a ,int b)
{
x=a;
y=b;
}
bool PointEgal (Points *p)
{
return(p==this);
}
bool PointCoincidant(Points *p)
{
return ((p->x==this->x)&&(p->y==this->y));
}
public:
int x;
int y;
};
Utilisation dans le fichier main.cpp :
Programme principal
#include <iostream.h>
#include "Points.cpp"
void main()
{
Commentaires :
• L’attribut couleur est privé
• La méthode colorier est privée
Les lignes :
Toute classe extérieure(ou module) ayant une visibilité sur la classe donnée peut utiliser cette
caractéristique.
# (Protected)
Seuls les descendants (qu’on verra plus tard) de la classe peuvent utiliser cette caractéristique.
- (Private)
La classe, et elle seule, peut utiliser cette caractéristique.
Intéressant!!
Les développeurs s’échangent des classes et ne risquent pas d’accéder par erreur à un
membre privé: c’est une protection.
Ainsi l'implémentation d'un objet peut être modifiée sans affecter les applications qui
emploient cet objet. Voici une figue caractéristique :
Il dispose de commandes, interfaces, pour démarrer, accélérer et freiner, lui permettent d’utiliser sa
motocyclette.
Pour connaître la vitesse, qui est une propriété de la motocyclette, il dispose d’une autre interface: le
compteur de vitesse.
class Cpersonne{
public:
char Nom [50],
Prenom [50];
private:
char Adresse [100];
public :
int Age;
void Changer_adresse (char nouv_adresse[])
{ strcpy(Adresse, nouv_adresse); }
};
Nous supposons qu'un programme P utilise des objets de la classe Cpersonne il ne peut
utiliser que les attributs: Nom, Prénom, Age et l'opération Changer_adresse parce que
Adresse est déclarée comme PRIVATE soit:
PROGRAMME P
int main()
{ Cpersonne Per;
strcpy(Per.Nom,"Ben Abdallah");
strcpy(Per.prénom,"Mohamed" );
Per.Age=25;
Per.Changer_Adresse("5, rue des fleures Tunis");
return 0;
}
Nous remarquons que ce changement n'affecte pas le programme P qui utilise des objets de
cette classe. Il peut continuer à utiliser Per.Changer_Adresse("5, rue des fleures
Tunis"); sans savoir qu’une modification a été portée. Il n’a pas besoin de le savoir !
Qui peut accéder aux membres PRIVATE? : seuls les fonctions membres de la classe qui en
font appel.
EXERCICE:
Faire le commentaire de ce programme.
exemple II.4.1
class Employe{
public:
float getSalaireNet()
{
setImpots();
setPrime();
return 12*SalBase+Impots;
}
#include <iostream.h>
#include "Employe.cpp"
void main()
{
Employe *emp=new Employe;
emp->setTauxImpots(25);
emp->setSalBase(500.000);
float SalNet=emp->getSalaireNet();
cout<<"Le salaire de l'employé vaut:"<<SalNet;
}
private:
float SalBase;
float Impots;
float TauxImpots;
float Prime;
DISCUSSION?
Pour des raisons de clarté et d’organisation du code, il est possible en C++ (et non en C) de
définir une fonction en 2 étapes :
1- Prototype de la fonction dans un fichier .h (non compilé)
2- Implémentation de la fonction dans un fichier .cpp
Exemple :
Fichier Calcul.h
int somme(int a,int b);
Fichier Calcul.cpp
#include "calcul.h"
int somme(int a,int b)
{
return a+b;
}
Fichier main.cpp
#include <iostream.h>
#include "calcul.h"
void main()
{
cout <<somme(10,20);
}
Tous les membres d’une classe sont déclarées normalement à l’intérieur du bloc class
<NomClasse>
{ ….. }
Cependant dans le cas des fonctions, aussi bien publiqus que privées,on peut se limiter à
n’écrire que leur en-tête à l’intérieur de la classe et définir le corps ailleurs, plus loin dans le
même fichier ou dans un fichier séparé.
Il faut un moyen pour indiquer qu’une définition de fonction, écrite en dehors de toute classe,
est en réalité la définition d’une fonction membre d’une classe. Ce moyen s’appelle
opérateur de résolution de portée, dont la syntaxe est
NomDeClasse::
exemple II.5.1
class Points{
int couleur;
void colorier(int pcouleur);
public :
void afficher();
public:
int x;
int y;
};
II.6. LA SURCHARGE:
La surcharge est une des nouvelles techniques permettant d’améliorer la réutilisabilité en
conception Objet. Elle permet d’attribuer le même nom à plusieurs opérateurs ou à plusieurs
fonctions. L’ambiguïté sur la fonction appelée est alors levée après examen du contexte, c’est
à dire du nombre et/ou des types de paramètres. Cette technique est disponible en C++, qui
offre ainsi la possibilité de définir plusieurs fonctions portant le même nom, à la condition que
ces fonctions aient des profils différents.
void affiche(int x)
{
void affiche(float z)
{
Cout<<z;
}
void main()
{
int d = 10;
float pi = atan(1) * 4;
affiche(d);
affiche(pi);
affiche("Fin de l'exemple");
}
Fichier LeMax.h
exemple II.12.1
#include <iostream.h>
#include <vector>
using std::vector;
class LeMax{
public:
int max(const int a,const int b);
char* max( char *a, char *b);
int max(vector<int> tab,int taille); // ne vous perturbez pas par
le type vector<int> ! analyser juste la surcharge !
};
Fichier Surcharge.cpp
Fichier main.cpp
Public double distance (double x,double y); //Calculer la distance entre l’objet en
cours et une autre coordonnée.
Tout comme une fonction C++ classique, il est possible de définir des arguments par défaut.
Ceux-ci permettent à l'utilisateur de ne pas renseigner certains paramètres. Dans l’exemple
précédent, on peut écrire :
int LeMax::max(int a=0,int b=0)
exemple II.7.1
class Rectangle{
public:
// Les constructeurs/////////////////////
Rectangle()
{
largeur=10;
hauteur=10;
}
Rectangle(int largeurInitiale,int hauteurInitiale)
{
largeur=largeurInitiale;
hauteur=hauteurInitiale;
}
/////////////////////////////////////////
L’OBJET RECTANGLE
Le rôle d’un constructeur est d’initialiser un objet, notamment en donnant des valeurs aux
attibuts membres.
Tout objet doit avoir ses valeurs initiales positionnées.Un constructeur permet de fixer ces
valeurs à la création de l’objet.
Toute classe possède un constructeur par défaut implicite, il peut être redéfini.
Une classe peut avoir plusieurs constructeurs qui diffèrent par le nombre et la nature de leur
paramètres(paramètres d’entrée différents), c’est la surcharge de constructeur :
exemple II.6
#include <iostream.h>
#include "Rectangle.cpp"
void main()
{
Rectangle *rect=new Rectangle();
cout<<"LARGEUR : "<<rect->largeur<<endl;
cout<<"HAUTEUR : "<<rect->hauteur<<endl;
delete rect;
rect=new Rectangle(50,50);
cout<<"LARGEUR : "<<rect->largeur<<endl;
cout<<"HAUTEUR : "<<rect->hauteur<<endl;
delete rect;
}
Si on affecte B dans A ceci se traduit par: A et B partagent la même adresse mémoire et donc
le même contenu :
exemple II.8.1
#include <string.h>
class PointNomme{
public:
PointNomme(int a,int b,char *s="")
{
x=a;
Commentaires
On remarque que nous avons effectué l’affectation avant de changer l’attribut label de a et
pourtant b->label a aussi changé!
Cependant si on cherche à faire la duplication de a dans le but de gérer séparément les objets
« égaux » pointés par a et b :
exemple II.8.2
#include <string.h>
class PointNomme{
public:
PointNomme(int a,int b,char *s="")
{
x=a;
y=b;
label=new char[strlen(s)+1];
strcpy(label,s);
}
// Constructeur à partir d’un objet existant : cloneur !
PointNomme(const PointNomme &p)
{
x=p.x;
y=p.y;
label=new char[strlen(p.label)+1];
strcpy(label,p.label);
}
public:
int x,y;
char *label;
};
#include <iostream.h>
#include "PointNomme.cpp"
void main()
Exercice :
Reprendre l’exemple précédent avec des déclarations statiques.
II.8.2. DESTRUCTEUR:
Un destructeur d’une classe donnée est une méthode exécutée automatiquement à chaque
fois qu’une instance de la classe donnée disparait.
L’identificateur est celui de la classe précédé du caractère ~ ;
- C’est une méthode sans type de retour ;
- C’est une méthode sans paramètre, elle ne peut donc pas être surchargée ;
- C’est une méthode en accès public.
class C_crible
{
private :
E_booleen *verite; // Adresse du 1er elts
unsigned nb_element; // Nbre d’elt ds le tableau
public :
// Les constructeurs
C_crible();
C_crible(unsigned);
C_crible(unsigned, E_booleen);
// Le destructeur
~C_crible();
#include <iostream.h>
class Ctest{
int attr;
public:
Ctest()
{
cout<<"----- contructeur par defaut! "<<endl;
~Ctest()
{
cout<<"----- Le destructeur ! "<<endl;
}
};
void blocLocal()
{
cout<<"BLOC LOCAL : "<<endl;
Ctest CTlocal;
}
void main()
{
cout<<"BLOC MAIN : "<<endl;
Ctest *CTmain=new Ctest;
cout<<"Appel du bloc local dans main : "<<endl;
blocLocal ();
cout<<"APPEL DE DELETE : "<<endl;
delete CTmain;
}
NomDeLaClasse(paramètres)
: membre1(paramètres),membre2(paramètres),…
{
Corps du constructeur de NomDeLaClasse
…………..
}
Voici un exemple : On considère la classe Point et la classe Segment formée par deux objets
membres Origine de classe Point et extremite de classe Point. L’appel des constructeurs
s’effectue de la façon suivante :
exemple II.9
class Point{
public:
Point(int px,int py)
{
x=px;
y=py;
}
private:
int x;
int y;
};
class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
public:
Segment(int ox,int oy,int ex,int ey,int ep):
origine(ox,oy),extremite(ex,ey)
{
epaisseur=ep;
}
};
epaisseur=ep;
}
};
class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
public:
Segment(int ox,int oy,int ex,int ey,int ep)
{
epaisseur=ep;
}
};
Une donnée membre dans une classe peut être qualifiée const. Il est alors obligatoire de
l’initialiser lors de la construction d’un objet, et sa valeur ne pourra pas être modifiée à la
suite.
Voici un programme que l’on va tester:
exemple II.10.1
class Segment{
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
const int numeroDeSerie; // membre constant
public:
Segment(int ox,int oy,int ex,int ey,int ep,int num):
origine(ox,oy),extremite(ex,ey)
{
epaisseur=ep;
}
};
Commentaires:
1- Le compilateur oblige d’écrire au moins un constructeur.
2- Le constructeur explicite doit initialiser l’attribut constant
class Segment{
public :
Point origine; //Objet membre
Point extremite; //Objet membre
int epaisseur;
const int numeroDeSerie; // membre constant
public:
Segment(int ox,int oy,int ex,int ey,int ep,int num):
origine(ox,oy),extremite(ex,ey),numeroDeSerie(num)
{
epaisseur=ep;
}
};
#include "Point.cpp"
void main()
{
Segment s=Segment(0,1,2,5,10,200);
s.numeroDeSerie=200; // erreur de compilation.
}
Le mot const placé à la fin de l’entête d’une fonction membre indique que l’état de l’objet à
travers lequel la fonction est appelée ne change pas suite à l’appel: il reste constant. C’est une
manière de dire qu’il s’agit d’une fonction de consultation de l’objet et non d’une fonction de
modification
exemple II.10.2
class Point{
public:
Point(int px,int py)
{
x=px;
y=py;
}
void Deplacer(int a,int b)
{
Dans une classe Geometrie on fait appel à la fonction distance dans une fonction membre
point le plus proche
class Geometrie
{
public:
Point PointLePlusProche (const Point *p,Point *p1, Point *p2)
{
float dp1=p->distance(p1);
float dp2=p->distance(p1);
if (dp1>dp2)
{
return *p2;}
else {
return *p1;}
}
};
On remarque que la fonction exige que le paramètre p de classe point reste constant et ne
change pas de valeur.
Cette utilisation exige que la fonction distance soit constante pour qu’elle ne modifie pas
l’état de l’objet p. C’est une mesure de sécurité prise par le compilateur.
Si on enlève le mot const de distance, le compilateur retourne une erreur.
error C2662: 'distance' : cannot convert 'this' pointer from 'const class
Point' to 'class Point &'
Remarquer que la classe deplacer (a, b): change l’état d’un objet et il est alors impossible de
placer le mot clé const à cette fonction.
2 exemples incorrects:
class C_booleen
{
E_booleen verite;
public :
void set_bool(E_booleen);
E_booleen get_bool(); const
void mensonge(); const
};
void C_booleen::mensonge() const
int main()
{
const C_booleen v;
C_booleen trouve;
// .....
trouve.set_booleen(FAUX);
v.set_booleen(VRAI);
}
Exemple1 : mensonge() est une méthode déclarée const, mais elle modifie l’état de l’objet.
Exemple2 : v est un objet constant sur lequel on applique une méthode de modification !
Un attribut static :
- obeit aux règles d’accès des attributs ;
- est partagé par toutes les instances la classe ;
- est créé et initialisé avant le main() ;
- Sa définition se fait dans la partie globale du programme.
Fichier MonPoint.H
exemple II.11.1
class MonPoint
{
public:
MonPoint(int px, int py);
static nombreDePoints;
private:
int x;
int y;
};
Fichier MonPoint.cpp
#include "MonPoint.h"
int MonPoint::nombreDePoints= 20; // initialization du membre statique
MonPoint::MonPoint(int px, int py)
{
x=px;
y=py;
Fichier Main.cpp
#include <iostream.h>
#include "MonPoint.h"
void main()
{
MonPoint *p=new MonPoint(1,2);
MonPoint *q=new MonPoint(14,72);
int nbstatic=MonPoint::nombreDePoints;
cout <<"Nombre de points statiques :"<<nbstatic<<endl;
cout <<"On vérifie le nombre de points statiques pour p :"<<p-
>nombreDePoints<<endl;
cout <<"On vérifie le nombre de points statiques pour q :"<<q-
>nombreDePoints<<endl;
MonPoint::nombreDePoints=10;
cout <<"On vérifie le nombre de points statiques pour p :"<<p-
>nombreDePoints<<endl;
cout <<"On vérifie le nombre de points statiques pour q :"<<q-
>nombreDePoints<<endl;
}
Commentaires :
- L’initialisation du membre statique nombreDePoints ne peut pas se faire dans le
fichier MonPoint.H mains dans un fichier .CPP
- Vérifier que toute instance de la même classe déclarée p et q donne la même valeur.
- Après son changement MonPoint::nombreDePoints=10; dans la fonction
main() on vérifie que les instances prennent tous la dernière valeur changée.
C’est un attribut membre statique
Exercice : tester le cas où nombreDePoints est un membre privé. Que se passe-t-il?
Une fonction membre statique n’est pas attachée à un objet. Par conséquent:
- Elle ne dispose pas du pointeur this.
- Ces fonctions servent parfois à accéder aux membres statiques PRIVATE.
- On les utilise quand il s’agit d’une fonction qui ne dépend pas d’une instance.
- L’exemple suivant le montre :
Fichier MonPoint.H
exemple II.11.2
class MonPoint
{
public:
MonPoint(int px, int py);
static int getNombreDePoints();
private:
int x;
int y;
static nombreDePoints;
Fichier MonPoint.CPP
#include "MonPoint.h"
int MonPoint::nombreDePoints= 20;
int MonPoint::getNombreDePoints()
{
return nombreDePoints;
}
MonPoint::MonPoint(int px, int py)
{
x=px;
y=py;
}
Commentaires :
Voir qu’on a eu besoin d’une fonction de type static pour accéder au membre statique devenu
PRIVATE : nombredePoints. Mais cette fonction peut ne pas être STATIC !
On considère la classe Circle, on veut comparer 2 cercles par leur rayon. L’utilisation d’une
méthode statique invoque directement la classe lors de l’appel sans avoir besoin à déclarer
une instance. Soit la méthode
Static Circle bigger(Circle *c1,Circle *c2)
L’appel se fait comme suit : Circle::bigger(c1,c2)
Circle.cpp
exemple II.11.3
class Circle{
public:
static int count;
double x,y,r;
public :
Circle(double r)
{
this->r=r;
this->count++;
}
static Circle bigger(Circle* c1,Circle *c2)
{
if (c1->r>c2->r)
{
return *c1;
}
else
{
return *c2;
}
}
Fichier main.cpp
#include "Circle.cpp"
#include <iostream.h>
int Circle::count= 0; // Initialisation d'un membre statique
void main()
{
Le mécanisme de l’héritage consiste en la définition d’une classe par réunion des membres
d’une ou plusieurs classes préexistentes dites classes de base directes et d’un ensemble de
membres spécifiques de la classe nouvelle appelée alors classes dérivées.
Exemples :
Le mécanisme d’héritage est aussi appelé derivation : on parle de classe mere ou classe de
base et de classe fille ou classe derive.
La dérivation définit l’accessibilité des membres de la classe de base (des classes de bases) ;
class C_mere
{
//....
};
class C_deriveedemere : <mode> C_mere
{
//...
};
Classe Compte :
exemple III.1
#include <string>
using namespace std;
class Compte{
private:
string numCompte;
int codeClient;
double solde;
public:
// Les accesseurs
string getNumCompte()
{
};
Commentaires:
- Remarquer l’encapsulation du : numCompte,codeClient et solde
- Inconvénients de ce code: absence de constructeur et destructeur. Que ferait le
compilateur ?
Utilisation de ce code:
#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte;
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}
Exécuter ce programme
Le résultat est mauvais. Pourquoi ?
Que faut-il faire ?
#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte("05-545-012",133454,800.500);
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}
Le résultat de l’exécution :
Commentaire :
Le constructeur prévoit que le solde de départ pourrait être 0, on peut par conséquent appeler
le constructeur comme ceci.
#include <iostream.h>
#include "Compte.cpp"
void main()
{
Compte *monCpt=new Compte("05-545-012",133454);// absence du
paramètre solde.
cout<<"Le solde de votre compte est :"<<monCpt->getSolde()<<endl;
}
Compte(double solde=0)
{
this->solde=solde;
}
Et dans la classe fille ajoutons ce code :
CompteEpargne(double txInteret=0.05):Compte()
{
this->txInteret=txInteret;
}
Remarque
Les constructeurs des classes dérivées appellent en premier lieu les constructeurs des classes
de bases. Alors que les destructeurs des classes dérivées s'exécutent puis appellent les
destructeurs des classes de base.
exemple III.2.
class Compte{
private:
string numCompte;
int codeClient;
double solde;
protected: // -> -> private a été changée en protected
Compte(string numCompte,int codeClient,double solde=0)
{
this->numCompte=numCompte;
this->codeClient=codeClient;
this->solde=solde;
}
Compte(double solde=0)
{
this->solde=solde;
}
// Les accesseurs
string getNumCompte()
{
return numCompte;
}
int getCodeClient()
{
return codeClient;
}
double getSolde()
{
return solde;
}
// Les modificateurs
};
};
Progamme main()
#include <iostream.h>
#include "Compte.cpp"
void main()
{
CompteEpargne *cptEpargne=new CompteEpargne(0.07);
cptEpargne->setSoldeEpargne(200.000);
cout<<"Votre solde Epargne est :"<<cptEpargne->getSoldeEpargne();
EXEMPLE et EXERCICE
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.
exemple III.3.
#include <string>
using namespace std;
#include <vector>
using std::vector;
class Article{
public:
string codeArticle;
string desigArticle;
double quantite;
double prixUnitaire;
public:
Article(string codeArticle,string desigArticle,
double quantite,double prixUnitaire)
{
this->codeArticle=codeArticle;
this->desigArticle=desigArticle;
this->quantite=quantite;
this->prixUnitaire=prixUnitaire;
}
Article(Article *article)
{
this->codeArticle=article->codeArticle;
this->desigArticle=article->desigArticle;
this->quantite=article->quantite;
this->prixUnitaire=article->prixUnitaire;
}
double getQuantite()
{
return quantite;
}
double getPrixUnitaire()
{
return prixUnitaire;
}
double getPrix()
{
return getQuantite()*getPrixUnitaire();
}
};
Classe Boissons
Commentaires:
Le constructeur de la classe Boissons appelle celui de la classe article et complète les
informations manquantes pour l’attribut volume.
Le constructeur de la classe BoissonsChaudes appelle celui de Boissons et complète pour
l’attribut nature
III.4. LE POLYMORPHISME:
C’est un aspect très puissant du paradigme objet. Il permet à une méthode d’adopter plusieurs
formes sur des classes différentes. Quel avantage ?
Essayons à travers les exemples de comprendre ce mécanisme et pourquoi un tel mécanisme
est puissant.
getQuantite() et getPrixUnitaire()
Classe Boissons
exemple III.4
class Boissons:public Article{
public:
double volume;
public:
//Constructeur
double getQuantite()
{
return volume;
}
double getPrixUnitaire()
{
return (prixUnitaire*0.95);
}
}
;
Classe Confitures
double getQuantite()
{
return poids*0.5;
}
double getPrixUnitaire()
{
return (prixUnitaire*0.75);
}
}
;
Classe BoissonsChaudes
double getPrixUnitaire()
{
return (prixUnitaire*0.75);
Remarquer que dans chaque classe les méthodes getQuantite() et getPrixUnitaire() portent la
même signature mais contiennent un code différent propre à la spécificité de la classe.
Première Utilisation :
Main.cpp
#include <iostream.h>
#include "Article.cpp"
#include <string>
using namespace std;
#include <vector>
using std::vector;
void main()
{
Boissons *B1=new Boissons("B001","BOGA",3,4,5);
BoissonsChaudes *Bc=new BoissonsChaudes("WX","CAFE",2.5,0.450,10,"AU
LAIT");
Article *A=new Article(B1);
Article *A1=new Article(Bc);
double b1=B1->getPrixUnitaire();
double a=A->getPrixUnitaire();
double a1=A1->getPrixUnitaire();
cout<<"b1 = "<<b1<<endl;
cout<<"a = "<<a<<endl;
cout<<"a1 = "<<a1<<endl;
}
Resultat de l’exécution :
L’héritage et le polymorphisme sont des mécanismes très puissants mais jusque là les
méthodes ont une liaison statique à la compilation c’est à dire que les 2 lignes suivantes :
double b1=B1->getPrixUnitaire();
double a=A->getPrixUnitaire();
double a1=A1->getPrixUnitaire();
Question : comment faut-il faire pour appliquer le corps spécifique à chaque classe
dynamiquement. C’est à dire qu’à l’exécution, le compilateur applique la méthode
getPrixUnitaire pour chaque objet selon sa propre définition.
exemple III.5.
class Article{
public:
string codeArticle;
string desigArticle;
double quantite;
double prixUnitaire;
public:
Article(string codeArticle,string desigArticle,
double quantite,double prixUnitaire)
{
this->codeArticle=codeArticle;
this->desigArticle=desigArticle;
this->quantite=quantite;
this->prixUnitaire=prixUnitaire;
}
Article(Article *article)
{
this->codeArticle=article->codeArticle;
this->desigArticle=article->desigArticle;
this->quantite=article->quantite;
this->prixUnitaire=article->prixUnitaire;
}
virtual double getQuantite() //méthode virtuelle
{
return quantite;
}
virtual double getPrixUnitaire() // méthode virtuelle
{
return prixUnitaire;
}
double getPrix()
{
return getQuantite()*getPrixUnitaire();
}
};
Le module main()
#include <iostream.h>
#include "Article.cpp"
#include <string>
using namespace std;
#include <vector>
using std::vector;
//tableau d'articles
vector<Article*> ListeArticles;
Explicite : la cast
Si la conversion implicite marche très bien du type B vers le type A, ce n’est pas le cas de la
conversion inverse : il faut s’assurer que cette conversion a un sens.
exemple III.5.2
Class Figure {
Public:
virtual float perimetre()=0;
virtual float surface()=0;
virtual void dessiner();
}
La classe figure est une abstraction. Un programme ne créera pas d’objet Figure, mais des
objets Rectangle, Cercle, etc..
On remarque que la fonction perimetre() introduite au niveau de la classe Figure n’est pas un
service rendu aux programmeurs mais une contrainte : son rôle n’est pas de dire ce qu’est le
périmètre d’une figure, mais d’obliger les futures classes dérivées de figure à le dire.
Exemple :
Figure
perimetre()
getNom()
UnRectangle
longueur : float
largeur : float
UnCarre
cote : float
#include <string>
using namespace std;
class Figure{
public:
virtual float perimetre()=0;
virtual string getNom()=0;
};
class UnRectangle:public Figure
Main()
#include <iostream.h>
#include "Figure.cpp"
#include <vector>
using std::vector;
void main()
{
UnRectangle *Rect=new UnRectangle(10,5);
UnCarre *Carre=new UnCarre(10);
vector<Figure*> ListeFigures;
ListeFigures.push_back(Rect);
ListeFigures.push_back(Carre);
for (int i=0;i<=ListeFigures.size()-1;i++)
{
cout<<"Le PERIMETRE DU "<<ListeFigures[i]->getNom().data()<<"
"<<ListeFigures[i]->perimetre()<<endl;
}
}
A chaque famille de liens entre objets correspond une relation entre les classes de ces mêmes
objets.
- L’association
- L’agrégation
- La généralisation / spécialisation (héritage)
IV.1. ASSOCIATIONS
• Les liens permettent d'établir des relations entre objets (ou instances). Les associations
permettent d'établir des relations entre classes.
• Une association est une abstraction des liens qui existent entre les objets instances des
classes associées.
• L’association exprime une connexion entre classes.
• Les associations se représentent de la même manière.
Les associations décrivent des relations structurelles entre des classes, qui deviennent des
liens entre les instances de ces classes. Ces liens représentent une navigation interobjet, c'est-
à-dire le fait qu'une instance puisse extraire une autre instance via le lien navigable.
Lorsque l'extrémité d'une association est navigable, cela signifie que vous souhaitez pouvoir
extraire l'instance de la classe à laquelle cette extrémité est liée, cette instance s'affiche sous
forme d'attribut migré dans l'instance courante d'une classe. Le nom de rôle de cette extrémité
peut être utilisé pour clarifier la structure utilisée pour représenter le lien.
Par exemple, considérons une association entre la classe Société et la classe Personne. La
navigation est possible dans les deux directions pour permettre à Société d'obtenir la liste des
employés, et à chaque employé d'obtenir le nom de sa société.
Lorsque la multiplicité d'association est supérieure à un, le type est le plus souvent un tableau
de la classe, affiché avec des signes [ ]. Dans notre exemple, l'attribut Employé de la classe
Société est de type Personne et a un tableau de valeurs. Lorsque vous instanciez la classe
Société, vous obtenez une liste d'employés à stocker pour chaque société.
La classe d’association possède à la fois les caractéristiques d’une association et celle d’une
classe et peut à ce titre participer à d’autres relations dans le modèle.
En C++ :
Pour résoudre ce problème nous avaons créé 2 champs de type Etudiant et Cours dans la
classe Evaluation. Cette classe gère le lien entrs les Etudiants et les Cours qu’il doivent
passer.
La notion de cours,dateEvaluation et note n’est ni la responsabilité de la classe Etudiant ni la
responsabilité de la classe Cours c’est plutôt une classe qui gère ces notions tout en ayant
l’information quel Etudiant pour quel Cours.
IV.2. L’AGREGATION
Une agrégation représente une association non symétrique dans laquelle une des extrémités
joue un rôle prédominant par rapport à l’autre extrémité.
Agrégation = relation partie de.
• Par défaut, l’association représente un couplage faible, les classes associées restant
relativement indépendantes l’une de l’autre.
• L’agrégation est une forme particulière d’association qui exprime un couplage plus fort
entre classes.
• L’agrégation permet de représenter des relations de type
– maître et esclaves,
– tout et parties
– ou composés et composants.
Une agrégation peut être perçue comme une association. Cependant une association ne peut
être une agrégation
L’agrégation spécifie la relation «est partie de» (une sémantique de style il faut pouvoir dire
“ est composée de... ” ou “ est une partie de ... ”.
Exemple 1
Exemple:
– La composition et l’agrégation sont deux vues subjectives qui sont utilisées pour
ajouter de la sémantique au modèle lorsque c’est pertinent de le faire même si cette
pertinence ne reflète pas la réalité.
–